diff --git a/SkeinPyPy_NewUI/SkeinforgeVersion b/SkeinPyPy_NewUI/SkeinforgeVersion new file mode 100644 index 0000000..4f56f12 --- /dev/null +++ b/SkeinPyPy_NewUI/SkeinforgeVersion @@ -0,0 +1,2 @@ +This SkeinPyPy version is based in Skeinforge: 49 + diff --git a/SkeinPyPy_NewUI/__init__.py b/SkeinPyPy_NewUI/__init__.py new file mode 100644 index 0000000..6e968ba --- /dev/null +++ b/SkeinPyPy_NewUI/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 0 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/__init__.py new file mode 100644 index 0000000..1638033 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/__init__.py @@ -0,0 +1,13 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys + +numberOfLevelsDeepInPackageHierarchy = 1 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/archive.py b/SkeinPyPy_NewUI/fabmetheus_utilities/archive.py new file mode 100644 index 0000000..8b6d93f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/archive.py @@ -0,0 +1,380 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +import os +import sys +import traceback + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalTemporarySettingsPath = os.path.join(os.path.expanduser('~'), '.skeinforge_pypy') + + +def addToNamePathDictionary(directoryPath, namePathDictionary): + 'Add to the name path dictionary.' + pluginFileNames = getPluginFileNamesFromDirectoryPath(directoryPath) + for pluginFileName in pluginFileNames: + namePathDictionary[pluginFileName.replace('_', '')] = os.path.join(directoryPath, pluginFileName) + +def getAbsoluteFolderPath(filePath, folderName=''): + 'Get the absolute folder path.' + absoluteFolderPath = os.path.dirname(os.path.abspath(filePath)) + if folderName == '': + return absoluteFolderPath + return os.path.join(absoluteFolderPath, folderName) + +def getAbsoluteFrozenFolderPath(filePath, folderName=''): + 'Get the absolute frozen folder path.' + if hasattr(sys, 'frozen'): + if '.py' in filePath: + filePath = ''.join(filePath.rpartition('\\')[: 2]) + filePath = os.path.join(filePath, 'skeinforge_application') + return getAbsoluteFolderPath(filePath, folderName) + +def getAnalyzePluginsDirectoryPath(subName=''): + 'Get the analyze plugins directory path.' + return getJoinedPath(getSkeinforgePluginsPath('analyze_plugins'), subName) + +def getCraftPluginsDirectoryPath(subName=''): + 'Get the craft plugins directory path.' + return getJoinedPath(getSkeinforgePluginsPath('craft_plugins'), subName) + +def getDocumentationPath(subName=''): + 'Get the documentation file path.' + return getJoinedPath(getFabmetheusPath('documentation'), subName) + +def getElementsPath(subName=''): + 'Get the evaluate_elements directory path.' + return getJoinedPath(getGeometryUtilitiesPath('evaluate_elements'), subName) + +def getEndsWithList(word, wordEndings): + 'Determine if the word ends with a list.' + for wordEnding in wordEndings: + if word.endswith(wordEnding): + return True + return False + +def getFabmetheusPath(subName=''): + 'Get the fabmetheus directory path.' + fabmetheusFile = None + if hasattr(sys, 'frozen'): + fabmetheusFile = unicode(sys.executable, sys.getfilesystemencoding()) + else: + fabmetheusFile = os.path.dirname(os.path.abspath(__file__)) + return getJoinedPath(os.path.dirname(fabmetheusFile), subName) + +def getFabmetheusToolsPath(subName=''): + 'Get the fabmetheus tools directory path.' + return getJoinedPath(getFabmetheusUtilitiesPath('fabmetheus_tools'), subName) + +def getFabmetheusUtilitiesPath(subName=''): + 'Get the fabmetheus utilities directory path.' + return getJoinedPath(getFabmetheusPath('fabmetheus_utilities'), subName) + +def getFileNamesByFilePaths(pluginFilePaths): + 'Get the file names of the plugins by the file paths.' + fileNames = [] + for pluginFilePath in pluginFilePaths: + pluginBasename = os.path.basename(pluginFilePath) + pluginBasename = getUntilDot(pluginBasename) + fileNames.append(pluginBasename) + return fileNames + +def getFilePaths(fileInDirectory=''): + 'Get the file paths in the directory of the file in directory.' + directoryName = os.getcwd() + if fileInDirectory != '': + directoryName = os.path.dirname(fileInDirectory) + return getFilePathsByDirectory(directoryName) + +def getFilePathsByDirectory(directoryName): + 'Get the file paths in the directory of the file in directory.' + absoluteDirectoryPath = os.path.abspath(directoryName) + directory = os.listdir(directoryName) + filePaths = [] + for fileName in directory: + filePaths.append(os.path.join(absoluteDirectoryPath, fileName)) + return filePaths + +def getFilePathsRecursively(fileInDirectory=''): + 'Get the file paths in the directory of the file in directory.' + filePaths = getFilePaths(fileInDirectory) + filePathsRecursively = filePaths[:] + for filePath in filePaths: + if os.path.isdir(filePath): + directory = os.listdir(filePath) + if len(directory) > 0: + filePathsRecursively += getFilePathsRecursively(os.path.join(filePath, directory[0])) + return filePathsRecursively + +def getFilePathWithUnderscoredBasename(fileName, suffix): + 'Get the file path with all spaces in the basename replaced with underscores.' + suffixFileName = getUntilDot(fileName) + suffix + suffixDirectoryName = os.path.dirname(suffixFileName) + suffixReplacedBaseName = os.path.basename(suffixFileName).replace(' ', '_') + return os.path.join(suffixDirectoryName, suffixReplacedBaseName) + +def getFilesWithFileTypesWithoutWords(fileTypes, words = [], fileInDirectory=''): + 'Get files which have a given file type, but with do not contain a word in a list.' + filesWithFileTypes = [] + for filePath in getFilePaths(fileInDirectory): + for fileType in fileTypes: + if isFileWithFileTypeWithoutWords(fileType, filePath, words): + filesWithFileTypes.append(filePath) + filesWithFileTypes.sort() + return filesWithFileTypes + +def getFilesWithFileTypesWithoutWordsRecursively(fileTypes, words = [], fileInDirectory=''): + 'Get files recursively which have a given file type, but with do not contain a word in a list.' + filesWithFileTypesRecursively = [] + for filePath in getFilePathsRecursively(fileInDirectory): + for fileType in fileTypes: + if isFileWithFileTypeWithoutWords(fileType, filePath, words): + filesWithFileTypesRecursively.append(filePath) + filesWithFileTypesRecursively.sort() + return filesWithFileTypesRecursively + +def getFilesWithFileTypeWithoutWords(fileType, words = [], fileInDirectory=''): + 'Get files which have a given file type, but with do not contain a word in a list.' + filesWithFileType = [] + for filePath in getFilePaths(fileInDirectory): + if isFileWithFileTypeWithoutWords(fileType, filePath, words): + filesWithFileType.append(filePath) + filesWithFileType.sort() + return filesWithFileType + +def getFileText(fileName, printWarning=True, readMode='r'): + 'Get the entire text of a file.' + try: + file = open(fileName, readMode) + fileText = file.read() + file.close() + return fileText + except IOError: + if printWarning: + print('The file ' + fileName + ' does not exist.') + return '' + +def getFileTextInFileDirectory(fileInDirectory, fileName, readMode='r'): + 'Get the entire text of a file in the directory of the file in directory.' + absoluteFilePathInFileDirectory = os.path.join(os.path.dirname(fileInDirectory), fileName) + return getFileText(absoluteFilePathInFileDirectory, True, readMode) + +def getFundamentalsPath(subName=''): + 'Get the evaluate_fundamentals directory path.' + return getJoinedPath(getGeometryUtilitiesPath('evaluate_fundamentals'), subName) + +def getGeometryDictionary(folderName): + 'Get to the geometry name path dictionary.' + geometryDictionary={} + geometryDirectory = getGeometryPath() + addToNamePathDictionary(os.path.join(geometryDirectory, folderName), geometryDictionary) + geometryPluginsDirectory = getFabmetheusUtilitiesPath('geometry_plugins') + addToNamePathDictionary(os.path.join(geometryPluginsDirectory, folderName), geometryDictionary) + return geometryDictionary + +def getGeometryPath(subName=''): + 'Get the geometry directory path.' + return getJoinedPath(getFabmetheusUtilitiesPath('geometry'), subName) + +def getGeometryToolsPath(subName=''): + 'Get the geometry tools directory path.' + return getJoinedPath(getGeometryPath('geometry_tools'), subName) + +def getGeometryUtilitiesPath(subName=''): + 'Get the geometry_utilities directory path.' + return getJoinedPath(getGeometryPath('geometry_utilities'), subName) + +def getInterpretPluginsPath(subName=''): + 'Get the interpret plugins directory path.' + return getJoinedPath(getFabmetheusToolsPath('interpret_plugins'), subName) + +def getJoinedPath(path, subName=''): + 'Get the joined file path.' + if subName == '': + return path + return os.path.join(path, subName) + +def getModuleWithDirectoryPath(directoryPath, fileName): + 'Get the module from the fileName and folder name.' + if fileName == '': + print('The file name in getModule in archive was empty.') + return None + originalSystemPath = sys.path[:] + try: + sys.path.insert(0, directoryPath) + folderPluginsModule = __import__(fileName) + sys.path = originalSystemPath + return folderPluginsModule + except: + sys.path = originalSystemPath + print('') + print('Exception traceback in getModuleWithDirectoryPath in archive:') + traceback.print_exc(file=sys.stdout) + print('') + print('That error means; could not import a module with the fileName ' + fileName) + print('and an absolute directory name of ' + directoryPath) + print('') + return None + +def getModuleWithPath(path): + 'Get the module from the path.' + return getModuleWithDirectoryPath(os.path.dirname(path), os.path.basename(path)) + +def getPluginFileNamesFromDirectoryPath(directoryPath): + 'Get the file names of the python plugins in the directory path.' + fileInDirectory = os.path.join(directoryPath, '__init__.py') + return getFileNamesByFilePaths(getPythonFileNamesExceptInit(fileInDirectory)) + +def getProfilesPath(subName=''): + 'Get the profiles directory path, which is the settings directory joined with profiles.' + return getJoinedPath(getSettingsPath('profiles'), subName) + +def getPythonDirectoryNames(directoryName): + 'Get the python directories.' + pythonDirectoryNames = [] + directory = os.listdir(directoryName) + for fileName in directory: + subdirectoryName = os.path.join(directoryName, fileName) + if os.path.isdir(subdirectoryName): + if os.path.isfile(os.path.join(subdirectoryName, '__init__.py')): + pythonDirectoryNames.append(subdirectoryName) + return pythonDirectoryNames + +def getPythonDirectoryNamesRecursively(directoryName=''): + 'Get the python directories recursively.' + recursivePythonDirectoryNames = [] + if directoryName == '': + directoryName = os.getcwd() + if os.path.isfile(os.path.join(directoryName, '__init__.py')): + recursivePythonDirectoryNames.append(directoryName) + pythonDirectoryNames = getPythonDirectoryNames(directoryName) + for pythonDirectoryName in pythonDirectoryNames: + recursivePythonDirectoryNames += getPythonDirectoryNamesRecursively(pythonDirectoryName) + else: + return [] + return recursivePythonDirectoryNames + +def getPythonFileNamesExceptInit(fileInDirectory=''): + 'Get the python fileNames of the directory which the fileInDirectory is in, except for the __init__.py file.' + pythonFileNamesExceptInit = getFilesWithFileTypeWithoutWords('py', ['__init__.py'], fileInDirectory) + pythonFileNamesExceptInit.sort() + return pythonFileNamesExceptInit + +def getPythonFileNamesExceptInitRecursively(directoryName=''): + 'Get the python fileNames of the directory recursively, except for the __init__.py files.' + pythonDirectoryNames = getPythonDirectoryNamesRecursively(directoryName) + pythonFileNamesExceptInitRecursively = [] + for pythonDirectoryName in pythonDirectoryNames: + pythonFileNamesExceptInitRecursively += getPythonFileNamesExceptInit(os.path.join(pythonDirectoryName, '__init__.py')) + pythonFileNamesExceptInitRecursively.sort() + return pythonFileNamesExceptInitRecursively + +def getSettingsPath(subName=''): + 'Get the settings directory path, which is the home directory joined with .skeinforge.' + global globalTemporarySettingsPath + return getJoinedPath(globalTemporarySettingsPath, subName) + +def getSkeinforgePath(subName=''): + 'Get the skeinforge directory path.' + return getJoinedPath(getFabmetheusPath('skeinforge_application'), subName) + +def getSkeinforgePluginsPath(subName=''): + 'Get the skeinforge plugins directory path.' + return getJoinedPath(getSkeinforgePath('skeinforge_plugins'), subName) + +def getSummarizedFileName(fileName): + 'Get the fileName basename if the file is in the current working directory, otherwise return the original full name.' + if os.getcwd() == os.path.dirname(fileName): + return os.path.basename(fileName) + return fileName + +def getTemplatesPath(subName=''): + 'Get the templates directory path.' + return getJoinedPath(getFabmetheusUtilitiesPath('templates'), subName) + +def getTextIfEmpty(fileName, text): + 'Get the text from a file if it the text is empty.' + if text != '': + return text + return getFileText(fileName) + +def getTextLines(text): + 'Get the all the lines of text of a text.' + if '\r' in text: + text = text.replace('\r', '\n').replace('\n\n', '\n') + textLines = text.split('\n') + if len(textLines) == 1: + if textLines[0] == '': + return [] + return textLines + +def getUntilDot(text): + 'Get the text until the last dot, if any.' + dotIndex = text.rfind('.') + if dotIndex < 0: + return text + return text[: dotIndex] + +def getVersionFileName(): + 'Get the file name of the version date.getFabmetheusUtilitiesPath(subName='')' + return getFabmetheusUtilitiesPath('version.txt') + +def isFileWithFileTypeWithoutWords(fileType, fileName, words): + 'Determine if file has a given file type, but with does not contain a word in a list.' + fileName = os.path.basename(fileName) + fileTypeDot = '.' + fileType + if not fileName.endswith(fileTypeDot): + return False + for word in words: + if fileName.find(word) >= 0: + return False + return True + +def makeDirectory(directoryPath): + 'Make a directory if it does not already exist.' + if os.path.isdir(directoryPath): + return + try: + print('The following directory was made:') + print(os.path.abspath(directoryPath)) + os.makedirs(directoryPath) + except OSError: + print('Skeinforge can not make the directory %s so give it read/write permission for that directory and the containing directory.' % directoryPath) + +def removeBackupFilesByType(fileType): + 'Remove backup files by type.' + backupFilePaths = getFilesWithFileTypesWithoutWordsRecursively([fileType + '~']) + for backupFilePath in backupFilePaths: + os.remove(backupFilePath) + +def removeBackupFilesByTypes(fileTypes): + 'Remove backup files by types.' + for fileType in fileTypes: + removeBackupFilesByType(fileType) + +def writeFileMessageEnd(end, fileName, fileText, message): + 'Write to a fileName with a suffix and print a message.' + suffixFileName = getUntilDot(fileName) + end + writeFileText(suffixFileName, fileText) + print(message + getSummarizedFileName(suffixFileName)) + +def writeFileText(fileName, fileText, writeMode='w+'): + 'Write a text to a file.' + try: + file = open(fileName, writeMode) + file.write(fileText) + file.close() + except IOError: + print('The file ' + fileName + ' can not be written to.') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/euclidean.py b/SkeinPyPy_NewUI/fabmetheus_utilities/euclidean.py new file mode 100644 index 0000000..072a5fa --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/euclidean.py @@ -0,0 +1,2498 @@ +""" +Euclidean is a collection of python utilities for complex numbers, paths, polygons & Vector3s. + +To use euclidean, install python 2.x on your machine, which is avaliable from http://www.python.org/download/ + +Then in the folder which euclidean is in, type 'python' in a shell to run the python interpreter. Finally type 'import euclidean' to import these utilities and 'from vector3 import Vector3' to import the Vector3 class. + + +Below are examples of euclidean use. + +>>> from euclidean import * +>>> origin=complex() +>>> right=complex(1.0,0.0) +>>> back=complex(0.0,1.0) +>>> getMaximum(right,back) +1.0, 1.0 +>>> polygon=[origin, right, back] +>>> getLoopLength(polygon) +3.4142135623730949 +>>> getAreaLoop(polygon) +0.5 +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import xml_simple_writer +import cStringIO +import math +import random + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalGoldenAngle = 3.8832220774509332 # (math.sqrt(5.0) - 1.0) * math.pi +globalGoldenRatio = 1.6180339887498948482045868 # math.sqrt(1.25) + 0.5 +globalTau = math.pi + math.pi # http://tauday.com/ + + +def addElementToListDictionary(element, key, listDictionary): + 'Add an element to the list table.' + if key in listDictionary: + listDictionary[key].append(element) + else: + listDictionary[key] = [element] + +def addElementToListDictionaryIfNotThere(element, key, listDictionary): + 'Add the value to the lists.' + if key in listDictionary: + elements = listDictionary[key] + if element not in elements: + elements.append(element) + else: + listDictionary[key] = [element] + +def addElementToPixelList( element, pixelDictionary, x, y ): + 'Add an element to the pixel list.' + addElementToListDictionary( element, (x, y), pixelDictionary ) + +def addElementToPixelListFromPoint( element, pixelDictionary, point ): + 'Add an element to the pixel list.' + addElementToPixelList( element, pixelDictionary, int( round( point.real ) ), int( round( point.imag ) ) ) + +def addHorizontallyBoundedPoint(begin, center, end, horizontalBegin, horizontalEnd, path): + 'Add point if it is within the horizontal bounds.' + if center.real >= horizontalEnd and center.real <= horizontalBegin: + path.append(center) + return + if end != None: + if center.real > horizontalBegin and end.real <= horizontalBegin: + centerMinusEnd = center - end + along = (center.real - horizontalBegin) / centerMinusEnd.real + path.append(center - along * centerMinusEnd) + return + if begin != None: + if center.real < horizontalEnd and begin.real >= horizontalEnd: + centerMinusBegin = center - begin + along = (center.real - horizontalEnd) / centerMinusBegin.real + path.append(center - along * centerMinusBegin) + +def addListToListTable( elementList, key, listDictionary ): + 'Add a list to the list table.' + if key in listDictionary: + listDictionary[key] += elementList + else: + listDictionary[key] = elementList + +def addLoopToPixelTable( loop, pixelDictionary, width ): + 'Add loop to the pixel table.' + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, None, width ) + +def addNestedRingBeginning(distanceFeedRate, loop, z): + 'Add nested ring beginning to gcode output.' + distanceFeedRate.addLine('()') + distanceFeedRate.addLine('()') + for point in loop: + pointVector3 = Vector3(point.real, point.imag, z) + distanceFeedRate.addLine(distanceFeedRate.getBoundaryLine(pointVector3)) + +def addPathToPixelTable( path, pixelDictionary, value, width ): + 'Add path to the pixel table.' + for pointIndex in xrange( len(path) - 1 ): + pointBegin = path[pointIndex] + pointEnd = path[pointIndex + 1] + addValueSegmentToPixelTable( pointBegin, pointEnd, pixelDictionary, value, width ) + +def addPixelTableToPixelTable( fromPixelTable, intoPixelTable ): + 'Add from pixel table to the into pixel table.' + for fromPixelTableKey in fromPixelTable.keys(): + intoPixelTable[ fromPixelTableKey ] = fromPixelTable[ fromPixelTableKey ] + +def addPixelToPixelTableWithSteepness( isSteep, pixelDictionary, value, x, y ): + 'Add pixels to the pixel table with steepness.' + if isSteep: + pixelDictionary[(y, x)] = value + else: + pixelDictionary[(x, y)] = value + +def addPointToPath( path, pixelDictionary, point, value, width ): + 'Add a point to a path and the pixel table.' + path.append(point) + if len(path) < 2: + return + begin = path[-2] + addValueSegmentToPixelTable( begin, point, pixelDictionary, value, width ) + +def addSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, shortenDistanceBegin, shortenDistanceEnd, width ): + 'Add line segment to the pixel table.' + if abs( beginComplex - endComplex ) <= 0.0: + return + beginComplex /= width + endComplex /= width + if shortenDistanceBegin > 0.0: + endMinusBeginComplex = endComplex - beginComplex + endMinusBeginComplexLength = abs( endMinusBeginComplex ) + if endMinusBeginComplexLength < shortenDistanceBegin: + return + beginComplex = beginComplex + endMinusBeginComplex * shortenDistanceBegin / endMinusBeginComplexLength + if shortenDistanceEnd > 0.0: + beginMinusEndComplex = beginComplex - endComplex + beginMinusEndComplexLength = abs( beginMinusEndComplex ) + if beginMinusEndComplexLength < 0.0: + return + endComplex = endComplex + beginMinusEndComplex * shortenDistanceEnd / beginMinusEndComplexLength + deltaX = endComplex.real - beginComplex.real + deltaY = endComplex.imag - beginComplex.imag + isSteep = abs( deltaY ) > abs( deltaX ) + if isSteep: + beginComplex = complex( beginComplex.imag, beginComplex.real ) + endComplex = complex( endComplex.imag, endComplex.real ) + if beginComplex.real > endComplex.real: + endComplex, beginComplex = beginComplex, endComplex + deltaX = endComplex.real - beginComplex.real + deltaY = endComplex.imag - beginComplex.imag + if deltaX > 0.0: + gradient = deltaY / deltaX + else: + gradient = 0.0 + print('Warning, deltaX in addSegmentToPixelTable in euclidean is 0.') + print(beginComplex) + print(endComplex) + print(shortenDistanceBegin) + print(shortenDistanceEnd) + print(width) + xBegin = int(round(beginComplex.real)) + xEnd = int(round(endComplex.real)) + yIntersection = beginComplex.imag - beginComplex.real * gradient + if isSteep: + pixelDictionary[( int( round( beginComplex.imag ) ), xBegin)] = None + pixelDictionary[( int( round( endComplex.imag ) ), xEnd )] = None + for x in xrange( xBegin + 1, xEnd ): + y = int( math.floor( yIntersection + x * gradient ) ) + pixelDictionary[(y, x)] = None + pixelDictionary[(y + 1, x)] = None + else: + pixelDictionary[(xBegin, int( round( beginComplex.imag ) ) )] = None + pixelDictionary[(xEnd, int( round( endComplex.imag ) ) )] = None + for x in xrange( xBegin + 1, xEnd ): + y = int( math.floor( yIntersection + x * gradient ) ) + pixelDictionary[(x, y)] = None + pixelDictionary[(x, y + 1)] = None + +def addSquareTwoToPixelDictionary(pixelDictionary, point, value, width): + 'Add square with two pixels around the center to pixel dictionary.' + point /= width + x = int(round(point.real)) + y = int(round(point.imag)) + for xStep in xrange(x - 2, x + 3): + for yStep in xrange(y - 2, y + 3): + pixelDictionary[(xStep, yStep)] = value + +def addToThreadsFromLoop(extrusionHalfWidth, gcodeType, loop, oldOrderedLocation, skein): + 'Add to threads from the last location from loop.' + loop = getLoopStartingClosest(extrusionHalfWidth, oldOrderedLocation.dropAxis(), loop) + oldOrderedLocation.x = loop[0].real + oldOrderedLocation.y = loop[0].imag + gcodeTypeStart = gcodeType + if isWiddershins(loop): + skein.distanceFeedRate.addLine('(<%s> outer )' % gcodeType) + else: + skein.distanceFeedRate.addLine('(<%s> inner )' % gcodeType) + skein.addGcodeFromThreadZ(loop + [loop[0]], oldOrderedLocation.z) + skein.distanceFeedRate.addLine('()' % gcodeType) + +def addToThreadsRemove(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence): + 'Add to threads from the last location from nested rings.' + while len(nestedRings) > 0: + getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence) + +def addValueSegmentToPixelTable( beginComplex, endComplex, pixelDictionary, value, width ): + 'Add line segment to the pixel table.' + if abs( beginComplex - endComplex ) <= 0.0: + return + beginComplex /= width + endComplex /= width + deltaX = endComplex.real - beginComplex.real + deltaY = endComplex.imag - beginComplex.imag + isSteep = abs( deltaY ) > abs( deltaX ) + if isSteep: + beginComplex = complex( beginComplex.imag, beginComplex.real ) + endComplex = complex( endComplex.imag, endComplex.real ) + if beginComplex.real > endComplex.real: + endComplex, beginComplex = beginComplex, endComplex + deltaX = endComplex.real - beginComplex.real + deltaY = endComplex.imag - beginComplex.imag + if deltaX > 0.0: + gradient = deltaY / deltaX + else: + gradient = 0.0 + print('Warning, deltaX in addValueSegmentToPixelTable in euclidean is 0.') + print(beginComplex) + print(value) + print(endComplex) + print(width) + xBegin = int(round(beginComplex.real)) + xEnd = int(round(endComplex.real)) + yIntersection = beginComplex.imag - beginComplex.real * gradient + if isSteep: + pixelDictionary[(int( round( beginComplex.imag ) ), xBegin)] = value + pixelDictionary[(int( round( endComplex.imag ) ), xEnd)] = value + for x in xrange( xBegin + 1, xEnd ): + y = int( math.floor( yIntersection + x * gradient ) ) + pixelDictionary[(y, x)] = value + pixelDictionary[(y + 1, x)] = value + else: + pixelDictionary[(xBegin, int( round( beginComplex.imag ) ))] = value + pixelDictionary[(xEnd, int( round( endComplex.imag ) ))] = value + for x in xrange( xBegin + 1, xEnd ): + y = int( math.floor( yIntersection + x * gradient ) ) + pixelDictionary[(x, y)] = value + pixelDictionary[(x, y + 1)] = value + +def addValueToOutput(depth, keyInput, output, value): + 'Add value to the output.' + depthStart = ' ' * depth + output.write('%s%s:' % (depthStart, keyInput)) + if value.__class__ == dict: + output.write('\n') + keys = value.keys() + keys.sort() + for key in keys: + addValueToOutput(depth + 1, key, output, value[key]) + return + if value.__class__ == list: + output.write('\n') + for elementIndex, element in enumerate(value): + addValueToOutput(depth + 1, elementIndex, output, element) + return + output.write(' %s\n' % value) + +def addXIntersectionIndexesFromLoopListsY( loopLists, xIntersectionIndexList, y ): + 'Add the x intersection indexes for the loop lists.' + for loopListIndex in xrange( len(loopLists) ): + loopList = loopLists[ loopListIndex ] + addXIntersectionIndexesFromLoopsY( loopList, loopListIndex, xIntersectionIndexList, y ) + +def addXIntersectionIndexesFromLoopsY( loops, solidIndex, xIntersectionIndexList, y ): + 'Add the x intersection indexes for the loops.' + for loop in loops: + addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y ) + +def addXIntersectionIndexesFromLoopY( loop, solidIndex, xIntersectionIndexList, y ): + 'Add the x intersection indexes for a loop.' + for pointIndex in xrange(len(loop)): + pointFirst = loop[pointIndex] + pointSecond = loop[(pointIndex + 1) % len(loop)] + xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y ) + if xIntersection != None: + xIntersectionIndexList.append( XIntersectionIndex( solidIndex, xIntersection ) ) + +def addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList ): + 'Add the x intersection indexes from the segment.' + for endpoint in segment: + xIntersectionIndexList.append( XIntersectionIndex( index, endpoint.point.real ) ) + +def addXIntersectionIndexesFromSegments( index, segments, xIntersectionIndexList ): + 'Add the x intersection indexes from the segments.' + for segment in segments: + addXIntersectionIndexesFromSegment( index, segment, xIntersectionIndexList ) + +def addXIntersectionIndexesFromXIntersections( index, xIntersectionIndexList, xIntersections ): + 'Add the x intersection indexes from the XIntersections.' + for xIntersection in xIntersections: + xIntersectionIndexList.append( XIntersectionIndex( index, xIntersection ) ) + +def addXIntersections( loop, xIntersections, y ): + 'Add the x intersections for a loop.' + for pointIndex in xrange(len(loop)): + pointFirst = loop[pointIndex] + pointSecond = loop[(pointIndex + 1) % len(loop)] + xIntersection = getXIntersectionIfExists( pointFirst, pointSecond, y ) + if xIntersection != None: + xIntersections.append( xIntersection ) + +def addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width): + 'Add the x intersections for a loop into a table.' + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + if pointBegin.imag > pointEnd.imag: + pointOriginal = pointBegin + pointBegin = pointEnd + pointEnd = pointOriginal + fillBegin = int( math.ceil( pointBegin.imag / width ) ) + fillEnd = int( math.ceil( pointEnd.imag / width ) ) + if fillEnd > fillBegin: + secondMinusFirstComplex = pointEnd - pointBegin + secondMinusFirstImaginaryOverReal = secondMinusFirstComplex.real / secondMinusFirstComplex.imag + beginRealMinusImaginary = pointBegin.real - pointBegin.imag * secondMinusFirstImaginaryOverReal + for fillLine in xrange( fillBegin, fillEnd ): + y = fillLine * width + xIntersection = y * secondMinusFirstImaginaryOverReal + beginRealMinusImaginary + addElementToListDictionary( xIntersection, fillLine, xIntersectionsTable ) + +def addXIntersectionsFromLoops(loops, xIntersections, y): + 'Add the x intersections for the loops.' + for loop in loops: + addXIntersections(loop, xIntersections, y) + +def addXIntersectionsFromLoopsForTable(loops, xIntersectionsTable, width): + 'Add the x intersections for a loop into a table.' + for loop in loops: + addXIntersectionsFromLoopForTable(loop, xIntersectionsTable, width) + +def compareSegmentLength( endpoint, otherEndpoint ): + 'Get comparison in order to sort endpoints in ascending order of segment length.' + if endpoint.segmentLength > otherEndpoint.segmentLength: + return 1 + if endpoint.segmentLength < otherEndpoint.segmentLength: + return - 1 + return 0 + +def concatenateRemovePath( connectedPaths, pathIndex, paths, pixelDictionary, segments, width ): + 'Get connected paths from paths.' + bottomSegment = segments[ pathIndex ] + path = paths[ pathIndex ] + if bottomSegment == None: + connectedPaths.append(path) + return + endpoints = getEndpointsFromSegments( segments[ pathIndex + 1 : ] ) + bottomSegmentEndpoint = bottomSegment[0] + nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath( endpoints, bottomSegmentEndpoint.path, pixelDictionary, width ) + if nextEndpoint == None: + bottomSegmentEndpoint = bottomSegment[1] + nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath( endpoints, bottomSegmentEndpoint.path, pixelDictionary, width ) + if nextEndpoint == None: + connectedPaths.append(path) + return + if len( bottomSegmentEndpoint.path ) > 0 and len( nextEndpoint.path ) > 0: + bottomEnd = bottomSegmentEndpoint.path[-1] + nextBegin = nextEndpoint.path[-1] + nextMinusBottomNormalized = getNormalized( nextBegin - bottomEnd ) + if len( bottomSegmentEndpoint.path ) > 1: + bottomPenultimate = bottomSegmentEndpoint.path[-2] + if getDotProduct( getNormalized( bottomPenultimate - bottomEnd ), nextMinusBottomNormalized ) > 0.9: + connectedPaths.append(path) + return + if len( nextEndpoint.path ) > 1: + nextPenultimate = nextEndpoint.path[-2] + if getDotProduct( getNormalized( nextPenultimate - nextBegin ), - nextMinusBottomNormalized ) > 0.9: + connectedPaths.append(path) + return + nextEndpoint.path.reverse() + concatenatedPath = bottomSegmentEndpoint.path + nextEndpoint.path + paths[ nextEndpoint.pathIndex ] = concatenatedPath + segments[ nextEndpoint.pathIndex ] = getSegmentFromPath( concatenatedPath, nextEndpoint.pathIndex ) + addValueSegmentToPixelTable( bottomSegmentEndpoint.point, nextEndpoint.point, pixelDictionary, None, width ) + +def getAngleAroundZAxisDifference( subtractFromVec3, subtractVec3 ): + 'Get the angle around the Z axis difference between a pair of Vector3s.' + subtractVectorMirror = complex( subtractVec3.x , - subtractVec3.y ) + differenceVector = getRoundZAxisByPlaneAngle( subtractVectorMirror, subtractFromVec3 ) + return math.atan2( differenceVector.y, differenceVector.x ) + +def getAngleDifferenceByComplex( subtractFromComplex, subtractComplex ): + 'Get the angle between a pair of normalized complexes.' + subtractComplexMirror = complex( subtractComplex.real , - subtractComplex.imag ) + differenceComplex = subtractComplexMirror * subtractFromComplex + return math.atan2( differenceComplex.imag, differenceComplex.real ) + +def getAreaLoop(loop): + 'Get the area of a complex polygon.' + areaLoopDouble = 0.0 + for pointIndex, point in enumerate(loop): + pointEnd = loop[(pointIndex + 1) % len(loop)] + areaLoopDouble += point.real * pointEnd.imag - pointEnd.real * point.imag + return 0.5 * areaLoopDouble + +def getAreaLoopAbsolute(loop): + 'Get the absolute area of a complex polygon.' + return abs(getAreaLoop(loop)) + +def getAreaLoops(loops): + 'Get the area of a list of complex polygons.' + areaLoops = 0.0 + for loop in loops: + areaLoops += getAreaLoop(loop) + return areaLoops + +def getAreaVector3LoopAbsolute(loop): + 'Get the absolute area of a vector3 polygon.' + return getAreaLoopAbsolute(getComplexPath(loop)) + +def getAroundLoop(begin, end, loop): + 'Get an arc around a loop.' + aroundLoop = [] + if end <= begin: + end += len(loop) + for pointIndex in xrange(begin, end): + aroundLoop.append(loop[pointIndex % len(loop)]) + return aroundLoop + +def getAwayPath(path, radius): + 'Get a path with only the points that are far enough away from each other, except for the last point.' + if len(path) < 2: + return path + lastPoint = path[-1] + awayPath = getAwayPoints(path, radius) + if len(awayPath) == 0: + return [lastPoint] + if abs(lastPoint - awayPath[-1]) > 0.001 * radius: + awayPath.append(lastPoint) + return awayPath + +def getAwayPoints(points, radius): + 'Get a path with only the points that are far enough away from each other.' + awayPoints = [] + oneOverOverlapDistance = 1000.0 / radius + pixelDictionary = {} + for point in points: + x = int(point.real * oneOverOverlapDistance) + y = int(point.imag * oneOverOverlapDistance) + if not getSquareIsOccupied(pixelDictionary, x, y): + awayPoints.append(point) + pixelDictionary[(x, y)] = None + return awayPoints + +def getBooleanFromDictionary(defaultBoolean, dictionary, key): + 'Get boolean from the dictionary and key.' + if key not in dictionary: + return defaultBoolean + return getBooleanFromValue(dictionary[key]) + +def getBooleanFromValue(value): + 'Get boolean from the word.' + firstCharacter = str(value).lower().lstrip()[: 1] + return firstCharacter == 't' or firstCharacter == '1' + +def getBottomByPath(path): + 'Get the bottom of the path.' + bottom = 987654321987654321.0 + for point in path: + bottom = min(bottom, point.z) + return bottom + +def getBottomByPaths(paths): + 'Get the bottom of the paths.' + bottom = 987654321987654321.0 + for path in paths: + for point in path: + bottom = min(bottom, point.z) + return bottom + +def getClippedAtEndLoopPath( clip, loopPath ): + 'Get a clipped loop path.' + if clip <= 0.0: + return loopPath + loopPathLength = getPathLength(loopPath) + clip = min( clip, 0.3 * loopPathLength ) + lastLength = 0.0 + pointIndex = 0 + totalLength = 0.0 + clippedLength = loopPathLength - clip + while totalLength < clippedLength and pointIndex < len(loopPath) - 1: + firstPoint = loopPath[pointIndex] + secondPoint = loopPath[pointIndex + 1] + pointIndex += 1 + lastLength = totalLength + totalLength += abs(firstPoint - secondPoint) + remainingLength = clippedLength - lastLength + clippedLoopPath = loopPath[ : pointIndex ] + ultimateClippedPoint = loopPath[pointIndex] + penultimateClippedPoint = clippedLoopPath[-1] + segment = ultimateClippedPoint - penultimateClippedPoint + segmentLength = abs(segment) + if segmentLength <= 0.0: + return clippedLoopPath + newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength + return clippedLoopPath + [newUltimatePoint] + +def getClippedLoopPath(clip, loopPath): + 'Get a clipped loop path.' + if clip <= 0.0: + return loopPath + loopPathLength = getPathLength(loopPath) + clip = min(clip, 0.3 * loopPathLength) + lastLength = 0.0 + pointIndex = 0 + totalLength = 0.0 + while totalLength < clip and pointIndex < len(loopPath) - 1: + firstPoint = loopPath[pointIndex] + secondPoint = loopPath[pointIndex + 1] + pointIndex += 1 + lastLength = totalLength + totalLength += abs(firstPoint - secondPoint) + remainingLength = clip - lastLength + clippedLoopPath = loopPath[pointIndex :] + ultimateClippedPoint = clippedLoopPath[0] + penultimateClippedPoint = loopPath[pointIndex - 1] + segment = ultimateClippedPoint - penultimateClippedPoint + segmentLength = abs(segment) + loopPath = clippedLoopPath + if segmentLength > 0.0: + newUltimatePoint = penultimateClippedPoint + segment * remainingLength / segmentLength + loopPath = [newUltimatePoint] + loopPath + return getClippedAtEndLoopPath(clip, loopPath) + +def getClippedSimplifiedLoopPath(clip, loopPath, radius): + 'Get a clipped and simplified loop path.' + return getSimplifiedPath(getClippedLoopPath(clip, loopPath), radius) + +def getClosestDistanceIndexToLine(point, loop): + 'Get the distance squared to the closest segment of the loop and index of that segment.' + smallestDistance = 987654321987654321.0 + closestDistanceIndex = None + for pointIndex in xrange(len(loop)): + segmentBegin = loop[pointIndex] + segmentEnd = loop[(pointIndex + 1) % len(loop)] + distance = getDistanceToPlaneSegment(segmentBegin, segmentEnd, point) + if distance < smallestDistance: + smallestDistance = distance + closestDistanceIndex = DistanceIndex(distance, pointIndex) + return closestDistanceIndex + +def getClosestPointOnSegment(segmentBegin, segmentEnd, point): + 'Get the closest point on the segment.' + segmentDifference = segmentEnd - segmentBegin + if abs(segmentDifference) <= 0.0: + return segmentBegin + pointMinusSegmentBegin = point - segmentBegin + beginPlaneDot = getDotProduct(pointMinusSegmentBegin, segmentDifference) + differencePlaneDot = getDotProduct(segmentDifference, segmentDifference) + intercept = beginPlaneDot / differencePlaneDot + intercept = max(intercept, 0.0) + intercept = min(intercept, 1.0) + return segmentBegin + segmentDifference * intercept + +def getComplexByCommaString( valueCommaString ): + 'Get the commaString as a complex.' + try: + splitLine = valueCommaString.replace(',', ' ').split() + return complex( float( splitLine[0] ), float(splitLine[1]) ) + except: + pass + return None + +def getComplexByWords(words, wordIndex=0): + 'Get the complex by the first two words.' + try: + return complex(float(words[wordIndex]), float(words[wordIndex + 1])) + except: + pass + return None + +def getComplexDefaultByDictionary( defaultComplex, dictionary, key ): + 'Get the value as a complex.' + if key in dictionary: + return complex( dictionary[key].strip().replace('(', '').replace(')', '') ) + return defaultComplex + +def getComplexDefaultByDictionaryKeys( defaultComplex, dictionary, keyX, keyY ): + 'Get the value as a complex.' + x = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyX ) + y = getFloatDefaultByDictionary( defaultComplex.real, dictionary, keyY ) + return complex(x, y) + +def getComplexPath(vector3Path): + 'Get the complex path from the vector3 path.' + complexPath = [] + for point in vector3Path: + complexPath.append(point.dropAxis()) + return complexPath + +def getComplexPathByMultiplier(multiplier, path): + 'Get the multiplied complex path.' + complexPath = [] + for point in path: + complexPath.append(multiplier * point) + return complexPath + +def getComplexPaths(vector3Paths): + 'Get the complex paths from the vector3 paths.' + complexPaths = [] + for vector3Path in vector3Paths: + complexPaths.append(getComplexPath(vector3Path)) + return complexPaths + +def getComplexPolygon(center, radius, sides, startAngle=0.0): + 'Get the complex polygon.' + complexPolygon = [] + sideAngle = 2.0 * math.pi / float(sides) + for side in xrange(abs(sides)): + unitPolar = getWiddershinsUnitPolar(startAngle) + complexPolygon.append(unitPolar * radius + center) + startAngle += sideAngle + return complexPolygon + +def getComplexPolygonByComplexRadius(radius, sides, startAngle=0.0): + 'Get the complex polygon.' + complexPolygon = [] + sideAngle = 2.0 * math.pi / float(sides) + for side in xrange(abs(sides)): + unitPolar = getWiddershinsUnitPolar(startAngle) + complexPolygon.append(complex(unitPolar.real * radius.real, unitPolar.imag * radius.imag)) + startAngle += sideAngle + return complexPolygon + +def getComplexPolygonByStartEnd(endAngle, radius, sides, startAngle=0.0): + 'Get the complex polygon by start and end angle.' + angleExtent = endAngle - startAngle + sideAngle = 2.0 * math.pi / float(sides) + sides = int(math.ceil(abs(angleExtent / sideAngle))) + sideAngle = angleExtent / float(sides) + complexPolygon = [] + for side in xrange(abs(sides) + 1): + unitPolar = getWiddershinsUnitPolar(startAngle) + complexPolygon.append(unitPolar * radius) + startAngle += sideAngle + return getLoopWithoutCloseEnds(0.000001 * radius, complexPolygon) + +def getConcatenatedList(originalLists): + 'Get the lists as one concatenated list.' + concatenatedList = [] + for originalList in originalLists: + concatenatedList += originalList + return concatenatedList + +def getConnectedPaths( paths, pixelDictionary, width ): + 'Get connected paths from paths.' + if len(paths) < 2: + return paths + connectedPaths = [] + segments = [] + for pathIndex in xrange( len(paths) ): + path = paths[ pathIndex ] + segments.append( getSegmentFromPath( path, pathIndex ) ) + for pathIndex in xrange( 0, len(paths) - 1 ): + concatenateRemovePath( connectedPaths, pathIndex, paths, pixelDictionary, segments, width ) + connectedPaths.append( paths[-1] ) + return connectedPaths + +def getCrossProduct(firstComplex, secondComplex): + 'Get z component cross product of a pair of complexes.' + return firstComplex.real * secondComplex.imag - firstComplex.imag * secondComplex.real + +def getDecimalPlacesCarried(extraDecimalPlaces, value): + 'Get decimal places carried by the decimal places of the value plus the extraDecimalPlaces.' + return max(0, 1 + int(math.ceil(extraDecimalPlaces - math.log10(value)))) + +def getDiagonalFlippedLoop(loop): + 'Get loop flipped over the dialogonal, in other words with the x and y swapped.' + diagonalFlippedLoop = [] + for point in loop: + diagonalFlippedLoop.append( complex( point.imag, point.real ) ) + return diagonalFlippedLoop + +def getDiagonalFlippedLoops(loops): + 'Get loops flipped over the dialogonal, in other words with the x and y swapped.' + diagonalFlippedLoops = [] + for loop in loops: + diagonalFlippedLoops.append( getDiagonalFlippedLoop(loop) ) + return diagonalFlippedLoops + +def getDictionaryString(dictionary): + 'Get the dictionary string.' + output = cStringIO.StringIO() + keys = dictionary.keys() + keys.sort() + for key in keys: + addValueToOutput(0, key, output, dictionary[key]) + return output.getvalue() + +def getDistanceToLine(begin, end, point): + 'Get the distance from a vector3 point to an infinite line.' + pointMinusBegin = point - begin + if begin == end: + return abs(pointMinusBegin) + endMinusBegin = end - begin + return abs(endMinusBegin.cross(pointMinusBegin)) / abs(endMinusBegin) + +def getDistanceToLineByPath(begin, end, path): + 'Get the maximum distance from a path to an infinite line.' + distanceToLine = -987654321.0 + for point in path: + distanceToLine = max(getDistanceToLine(begin, end, point), distanceToLine) + return distanceToLine + +def getDistanceToLineByPaths(begin, end, paths): + 'Get the maximum distance from paths to an infinite line.' + distanceToLine = -987654321.0 + for path in paths: + distanceToLine = max(getDistanceToLineByPath(begin, end, path), distanceToLine) + return distanceToLine + +def getDistanceToPlaneSegment( segmentBegin, segmentEnd, point ): + 'Get the distance squared from a point to the x & y components of a segment.' + segmentDifference = segmentEnd - segmentBegin + pointMinusSegmentBegin = point - segmentBegin + beginPlaneDot = getDotProduct( pointMinusSegmentBegin, segmentDifference ) + if beginPlaneDot <= 0.0: + return abs( point - segmentBegin ) * abs( point - segmentBegin ) + differencePlaneDot = getDotProduct( segmentDifference, segmentDifference ) + if differencePlaneDot <= beginPlaneDot: + return abs( point - segmentEnd ) * abs( point - segmentEnd ) + intercept = beginPlaneDot / differencePlaneDot + interceptPerpendicular = segmentBegin + segmentDifference * intercept + return abs( point - interceptPerpendicular ) * abs( point - interceptPerpendicular ) + +def getDotProduct(firstComplex, secondComplex): + 'Get the dot product of a pair of complexes.' + return firstComplex.real * secondComplex.real + firstComplex.imag * secondComplex.imag + +def getDotProductPlusOne( firstComplex, secondComplex ): + 'Get the dot product plus one of the x and y components of a pair of Vector3s.' + return 1.0 + getDotProduct( firstComplex, secondComplex ) + +def getDurationString( seconds ): + 'Get the duration string.' + secondsRounded = int( round( seconds ) ) + durationString = getPluralString( secondsRounded % 60, 'second') + if seconds < 60: + return durationString + durationString = '%s %s' % ( getPluralString( ( secondsRounded / 60 ) % 60, 'minute'), durationString ) + if seconds < 3600: + return durationString + return '%s %s' % ( getPluralString( secondsRounded / 3600, 'hour'), durationString ) + +def getEndpointFromPath( path, pathIndex ): + 'Get endpoint segment from a path.' + begin = path[-1] + end = path[-2] + endpointBegin = Endpoint() + endpointEnd = Endpoint().getFromOtherPoint( endpointBegin, end ) + endpointBegin.getFromOtherPoint( endpointEnd, begin ) + endpointBegin.path = path + endpointBegin.pathIndex = pathIndex + return endpointBegin + +def getEndpointsFromSegments( segments ): + 'Get endpoints from segments.' + endpoints = [] + for segment in segments: + for endpoint in segment: + endpoints.append( endpoint ) + return endpoints + +def getEndpointsFromSegmentTable( segmentTable ): + 'Get the endpoints from the segment table.' + endpoints = [] + segmentTableKeys = segmentTable.keys() + segmentTableKeys.sort() + for segmentTableKey in segmentTableKeys: + for segment in segmentTable[ segmentTableKey ]: + for endpoint in segment: + endpoints.append( endpoint ) + return endpoints + +def getEnumeratorKeys(enumerator, keys): + 'Get enumerator keys.' + if len(keys) == 1: + return keys[0] + return getEnumeratorKeysExceptForOneArgument(enumerator, keys) + +def getEnumeratorKeysAlwaysList(enumerator, keys): + 'Get enumerator keys.' + if keys.__class__ != list: + return [keys] + if len(keys) == 1: + return keys + return getEnumeratorKeysExceptForOneArgument(enumerator, keys) + +def getEnumeratorKeysExceptForOneArgument(enumerator, keys): + 'Get enumerator keys, except when there is one argument.' + if len(keys) == 0: + return range(0, len(enumerator)) + beginIndex = keys[0] + endIndex = keys[1] + if len(keys) == 2: + if beginIndex == None: + beginIndex = 0 + if endIndex == None: + endIndex = len(enumerator) + return range(beginIndex, endIndex) + step = keys[2] + beginIndexDefault = 0 + endIndexDefault = len(enumerator) + if step < 0: + beginIndexDefault = endIndexDefault - 1 + endIndexDefault = -1 + if beginIndex == None: + beginIndex = beginIndexDefault + if endIndex == None: + endIndex = endIndexDefault + return range(beginIndex, endIndex, step) + +def getFillOfSurroundings(nestedRings, penultimateFillLoops): + 'Get extra fill loops of nested rings.' + fillOfSurroundings = [] + for nestedRing in nestedRings: + fillOfSurroundings += nestedRing.getFillLoops(penultimateFillLoops) + return fillOfSurroundings + +def getFlattenedNestedRings(nestedRings): + 'Get flattened nested rings.' + flattenedNestedRings = [] + for nestedRing in nestedRings: + nestedRing.addFlattenedNestedRings(flattenedNestedRings) + return flattenedNestedRings + +def getFloatDefaultByDictionary( defaultFloat, dictionary, key ): + 'Get the value as a float.' + evaluatedFloat = None + if key in dictionary: + evaluatedFloat = getFloatFromValue(dictionary[key]) + if evaluatedFloat == None: + return defaultFloat + return evaluatedFloat + +def getFloatFromValue(value): + 'Get the value as a float.' + try: + return float(value) + except: + pass + return None + +def getFourSignificantFigures(number): + 'Get number rounded to four significant figures as a string.' + if number == None: + return None + absoluteNumber = abs(number) + if absoluteNumber >= 100.0: + return getRoundedToPlacesString( 2, number ) + if absoluteNumber < 0.000000001: + return getRoundedToPlacesString( 13, number ) + return getRoundedToPlacesString( 3 - math.floor( math.log10( absoluteNumber ) ), number ) + +def getHalfSimplifiedLoop( loop, radius, remainder ): + 'Get the loop with half of the points inside the channel removed.' + if len(loop) < 2: + return loop + channelRadius = radius * .01 + simplified = [] + addIndex = 0 + if remainder == 1: + addIndex = len(loop) - 1 + for pointIndex in xrange(len(loop)): + point = loop[pointIndex] + if pointIndex % 2 == remainder or pointIndex == addIndex: + simplified.append(point) + elif not isWithinChannel( channelRadius, pointIndex, loop ): + simplified.append(point) + return simplified + +def getHalfSimplifiedPath(path, radius, remainder): + 'Get the path with half of the points inside the channel removed.' + if len(path) < 2: + return path + channelRadius = radius * .01 + simplified = [path[0]] + for pointIndex in xrange(1, len(path) - 1): + point = path[pointIndex] + if pointIndex % 2 == remainder: + simplified.append(point) + elif not isWithinChannel(channelRadius, pointIndex, path): + simplified.append(point) + simplified.append(path[-1]) + return simplified + +def getHorizontallyBoundedPath(horizontalBegin, horizontalEnd, path): + 'Get horizontally bounded path.' + horizontallyBoundedPath = [] + for pointIndex, point in enumerate(path): + begin = None + previousIndex = pointIndex - 1 + if previousIndex >= 0: + begin = path[previousIndex] + end = None + nextIndex = pointIndex + 1 + if nextIndex < len(path): + end = path[nextIndex] + addHorizontallyBoundedPoint(begin, point, end, horizontalBegin, horizontalEnd, horizontallyBoundedPath) + return horizontallyBoundedPath + +def getIncrementFromRank( rank ): + 'Get the increment from the rank which is 0 at 1 and increases by three every power of ten.' + rankZone = int( math.floor( rank / 3 ) ) + rankModulo = rank % 3 + powerOfTen = pow( 10, rankZone ) + moduloMultipliers = ( 1, 2, 5 ) + return float( powerOfTen * moduloMultipliers[ rankModulo ] ) + +def getInsidesAddToOutsides( loops, outsides ): + 'Add loops to either the insides or outsides.' + insides = [] + for loopIndex in xrange( len(loops) ): + loop = loops[loopIndex] + if isInsideOtherLoops( loopIndex, loops ): + insides.append(loop) + else: + outsides.append(loop) + return insides + +def getIntermediateLocation( alongWay, begin, end ): + 'Get the intermediate location between begin and end.' + return begin * ( 1.0 - alongWay ) + end * alongWay + +def getIntersectionOfXIntersectionIndexes( totalSolidSurfaceThickness, xIntersectionIndexList ): + 'Get x intersections from surrounding layers.' + xIntersectionList = [] + solidTable = {} + solid = False + xIntersectionIndexList.sort() + for xIntersectionIndex in xIntersectionIndexList: + toggleHashtable(solidTable, xIntersectionIndex.index, '') + oldSolid = solid + solid = len(solidTable) >= totalSolidSurfaceThickness + if oldSolid != solid: + xIntersectionList.append(xIntersectionIndex.x) + return xIntersectionList + +def getIntersectionOfXIntersectionsTables(xIntersectionsTables): + 'Get the intersection of the XIntersections tables.' + if len(xIntersectionsTables) == 0: + return {} + intersectionOfXIntersectionsTables = {} + firstIntersectionTable = xIntersectionsTables[0] + for firstIntersectionTableKey in firstIntersectionTable.keys(): + xIntersectionIndexList = [] + for xIntersectionsTableIndex in xrange(len(xIntersectionsTables)): + xIntersectionsTable = xIntersectionsTables[xIntersectionsTableIndex] + if firstIntersectionTableKey in xIntersectionsTable: + addXIntersectionIndexesFromXIntersections(xIntersectionsTableIndex, xIntersectionIndexList, xIntersectionsTable[firstIntersectionTableKey]) + xIntersections = getIntersectionOfXIntersectionIndexes(len(xIntersectionsTables), xIntersectionIndexList) + if len(xIntersections) > 0: + intersectionOfXIntersectionsTables[firstIntersectionTableKey] = xIntersections + return intersectionOfXIntersectionsTables + +def getIntFromValue(value): + 'Get the value as an int.' + try: + return int(value) + except: + pass + return None + +def getIsInFilledRegion(loops, point): + 'Determine if the point is in the filled region of the loops.' + return getNumberOfIntersectionsToLeftOfLoops(loops, point) % 2 == 1 + +def getIsInFilledRegionByPaths(loops, paths): + 'Determine if the point of any path is in the filled region of the loops.' + for path in paths: + if len(path) > 0: + if getIsInFilledRegion(loops, path[0]): + return True + return False + +def getIsRadianClose(firstRadian, secondRadian): + 'Determine if the firstRadian is close to the secondRadian.' + return abs(math.pi - abs(math.pi - ((firstRadian - secondRadian) % (math.pi + math.pi) ))) < 0.000001 + +def getIsWiddershinsByVector3( polygon ): + 'Determine if the polygon goes round in the widdershins direction.' + return isWiddershins( getComplexPath( polygon ) ) + +def getJoinOfXIntersectionIndexes( xIntersectionIndexList ): + 'Get joined x intersections from surrounding layers.' + xIntersections = [] + solidTable = {} + solid = False + xIntersectionIndexList.sort() + for xIntersectionIndex in xIntersectionIndexList: + toggleHashtable(solidTable, xIntersectionIndex.index, '') + oldSolid = solid + solid = len(solidTable) > 0 + if oldSolid != solid: + xIntersections.append(xIntersectionIndex.x) + return xIntersections + +def getLargestLoop(loops): + 'Get largest loop from loops.' + largestArea = -987654321.0 + largestLoop = [] + for loop in loops: + loopArea = abs(getAreaLoopAbsolute(loop)) + if loopArea > largestArea: + largestArea = loopArea + largestLoop = loop + return largestLoop + +def getLeftPoint(points): + 'Get the leftmost complex point in the points.' + leftmost = 987654321.0 + leftPointComplex = None + for pointComplex in points: + if pointComplex.real < leftmost: + leftmost = pointComplex.real + leftPointComplex = pointComplex + return leftPointComplex + +def getLeftPointIndex(points): + 'Get the index of the leftmost complex point in the points.' + if len(points) < 1: + return None + leftPointIndex = 0 + for pointIndex in xrange( len(points) ): + if points[pointIndex].real < points[ leftPointIndex ].real: + leftPointIndex = pointIndex + return leftPointIndex + +def getListTableElements( listDictionary ): + 'Get all the element in a list table.' + listDictionaryElements = [] + for listDictionaryValue in listDictionary.values(): + listDictionaryElements += listDictionaryValue + return listDictionaryElements + +def getLoopCentroid(polygonComplex): + 'Get the area of a complex polygon using http://en.wikipedia.org/wiki/Centroid.' + polygonDoubleArea = 0.0 + polygonTorque = 0.0 + for pointIndex in xrange( len(polygonComplex) ): + pointBegin = polygonComplex[pointIndex] + pointEnd = polygonComplex[ (pointIndex + 1) % len(polygonComplex) ] + doubleArea = pointBegin.real * pointEnd.imag - pointEnd.real * pointBegin.imag + doubleCenter = complex( pointBegin.real + pointEnd.real, pointBegin.imag + pointEnd.imag ) + polygonDoubleArea += doubleArea + polygonTorque += doubleArea * doubleCenter + torqueMultiplier = 0.333333333333333333333333 / polygonDoubleArea + return polygonTorque * torqueMultiplier + +def getLoopConvex(points): + 'Get convex hull of points using gift wrap algorithm.' + loopConvex = [] + pointSet = set() + for point in points: + if point not in pointSet: + pointSet.add(point) + loopConvex.append(point) + if len(loopConvex) < 4: + return loopConvex + leftPoint = getLeftPoint(loopConvex) + lastPoint = leftPoint + pointSet.remove(leftPoint) + loopConvex = [leftPoint] + lastSegment = complex(0.0, 1.0) + while True: + greatestDotProduct = -9.9 + greatestPoint = None + greatestSegment = None + if len(loopConvex) > 2: + nextSegment = getNormalized(leftPoint - lastPoint) + if abs(nextSegment) > 0.0: + greatestDotProduct = getDotProduct(nextSegment, lastSegment) + for point in pointSet: + nextSegment = getNormalized(point - lastPoint) + if abs(nextSegment) > 0.0: + dotProduct = getDotProduct(nextSegment, lastSegment) + if dotProduct > greatestDotProduct: + greatestDotProduct = dotProduct + greatestPoint = point + greatestSegment = nextSegment + if greatestPoint == None: + return loopConvex + lastPoint = greatestPoint + loopConvex.append(greatestPoint) + pointSet.remove(greatestPoint) + lastSegment = greatestSegment + return loopConvex + +def getLoopConvexCentroid(polygonComplex): + 'Get centroid of the convex hull of a complex polygon.' + return getLoopCentroid( getLoopConvex(polygonComplex) ) + +def getLoopInsideContainingLoop( containingLoop, loops ): + 'Get a loop that is inside the containing loop.' + for loop in loops: + if loop != containingLoop: + if isPathInsideLoop( containingLoop, loop ): + return loop + return None + +def getLoopLength( polygon ): + 'Get the length of a polygon perimeter.' + polygonLength = 0.0 + for pointIndex in xrange( len( polygon ) ): + point = polygon[pointIndex] + secondPoint = polygon[ (pointIndex + 1) % len( polygon ) ] + polygonLength += abs( point - secondPoint ) + return polygonLength + +def getLoopStartingClosest(extrusionHalfWidth, location, loop): + 'Add to threads from the last location from loop.' + closestIndex = getClosestDistanceIndexToLine(location, loop).index + loop = getAroundLoop(closestIndex, closestIndex, loop) + closestPoint = getClosestPointOnSegment(loop[0], loop[1], location) + if abs(closestPoint - loop[0]) > extrusionHalfWidth and abs(closestPoint - loop[1]) > extrusionHalfWidth: + loop = [closestPoint] + loop[1 :] + [loop[0]] + elif abs(closestPoint - loop[0]) > abs(closestPoint - loop[1]): + loop = loop[1 :] + [loop[0]] + return loop + +def getLoopWithoutCloseEnds(close, loop): + 'Get loop without close ends.' + if len(loop) < 2: + return loop + if abs(loop[0] - loop[-1]) > close: + return loop + return loop[: -1] + +def getLoopWithoutCloseSequentialPoints(close, loop): + 'Get loop without close sequential points.' + if len(loop) < 2: + return loop + lastPoint = loop[-1] + loopWithoutCloseSequentialPoints = [] + for point in loop: + if abs(point - lastPoint) > close: + loopWithoutCloseSequentialPoints.append(point) + lastPoint = point + return loopWithoutCloseSequentialPoints + +def getMaximum(firstComplex, secondComplex): + 'Get a complex with each component the maximum of the respective components of a pair of complexes.' + return complex(max(firstComplex.real, secondComplex.real), max(firstComplex.imag, secondComplex.imag)) + +def getMaximumByComplexPath(path): + 'Get a complex with each component the maximum of the respective components of a complex path.' + maximum = complex(-987654321987654321.0, -987654321987654321.0) + for point in path: + maximum = getMaximum(maximum, point) + return maximum + +def getMaximumByComplexPaths(paths): + 'Get a complex with each component the maximum of the respective components of complex paths.' + maximum = complex(-987654321987654321.0, -987654321987654321.0) + for path in paths: + for point in path: + maximum = getMaximum(maximum, point) + return maximum + +def getMaximumByVector3Path(path): + 'Get a vector3 with each component the maximum of the respective components of a vector3 path.' + maximum = Vector3(-987654321987654321.0, -987654321987654321.0, -987654321987654321.0) + for point in path: + maximum.maximize(point) + return maximum + +def getMaximumByVector3Paths(paths): + 'Get a complex with each component the maximum of the respective components of a complex path.' + maximum = Vector3(-987654321987654321.0, -987654231987654321.0, -987654321987654321.0) + for path in paths: + for point in path: + maximum.maximize(point) + return maximum + +def getMaximumSpan(loop): + 'Get the maximum span of the loop.' + extent = getMaximumByComplexPath(loop) - getMinimumByComplexPath(loop) + return max(extent.real, extent.imag) + +def getMinimum(firstComplex, secondComplex): + 'Get a complex with each component the minimum of the respective components of a pair of complexes.' + return complex(min(firstComplex.real, secondComplex.real), min(firstComplex.imag, secondComplex.imag)) + +def getMinimumByComplexPath(path): + 'Get a complex with each component the minimum of the respective components of a complex path.' + minimum = complex(987654321987654321.0, 987654321987654321.0) + for point in path: + minimum = getMinimum(minimum, point) + return minimum + +def getMinimumByComplexPaths(paths): + 'Get a complex with each component the minimum of the respective components of complex paths.' + minimum = complex(987654321987654321.0, 987654321987654321.0) + for path in paths: + for point in path: + minimum = getMinimum(minimum, point) + return minimum + +def getMinimumByVector3Path(path): + 'Get a vector3 with each component the minimum of the respective components of a vector3 path.' + minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0) + for point in path: + minimum.minimize(point) + return minimum + +def getMinimumByVector3Paths(paths): + 'Get a complex with each component the minimum of the respective components of a complex path.' + minimum = Vector3(987654321987654321.0, 987654321987654321.0, 987654321987654321.0) + for path in paths: + for point in path: + minimum.minimize(point) + return minimum + +def getMirrorPath(path): + "Get mirror path." + close = 0.001 * getPathLength(path) + for pointIndex in xrange(len(path) - 1, -1, -1): + point = path[pointIndex] + flipPoint = complex(-point.real, point.imag) + if abs(flipPoint - path[-1]) > close: + path.append(flipPoint) + return path + +def getNormal(begin, center, end): + 'Get normal.' + centerMinusBegin = (center - begin).getNormalized() + endMinusCenter = (end - center).getNormalized() + return centerMinusBegin.cross(endMinusCenter) + +def getNormalByPath(path): + 'Get normal by path.' + totalNormal = Vector3() + for pointIndex, point in enumerate(path): + center = path[(pointIndex + 1) % len(path)] + end = path[(pointIndex + 2) % len(path)] + totalNormal += getNormalWeighted(point, center, end) + return totalNormal.getNormalized() + +def getNormalized(complexNumber): + 'Get the normalized complex.' + complexNumberLength = abs(complexNumber) + if complexNumberLength > 0.0: + return complexNumber / complexNumberLength + return complexNumber + +def getNormalWeighted(begin, center, end): + 'Get weighted normal.' + return (center - begin).cross(end - center) + +def getNumberOfIntersectionsToLeft(loop, point): + 'Get the number of intersections through the loop for the line going left.' + numberOfIntersectionsToLeft = 0 + for pointIndex in xrange(len(loop)): + firstPointComplex = loop[pointIndex] + secondPointComplex = loop[(pointIndex + 1) % len(loop)] + xIntersection = getXIntersectionIfExists(firstPointComplex, secondPointComplex, point.imag) + if xIntersection != None: + if xIntersection < point.real: + numberOfIntersectionsToLeft += 1 + return numberOfIntersectionsToLeft + +def getNumberOfIntersectionsToLeftOfLoops(loops, point): + 'Get the number of intersections through the loop for the line starting from the left point and going left.' + totalNumberOfIntersectionsToLeft = 0 + for loop in loops: + totalNumberOfIntersectionsToLeft += getNumberOfIntersectionsToLeft(loop, point) + return totalNumberOfIntersectionsToLeft + +def getOrderedNestedRings(nestedRings): + 'Get ordered nestedRings from nestedRings.' + insides = [] + orderedNestedRings = [] + for loopIndex in xrange(len(nestedRings)): + nestedRing = nestedRings[loopIndex] + otherLoops = [] + for beforeIndex in xrange(loopIndex): + otherLoops.append(nestedRings[beforeIndex].boundary) + for afterIndex in xrange(loopIndex + 1, len(nestedRings)): + otherLoops.append(nestedRings[afterIndex].boundary) + if isPathEntirelyInsideLoops(otherLoops, nestedRing.boundary): + insides.append(nestedRing) + else: + orderedNestedRings.append(nestedRing) + for outside in orderedNestedRings: + outside.getFromInsideSurroundings(insides) + return orderedNestedRings + +def getPathCopy(path): + 'Get path copy.' + pathCopy = [] + for point in path: + pathCopy.append(point.copy()) + return pathCopy + +def getPathLength(path): + 'Get the length of a path ( an open polyline ).' + pathLength = 0.0 + for pointIndex in xrange( len(path) - 1 ): + firstPoint = path[pointIndex] + secondPoint = path[pointIndex + 1] + pathLength += abs(firstPoint - secondPoint) + return pathLength + +def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, width): + 'Get paths from endpoints.' + if len(endpoints) < 2: + return [] + endpoints = endpoints[:] # so that the first two endpoints aren't removed when used again + for beginningEndpoint in endpoints[: : 2]: + beginningPoint = beginningEndpoint.point + addSegmentToPixelTable(beginningPoint, beginningEndpoint.otherEndpoint.point, pixelDictionary, 0, 0, width) + endpointFirst = endpoints[0] + endpoints.remove(endpointFirst) + otherEndpoint = endpointFirst.otherEndpoint + endpoints.remove(otherEndpoint) + nextEndpoint = None + path = [] + paths = [path] + if len(endpoints) > 1: + nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, width) + if nextEndpoint != None: + if abs(nextEndpoint.point - endpointFirst.point) < abs(nextEndpoint.point - otherEndpoint.point): + endpointFirst = endpointFirst.otherEndpoint + otherEndpoint = endpointFirst.otherEndpoint + addPointToPath(path, pixelDictionary, endpointFirst.point, None, width) + addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width) + oneOverEndpointWidth = 1.0 / maximumConnectionLength + endpointTable = {} + for endpoint in endpoints: + addElementToPixelListFromPoint(endpoint, endpointTable, endpoint.point * oneOverEndpointWidth) + while len(endpointTable) > 0: + if len(endpointTable) == 1: + if len(endpointTable.values()[0]) < 2: + return [] + endpoints = getSquareValuesFromPoint(endpointTable, otherEndpoint.point * oneOverEndpointWidth) + nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, width) + if nextEndpoint == None: + path = [] + paths.append(path) + endpoints = getListTableElements(endpointTable) + nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints) +# this commented code should be faster than the getListTableElements code, but it isn't, someday a spiral algorithim could be tried +# endpoints = getSquareValuesFromPoint( endpointTable, otherEndpoint.point * oneOverEndpointWidth ) +# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints) +# if nextEndpoint == None: +# endpoints = [] +# for endpointTableValue in endpointTable.values(): +# endpoints.append( endpointTableValue[0] ) +# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints) +# endpoints = getSquareValuesFromPoint( endpointTable, nextEndpoint.point * oneOverEndpointWidth ) +# nextEndpoint = otherEndpoint.getClosestEndpoint(endpoints) + addPointToPath(path, pixelDictionary, nextEndpoint.point, len(paths) - 1, width) + removeElementFromPixelListFromPoint(nextEndpoint, endpointTable, nextEndpoint.point * oneOverEndpointWidth) + otherEndpoint = nextEndpoint.otherEndpoint + addPointToPath(path, pixelDictionary, otherEndpoint.point, len(paths) - 1, width) + removeElementFromPixelListFromPoint(otherEndpoint, endpointTable, otherEndpoint.point * oneOverEndpointWidth) + return paths + +def getPlaneDot( vec3First, vec3Second ): + 'Get the dot product of the x and y components of a pair of Vector3s.' + return vec3First.x * vec3Second.x + vec3First.y * vec3Second.y + +def getPluralString( number, suffix ): + 'Get the plural string.' + if number == 1: + return '1 %s' % suffix + return '%s %ss' % ( number, suffix ) + +def getPointPlusSegmentWithLength( length, point, segment ): + 'Get point plus a segment scaled to a given length.' + return segment * length / abs(segment) + point + +def getPointsByHorizontalDictionary(width, xIntersectionsDictionary): + 'Get points from the horizontalXIntersectionsDictionary.' + points = [] + xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys() + xIntersectionsDictionaryKeys.sort() + for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys: + for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]: + points.append(complex(xIntersection, xIntersectionsDictionaryKey * width)) + return points + +def getPointsByVerticalDictionary(width, xIntersectionsDictionary): + 'Get points from the verticalXIntersectionsDictionary.' + points = [] + xIntersectionsDictionaryKeys = xIntersectionsDictionary.keys() + xIntersectionsDictionaryKeys.sort() + for xIntersectionsDictionaryKey in xIntersectionsDictionaryKeys: + for xIntersection in xIntersectionsDictionary[xIntersectionsDictionaryKey]: + points.append(complex(xIntersectionsDictionaryKey * width, xIntersection)) + return points + +def getRadiusArealizedMultiplier(sides): + 'Get the radius multiplier for a polygon of equal area.' + return math.sqrt(globalTau / sides / math.sin(globalTau / sides)) + +def getRandomComplex(begin, end): + 'Get random complex.' + endMinusBegin = end - begin + return begin + complex(random.random() * endMinusBegin.real, random.random() * endMinusBegin.imag) + +def getRank(width): + 'Get the rank which is 0 at 1 and increases by three every power of ten.' + return int(math.floor(3.0 * math.log10(width))) + +def getRotatedComplexes(planeAngle, points): + 'Get points rotated by the plane angle' + rotatedComplexes = [] + for point in points: + rotatedComplexes.append(planeAngle * point) + return rotatedComplexes + +def getRotatedComplexLists(planeAngle, pointLists): + 'Get point lists rotated by the plane angle' + rotatedComplexLists = [] + for pointList in pointLists: + rotatedComplexLists.append(getRotatedComplexes(planeAngle, pointList)) + return rotatedComplexLists + +def getRotatedWiddershinsQuarterAroundZAxis(vector3): + 'Get Vector3 rotated a quarter widdershins turn around Z axis.' + return Vector3(-vector3.y, vector3.x, vector3.z) + +def getRoundedPoint(point): + 'Get point with each component rounded.' + return Vector3(round(point.x), round( point.y ), round(point.z)) + +def getRoundedToPlaces(decimalPlaces, number): + 'Get number rounded to a number of decimal places.' + decimalPlacesRounded = max(1, int(round(decimalPlaces))) + return round(number, decimalPlacesRounded) + +def getRoundedToPlacesString(decimalPlaces, number): + 'Get number rounded to a number of decimal places as a string, without exponential formatting.' + roundedToPlaces = getRoundedToPlaces(decimalPlaces, number) + roundedToPlacesString = str(roundedToPlaces) + if 'e' in roundedToPlacesString: + return ('%.15f' % roundedToPlaces).rstrip('0') + return roundedToPlacesString + +def getRoundedToThreePlaces(number): + 'Get number rounded to three places as a string.' + return str(round(number, 3)) + +def getRoundZAxisByPlaneAngle( planeAngle, vector3 ): + 'Get Vector3 rotated by a plane angle.' + return Vector3( vector3.x * planeAngle.real - vector3.y * planeAngle.imag, vector3.x * planeAngle.imag + vector3.y * planeAngle.real, vector3.z ) + +def getSegmentFromPath( path, pathIndex ): + 'Get endpoint segment from a path.' + if len(path) < 2: + return None + begin = path[-1] + end = path[-2] + forwardEndpoint = getEndpointFromPath( path, pathIndex ) + reversePath = path[:] + reversePath.reverse() + reverseEndpoint = getEndpointFromPath( reversePath, pathIndex ) + return ( forwardEndpoint, reverseEndpoint ) + +def getSegmentFromPoints( begin, end ): + 'Get endpoint segment from a pair of points.' + endpointFirst = Endpoint() + endpointSecond = Endpoint().getFromOtherPoint( endpointFirst, end ) + endpointFirst.getFromOtherPoint( endpointSecond, begin ) + return ( endpointFirst, endpointSecond ) + +def getSegmentsFromXIntersectionIndexes( xIntersectionIndexList, y ): + 'Get endpoint segments from the x intersection indexes.' + xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList ) + return getSegmentsFromXIntersections( xIntersections, y ) + +def getSegmentsFromXIntersections( xIntersections, y ): + 'Get endpoint segments from the x intersections.' + segments = [] + end = len( xIntersections ) + if len( xIntersections ) % 2 == 1: + end -= 1 + for xIntersectionIndex in xrange( 0, end, 2 ): + firstX = xIntersections[ xIntersectionIndex ] + secondX = xIntersections[ xIntersectionIndex + 1 ] + if firstX != secondX: + segments.append( getSegmentFromPoints( complex( firstX, y ), complex( secondX, y ) ) ) + return segments + +def getSimplifiedLoop( loop, radius ): + 'Get loop with points inside the channel removed.' + if len(loop) < 2: + return loop + simplificationMultiplication = 256 + simplificationRadius = radius / float( simplificationMultiplication ) + maximumIndex = len(loop) * simplificationMultiplication + pointIndex = 1 + while pointIndex < maximumIndex: + oldLoopLength = len(loop) + loop = getHalfSimplifiedLoop( loop, simplificationRadius, 0 ) + loop = getHalfSimplifiedLoop( loop, simplificationRadius, 1 ) + simplificationRadius += simplificationRadius + if oldLoopLength == len(loop): + if simplificationRadius > radius: + return getAwayPoints( loop, radius ) + else: + simplificationRadius *= 1.5 + simplificationRadius = min( simplificationRadius, radius ) + pointIndex += pointIndex + return getAwayPoints( loop, radius ) + +def getSimplifiedLoops( loops, radius ): + 'Get the simplified loops.' + simplifiedLoops = [] + for loop in loops: + simplifiedLoops.append( getSimplifiedLoop( loop, radius ) ) + return simplifiedLoops + +def getSimplifiedPath(path, radius): + 'Get path with points inside the channel removed.' + if len(path) < 2: + return path + simplificationMultiplication = 256 + simplificationRadius = radius / float(simplificationMultiplication) + maximumIndex = len(path) * simplificationMultiplication + pointIndex = 1 + while pointIndex < maximumIndex: + oldPathLength = len(path) + path = getHalfSimplifiedPath(path, simplificationRadius, 0) + path = getHalfSimplifiedPath(path, simplificationRadius, 1) + simplificationRadius += simplificationRadius + if oldPathLength == len(path): + if simplificationRadius > radius: + return getAwayPath(path, radius) + else: + simplificationRadius *= 1.5 + simplificationRadius = min(simplificationRadius, radius) + pointIndex += pointIndex + return getAwayPath(path, radius) + +def getSquareIsOccupied( pixelDictionary, x, y ): + 'Determine if a square around the x and y pixel coordinates is occupied.' + squareValues = [] + for xStep in xrange(x - 1, x + 2): + for yStep in xrange(y - 1, y + 2): + if (xStep, yStep) in pixelDictionary: + return True + return False + +def getSquareLoopWiddershins(beginComplex, endComplex): + 'Get a square loop from the beginning to the end and back.' + loop = [beginComplex, complex(endComplex.real, beginComplex.imag), endComplex] + loop.append(complex(beginComplex.real, endComplex.imag)) + return loop + +def getSquareValues( pixelDictionary, x, y ): + 'Get a list of the values in a square around the x and y pixel coordinates.' + squareValues = [] + for xStep in xrange(x - 1, x + 2): + for yStep in xrange(y - 1, y + 2): + stepKey = (xStep, yStep) + if stepKey in pixelDictionary: + squareValues += pixelDictionary[ stepKey ] + return squareValues + +def getSquareValuesFromPoint( pixelDictionary, point ): + 'Get a list of the values in a square around the point.' + return getSquareValues(pixelDictionary, int(round(point.real)), int(round(point.imag))) + +def getStepKeyFromPoint(point): + 'Get step key for the point.' + return (int(round(point.real)), int(round(point.imag))) + +def getThreeSignificantFigures(number): + 'Get number rounded to three significant figures as a string.' + absoluteNumber = abs(number) + if absoluteNumber >= 10.0: + return getRoundedToPlacesString( 1, number ) + if absoluteNumber < 0.000000001: + return getRoundedToPlacesString( 12, number ) + return getRoundedToPlacesString( 1 - math.floor( math.log10( absoluteNumber ) ), number ) + +def getTopPath(path): + 'Get the top of the path.' + top = -987654321987654321.0 + for point in path: + top = max(top, point.z) + return top + +def getTopPaths(paths): + 'Get the top of the paths.' + top = -987654321987654321.0 + for path in paths: + for point in path: + top = max(top, point.z) + return top + +def getTransferClosestNestedRing(extrusionHalfWidth, nestedRings, oldOrderedLocation, skein, threadSequence): + 'Get and transfer the closest remaining nested ring.' + if len(nestedRings) > 0: + oldOrderedLocation.z = nestedRings[0].z + closestDistance = 987654321987654321.0 + closestNestedRing = None + for remainingNestedRing in nestedRings: + distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingNestedRing.boundary).distance + if distance < closestDistance: + closestDistance = distance + closestNestedRing = remainingNestedRing + nestedRings.remove(closestNestedRing) + closestNestedRing.addToThreads(extrusionHalfWidth, oldOrderedLocation, skein, threadSequence) + return closestNestedRing + +def getTransferredNestedRings( insides, loop ): + 'Get transferred paths from inside nested rings.' + transferredSurroundings = [] + for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ): + insideSurrounding = insides[ insideIndex ] + if isPathInsideLoop( loop, insideSurrounding.boundary ): + transferredSurroundings.append( insideSurrounding ) + del insides[ insideIndex ] + return transferredSurroundings + +def getTransferredPaths( insides, loop ): + 'Get transferred paths from inside paths.' + transferredPaths = [] + for insideIndex in xrange( len( insides ) - 1, - 1, - 1 ): + inside = insides[ insideIndex ] + if isPathInsideLoop( loop, inside ): + transferredPaths.append( inside ) + del insides[ insideIndex ] + return transferredPaths + +def getTranslatedComplexPath(path, translateComplex): + 'Get the translated complex path.' + translatedComplexPath = [] + for point in path: + translatedComplexPath.append(point + translateComplex) + return translatedComplexPath + +def getVector3Path(complexPath, z=0.0): + 'Get the vector3 path from the complex path.' + vector3Path = [] + for complexPoint in complexPath: + vector3Path.append(Vector3(complexPoint.real, complexPoint.imag, z)) + return vector3Path + +def getVector3Paths(complexPaths, z=0.0): + 'Get the vector3 paths from the complex paths.' + vector3Paths = [] + for complexPath in complexPaths: + vector3Paths.append(getVector3Path(complexPath, z)) + return vector3Paths + +def getWiddershinsUnitPolar(angle): + 'Get polar complex from counterclockwise angle from 1, 0.' + return complex(math.cos(angle), math.sin(angle)) + +def getXIntersectionIfExists( beginComplex, endComplex, y ): + 'Get the x intersection if it exists.' + if ( y > beginComplex.imag ) == ( y > endComplex.imag ): + return None + endMinusBeginComplex = endComplex - beginComplex + return ( y - beginComplex.imag ) / endMinusBeginComplex.imag * endMinusBeginComplex.real + beginComplex.real + +def getXIntersectionsFromIntersections( xIntersectionIndexList ): + 'Get x intersections from the x intersection index list, in other words subtract non negative intersections from negatives.' + xIntersections = [] + fill = False + solid = False + solidTable = {} + xIntersectionIndexList.sort() + for solidX in xIntersectionIndexList: + if solidX.index >= 0: + toggleHashtable( solidTable, solidX.index, '' ) + else: + fill = not fill + oldSolid = solid + solid = ( len( solidTable ) == 0 and fill ) + if oldSolid != solid: + xIntersections.append( solidX.x ) + return xIntersections + +def getXYComplexFromVector3(vector3): + 'Get an xy complex from a vector3 if it exists, otherwise return None.' + if vector3 == None: + return None + return vector3.dropAxis() + +def getYIntersectionIfExists( beginComplex, endComplex, x ): + 'Get the y intersection if it exists.' + if ( x > beginComplex.real ) == ( x > endComplex.real ): + return None + endMinusBeginComplex = endComplex - beginComplex + return ( x - beginComplex.real ) / endMinusBeginComplex.real * endMinusBeginComplex.imag + beginComplex.imag + +def getZComponentCrossProduct( vec3First, vec3Second ): + 'Get z component cross product of a pair of Vector3s.' + return vec3First.x * vec3Second.y - vec3First.y * vec3Second.x + +def isInsideOtherLoops( loopIndex, loops ): + 'Determine if a loop in a list is inside another loop in that list.' + return isPathInsideLoops( loops[ : loopIndex ] + loops[loopIndex + 1 :], loops[loopIndex] ) + +def isLineIntersectingInsideXSegment( beginComplex, endComplex, segmentFirstX, segmentSecondX, y ): + 'Determine if the line is crossing inside the x segment.' + xIntersection = getXIntersectionIfExists( beginComplex, endComplex, y ) + if xIntersection == None: + return False + if xIntersection < min( segmentFirstX, segmentSecondX ): + return False + return xIntersection <= max( segmentFirstX, segmentSecondX ) + +def isLineIntersectingLoop( loop, pointBegin, pointEnd ): + 'Determine if the line is intersecting loops.' + normalizedSegment = pointEnd - pointBegin + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength > 0.0: + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = segmentYMirror * pointBegin + pointEndRotated = segmentYMirror * pointEnd + if isLoopIntersectingInsideXSegment( loop, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ): + return True + return False + +def isLineIntersectingLoops( loops, pointBegin, pointEnd ): + 'Determine if the line is intersecting loops.' + normalizedSegment = pointEnd - pointBegin + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength > 0.0: + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = segmentYMirror * pointBegin + pointEndRotated = segmentYMirror * pointEnd + if isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ): + return True + return False + +def isLoopIntersectingInsideXSegment( loop, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Determine if the loop is intersecting inside the x segment.' + rotatedLoop = getRotatedComplexes( segmentYMirror, loop ) + for pointIndex in xrange( len( rotatedLoop ) ): + pointFirst = rotatedLoop[pointIndex] + pointSecond = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ] + if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ): + return True + return False + +def isLoopIntersectingLoop( loop, otherLoop ): + 'Determine if the loop is intersecting the other loop.' + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + if isLineIntersectingLoop( otherLoop, pointBegin, pointEnd ): + return True + return False + +def isLoopIntersectingLoops( loop, otherLoops ): + 'Determine if the loop is intersecting other loops.' + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + if isLineIntersectingLoops( otherLoops, pointBegin, pointEnd ): + return True + return False + +def isLoopListIntersecting(loops): + 'Determine if a loop in the list is intersecting the other loops.' + for loopIndex in xrange(len(loops) - 1): + loop = loops[loopIndex] + if isLoopIntersectingLoops(loop, loops[loopIndex + 1 :]): + return True + return False + +def isLoopListIntersectingInsideXSegment( loopList, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Determine if the loop list is crossing inside the x segment.' + for alreadyFilledLoop in loopList: + if isLoopIntersectingInsideXSegment( alreadyFilledLoop, segmentFirstX, segmentSecondX, segmentYMirror, y ): + return True + return False + +def isPathEntirelyInsideLoop(loop, path): + 'Determine if a path is entirely inside another loop.' + for point in path: + if not isPointInsideLoop(loop, point): + return False + return True + +def isPathEntirelyInsideLoops(loops, path): + 'Determine if a path is entirely inside another loop in a list.' + for loop in loops: + if isPathEntirelyInsideLoop(loop, path): + return True + return False + +def isPathInsideLoop(loop, path): + 'Determine if a path is inside another loop.' + return isPointInsideLoop(loop, getLeftPoint(path)) + +def isPathInsideLoops(loops, path): + 'Determine if a path is inside another loop in a list.' + for loop in loops: + if isPathInsideLoop(loop, path): + return True + return False + +def isPixelTableIntersecting( bigTable, littleTable, maskTable = {} ): + 'Add path to the pixel table.' + littleTableKeys = littleTable.keys() + for littleTableKey in littleTableKeys: + if littleTableKey not in maskTable: + if littleTableKey in bigTable: + return True + return False + +def isPointInsideLoop(loop, point): + 'Determine if a point is inside another loop.' + return getNumberOfIntersectionsToLeft(loop, point) % 2 == 1 + +def isSegmentCompletelyInX( segment, xFirst, xSecond ): + 'Determine if the segment overlaps within x.' + segmentFirstX = segment[0].point.real + segmentSecondX = segment[1].point.real + if max( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ): + return False + return min( segmentFirstX, segmentSecondX ) >= min( xFirst, xSecond ) + +def isWiddershins(polygonComplex): + 'Determine if the complex polygon goes round in the widdershins direction.' + return getAreaLoop(polygonComplex) > 0.0 + +def isWithinChannel( channelRadius, pointIndex, loop ): + 'Determine if the the point is within the channel between two adjacent points.' + point = loop[pointIndex] + behindSegmentComplex = loop[(pointIndex + len(loop) - 1) % len(loop)] - point + behindSegmentComplexLength = abs( behindSegmentComplex ) + if behindSegmentComplexLength < channelRadius: + return True + aheadSegmentComplex = loop[(pointIndex + 1) % len(loop)] - point + aheadSegmentComplexLength = abs( aheadSegmentComplex ) + if aheadSegmentComplexLength < channelRadius: + return True + behindSegmentComplex /= behindSegmentComplexLength + aheadSegmentComplex /= aheadSegmentComplexLength + absoluteZ = getDotProductPlusOne( aheadSegmentComplex, behindSegmentComplex ) + if behindSegmentComplexLength * absoluteZ < channelRadius: + return True + return aheadSegmentComplexLength * absoluteZ < channelRadius + +def isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Determine if a path is crossing inside the x segment.' + rotatedPath = getRotatedComplexes( segmentYMirror, path ) + for pointIndex in xrange( len( rotatedPath ) - 1 ): + pointFirst = rotatedPath[pointIndex] + pointSecond = rotatedPath[pointIndex + 1] + if isLineIntersectingInsideXSegment( pointFirst, pointSecond, segmentFirstX, segmentSecondX, y ): + return True + return False + +def isXSegmentIntersectingPaths( paths, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Determine if a path list is crossing inside the x segment.' + for path in paths: + if isXSegmentIntersectingPath( path, segmentFirstX, segmentSecondX, segmentYMirror, y ): + return True + return False + +def joinSegmentTables( fromTable, intoTable ): + 'Join both segment tables and put the join into the intoTable.' + intoTableKeys = intoTable.keys() + fromTableKeys = fromTable.keys() + joinedKeyTable = {} + concatenatedTableKeys = intoTableKeys + fromTableKeys + for concatenatedTableKey in concatenatedTableKeys: + joinedKeyTable[ concatenatedTableKey ] = None + joinedKeys = joinedKeyTable.keys() + joinedKeys.sort() + for joinedKey in joinedKeys: + xIntersectionIndexList = [] + if joinedKey in intoTable: + addXIntersectionIndexesFromSegments( 0, intoTable[ joinedKey ], xIntersectionIndexList ) + if joinedKey in fromTable: + addXIntersectionIndexesFromSegments( 1, fromTable[ joinedKey ], xIntersectionIndexList ) + xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList ) + lineSegments = getSegmentsFromXIntersections( xIntersections, joinedKey ) + if len( lineSegments ) > 0: + intoTable[ joinedKey ] = lineSegments + else: + print('This should never happen, there are no line segments in joinSegments in euclidean') + +def joinXIntersectionsTables( fromTable, intoTable ): + 'Join both XIntersections tables and put the join into the intoTable.' + joinedKeyTable = {} + concatenatedTableKeys = fromTable.keys() + intoTable.keys() + for concatenatedTableKey in concatenatedTableKeys: + joinedKeyTable[ concatenatedTableKey ] = None + for joinedKey in joinedKeyTable.keys(): + xIntersectionIndexList = [] + if joinedKey in intoTable: + addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, intoTable[ joinedKey ] ) + if joinedKey in fromTable: + addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, fromTable[ joinedKey ] ) + xIntersections = getJoinOfXIntersectionIndexes( xIntersectionIndexList ) + if len( xIntersections ) > 0: + intoTable[ joinedKey ] = xIntersections + else: + print('This should never happen, there are no line segments in joinSegments in euclidean') + +def overwriteDictionary(fromDictionary, keys, toDictionary): + 'Overwrite the dictionary.' + for key in keys: + if key in fromDictionary: + toDictionary[key] = fromDictionary[key] + +def removeElementFromDictionary(dictionary, key): + 'Remove element from the dictionary.' + if key in dictionary: + del dictionary[key] + +def removeElementFromListTable(element, key, listDictionary): + 'Remove an element from the list table.' + if key not in listDictionary: + return + elementList = listDictionary[key] + if len( elementList ) < 2: + del listDictionary[key] + return + if element in elementList: + elementList.remove(element) + +def removeElementFromPixelListFromPoint( element, pixelDictionary, point ): + 'Remove an element from the pixel list.' + stepKey = getStepKeyFromPoint(point) + removeElementFromListTable( element, stepKey, pixelDictionary ) + +def removeElementsFromDictionary(dictionary, keys): + 'Remove list from the dictionary.' + for key in keys: + removeElementFromDictionary(dictionary, key) + +def removePixelTableFromPixelTable( pixelDictionaryToBeRemoved, pixelDictionaryToBeRemovedFrom ): + 'Remove pixel from the pixel table.' + removeElementsFromDictionary( pixelDictionaryToBeRemovedFrom, pixelDictionaryToBeRemoved.keys() ) + +def removePrefixFromDictionary( dictionary, prefix ): + 'Remove the attributes starting with the prefix from the dictionary.' + for key in dictionary.keys(): + if key.startswith( prefix ): + del dictionary[key] + +def removeTrueFromDictionary(dictionary, key): + 'Remove key from the dictionary in the value is true.' + if key in dictionary: + if getBooleanFromValue(dictionary[key]): + del dictionary[key] + +def removeTrueListFromDictionary( dictionary, keys ): + 'Remove list from the dictionary in the value is true.' + for key in keys: + removeTrueFromDictionary( dictionary, key ) + +def subtractXIntersectionsTable( subtractFromTable, subtractTable ): + 'Subtract the subtractTable from the subtractFromTable.' + subtractFromTableKeys = subtractFromTable.keys() + subtractFromTableKeys.sort() + for subtractFromTableKey in subtractFromTableKeys: + xIntersectionIndexList = [] + addXIntersectionIndexesFromXIntersections( - 1, xIntersectionIndexList, subtractFromTable[ subtractFromTableKey ] ) + if subtractFromTableKey in subtractTable: + addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, subtractTable[ subtractFromTableKey ] ) + xIntersections = getXIntersectionsFromIntersections( xIntersectionIndexList ) + if len( xIntersections ) > 0: + subtractFromTable[ subtractFromTableKey ] = xIntersections + else: + del subtractFromTable[ subtractFromTableKey ] + +def swapList( elements, indexBegin, indexEnd ): + 'Swap the list elements.' + elements[ indexBegin ], elements[ indexEnd ] = elements[ indexEnd ], elements[ indexBegin ] + +def toggleHashtable( hashtable, key, value ): + 'Toggle a hashtable between having and not having a key.' + if key in hashtable: + del hashtable[key] + else: + hashtable[key] = value + +def transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein): + 'Transfer the closest remaining fill loop.' + closestDistance = 987654321987654321.0 + closestFillLoop = None + for remainingFillLoop in remainingFillLoops: + distance = getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingFillLoop).distance + if distance < closestDistance: + closestDistance = distance + closestFillLoop = remainingFillLoop + newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops) + while newClosestFillLoop != None: + closestFillLoop = newClosestFillLoop + newClosestFillLoop = getLoopInsideContainingLoop(closestFillLoop, remainingFillLoops) + remainingFillLoops.remove(closestFillLoop) + addToThreadsFromLoop(extrusionHalfWidth, 'loop', closestFillLoop[:], oldOrderedLocation, skein) + +def transferClosestPath( oldOrderedLocation, remainingPaths, skein ): + 'Transfer the closest remaining path.' + closestDistance = 987654321987654321.0 + closestPath = None + oldOrderedLocationComplex = oldOrderedLocation.dropAxis() + for remainingPath in remainingPaths: + distance = min( abs( oldOrderedLocationComplex - remainingPath[0] ), abs( oldOrderedLocationComplex - remainingPath[-1] ) ) + if distance < closestDistance: + closestDistance = distance + closestPath = remainingPath + remainingPaths.remove( closestPath ) + skein.addGcodeFromThreadZ( closestPath, oldOrderedLocation.z ) + oldOrderedLocation.x = closestPath[-1].real + oldOrderedLocation.y = closestPath[-1].imag + +def transferClosestPaths(oldOrderedLocation, remainingPaths, skein): + 'Transfer the closest remaining paths.' + while len(remainingPaths) > 0: + transferClosestPath(oldOrderedLocation, remainingPaths, skein) + +def transferPathsToNestedRings(nestedRings, paths): + 'Transfer paths to nested rings.' + for nestedRing in nestedRings: + nestedRing.transferPaths(paths) + +def translateVector3Path(path, translateVector3): + 'Translate the vector3 path.' + for point in path: + point.setToVector3(point + translateVector3) + +def translateVector3Paths(paths, translateVector3): + 'Translate the vector3 paths.' + for path in paths: + translateVector3Path(path, translateVector3) + +def unbuckleBasis( basis, maximumUnbuckling, normal ): + 'Unbuckle space.' + normalDot = basis.dot( normal ) + dotComplement = math.sqrt( 1.0 - normalDot * normalDot ) + unbuckling = maximumUnbuckling + if dotComplement > 0.0: + unbuckling = min( 1.0 / dotComplement, maximumUnbuckling ) + basis.setToVector3( basis * unbuckling ) + + +class DistanceIndex: + 'A class to hold the distance and the index of the loop.' + def __init__(self, distance, index): + 'Initialize.' + self.distance = distance + self.index = index + + def __repr__(self): + 'Get the string representation of this distance index.' + return '%s, %s' % (self.distance, self.index) + + +class Endpoint: + 'The endpoint of a segment.' + def __repr__(self): + 'Get the string representation of this Endpoint.' + return 'Endpoint %s, %s' % ( self.point, self.otherEndpoint.point ) + + def getClosestEndpoint( self, endpoints ): + 'Get closest endpoint.' + smallestDistance = 987654321987654321.0 + closestEndpoint = None + for endpoint in endpoints: + distance = abs( self.point - endpoint.point ) + if distance < smallestDistance: + smallestDistance = distance + closestEndpoint = endpoint + return closestEndpoint + + def getClosestMiss(self, endpoints, path, pixelDictionary, width): + 'Get the closest endpoint which the segment to that endpoint misses the other extrusions.' + pathMaskTable = {} + smallestDistance = 987654321.0 + penultimateMinusPoint = complex(0.0, 0.0) + if len(path) > 1: + penultimatePoint = path[-2] + addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width) + penultimateMinusPoint = penultimatePoint - self.point + if abs(penultimateMinusPoint) > 0.0: + penultimateMinusPoint /= abs(penultimateMinusPoint) + for endpoint in endpoints: + endpoint.segment = endpoint.point - self.point + endpoint.segmentLength = abs(endpoint.segment) + if endpoint.segmentLength <= 0.0: + return endpoint + endpoints.sort(compareSegmentLength) + for endpoint in endpoints[: 15]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds + normalizedSegment = endpoint.segment / endpoint.segmentLength + isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > 0.9 + if not isOverlappingSelf: + if len(path) > 2: + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointRotated = segmentYMirror * self.point + endpointPointRotated = segmentYMirror * endpoint.point + if isXSegmentIntersectingPath(path[max(0, len(path) - 21) : -1], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag): + isOverlappingSelf = True + if not isOverlappingSelf: + totalMaskTable = pathMaskTable.copy() + addSegmentToPixelTable(endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width) + segmentTable = {} + addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width) + if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable): + return endpoint + return None + + def getClosestMissCheckEndpointPath( self, endpoints, path, pixelDictionary, width ): + 'Get the closest endpoint which the segment to that endpoint misses the other extrusions, also checking the path of the endpoint.' + pathMaskTable = {} + smallestDistance = 987654321.0 + penultimateMinusPoint = complex(0.0, 0.0) + if len(path) > 1: + penultimatePoint = path[-2] + addSegmentToPixelTable( penultimatePoint, self.point, pathMaskTable, 0, 0, width ) + penultimateMinusPoint = penultimatePoint - self.point + if abs(penultimateMinusPoint) > 0.0: + penultimateMinusPoint /= abs(penultimateMinusPoint) + for endpoint in endpoints: + endpoint.segment = endpoint.point - self.point + endpoint.segmentLength = abs(endpoint.segment) + if endpoint.segmentLength <= 0.0: + return endpoint + endpoints.sort( compareSegmentLength ) + for endpoint in endpoints[ : 15 ]: # increasing the number of searched endpoints increases the search time, with 20 fill took 600 seconds for cilinder.gts, with 10 fill took 533 seconds + normalizedSegment = endpoint.segment / endpoint.segmentLength + isOverlappingSelf = getDotProduct( penultimateMinusPoint, normalizedSegment ) > 0.9 + if not isOverlappingSelf: + if len(path) > 2: + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointRotated = segmentYMirror * self.point + endpointPointRotated = segmentYMirror * endpoint.point + if isXSegmentIntersectingPath( path[ max( 0, len(path) - 21 ) : - 1 ], pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag ): + isOverlappingSelf = True + endpointPath = endpoint.path + if len( endpointPath ) > 2: + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointRotated = segmentYMirror * self.point + endpointPointRotated = segmentYMirror * endpoint.point + if isXSegmentIntersectingPath( endpointPath, pointRotated.real, endpointPointRotated.real, segmentYMirror, pointRotated.imag ): + isOverlappingSelf = True + if not isOverlappingSelf: + totalMaskTable = pathMaskTable.copy() + addSegmentToPixelTable( endpoint.point, endpoint.otherEndpoint.point, totalMaskTable, 0, 0, width ) + segmentTable = {} + addSegmentToPixelTable( self.point, endpoint.point, segmentTable, 0, 0, width ) + if not isPixelTableIntersecting( pixelDictionary, segmentTable, totalMaskTable ): + return endpoint + return None + + def getFromOtherPoint( self, otherEndpoint, point ): + 'Initialize from other endpoint.' + self.otherEndpoint = otherEndpoint + self.point = point + return self + + +class LoopLayer: + 'Loops with a z.' + def __init__(self, z): + 'Initialize.' + self.loops = [] + self.z = z + + def __repr__(self): + 'Get the string representation of this loop layer.' + return '%s, %s' % (self.z, self.loops) + + +class NestedRing: + 'A nested ring.' + def __init__(self): + 'Initialize.' + self.boundary = [] + self.innerNestedRings = None + + def __repr__(self): + 'Get the string representation of this nested ring.' + return str(self.__dict__) + + def addFlattenedNestedRings(self, flattenedNestedRings): + 'Add flattened nested rings.' + flattenedNestedRings.append(self) + for innerNestedRing in self.innerNestedRings: + flattenedNestedRings += getFlattenedNestedRings(innerNestedRing.innerNestedRings) + + def getFromInsideSurroundings(self, inputSurroundingInsides): + 'Initialize from inside nested rings.' + transferredSurroundings = getTransferredNestedRings(inputSurroundingInsides, self.boundary) + self.innerNestedRings = getOrderedNestedRings(transferredSurroundings) + return self + + +class NestedBand(NestedRing): + 'A loop that surrounds paths.' + def __init__(self): + 'Initialize.' + NestedRing.__init__(self) + self.edgePaths = [] + self.extraLoops = [] + self.infillBoundaries = [] + self.infillPaths = [] +# self.lastExistingFillLoops = None + self.lastFillLoops = None + self.loop = None + self.penultimateFillLoops = [] + self.z = None + + def __repr__(self): + 'Get the string representation of this nested ring.' + stringRepresentation = 'boundary\n%s\n' % self.boundary + stringRepresentation += 'loop\n%s\n' % self.loop + stringRepresentation += 'inner nested rings\n%s\n' % self.innerNestedRings + stringRepresentation += 'infillPaths\n' + for infillPath in self.infillPaths: + stringRepresentation += 'infillPath\n%s\n' % infillPath + stringRepresentation += 'edgePaths\n' + for edgePath in self.edgePaths: + stringRepresentation += 'edgePath\n%s\n' % edgePath + return stringRepresentation + '\n' + + def addPerimeterInner(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence): + 'Add to the edge and the inner island.' + if self.loop == None: + skein.distanceFeedRate.addLine('()') + transferClosestPaths(oldOrderedLocation, self.edgePaths[:], skein) + skein.distanceFeedRate.addLine('()') + else: + addToThreadsFromLoop(extrusionHalfWidth, 'edge', self.loop[:], oldOrderedLocation, skein) + skein.distanceFeedRate.addLine('()') + addToThreadsRemove(extrusionHalfWidth, self.innerNestedRings[:], oldOrderedLocation, skein, threadSequence) + + def addToBoundary(self, vector3): + 'Add vector3 to boundary.' + self.boundary.append(vector3.dropAxis()) + self.z = vector3.z + + def addToLoop(self, vector3): + 'Add vector3 to loop.' + if self.loop == None: + self.loop = [] + self.loop.append(vector3.dropAxis()) + self.z = vector3.z + + def addToThreads(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence): + 'Add to paths from the last location.' + addNestedRingBeginning(skein.distanceFeedRate, self.boundary, self.z) + threadFunctionDictionary = { + 'infill' : self.transferInfillPaths, 'loops' : self.transferClosestFillLoops, 'edge' : self.addPerimeterInner} + for threadType in threadSequence: + threadFunctionDictionary[threadType](extrusionHalfWidth, oldOrderedLocation, skein, threadSequence) + skein.distanceFeedRate.addLine('()') + + def getFillLoops(self, penultimateFillLoops): + 'Get last fill loops from the outside loop and the loops inside the inside loops.' + fillLoops = self.getLoopsToBeFilled()[:] + surroundingBoundaries = self.getSurroundingBoundaries() + withinLoops = [] + if penultimateFillLoops == None: + penultimateFillLoops = self.penultimateFillLoops + if penultimateFillLoops == None: + print('Warning, penultimateFillLoops == None in getFillLoops in NestedBand in euclidean.') + return fillLoops + for penultimateFillLoop in penultimateFillLoops: + if len(penultimateFillLoop) > 2: + if getIsInFilledRegion(surroundingBoundaries, penultimateFillLoop[0]): + withinLoops.append(penultimateFillLoop) + if not getIsInFilledRegionByPaths(self.penultimateFillLoops, fillLoops): + fillLoops += self.penultimateFillLoops + for nestedRing in self.innerNestedRings: + fillLoops += getFillOfSurroundings(nestedRing.innerNestedRings, penultimateFillLoops) + return fillLoops +# +# def getLastExistingFillLoops(self): +# 'Get last existing fill loops.' +# lastExistingFillLoops = self.lastExistingFillLoops[:] +# for nestedRing in self.innerNestedRings: +# lastExistingFillLoops += nestedRing.getLastExistingFillLoops() +# return lastExistingFillLoops + + def getLoopsToBeFilled(self): + 'Get last fill loops from the outside loop and the loops inside the inside loops.' + if self.lastFillLoops == None: + return self.getSurroundingBoundaries() + return self.lastFillLoops + + def getSurroundingBoundaries(self): + 'Get the boundary of the surronding loop plus any boundaries of the innerNestedRings.' + surroundingBoundaries = [self.boundary] + for nestedRing in self.innerNestedRings: + surroundingBoundaries.append(nestedRing.boundary) + return surroundingBoundaries + + def transferClosestFillLoops(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence): + 'Transfer closest fill loops.' + if len( self.extraLoops ) < 1: + return + remainingFillLoops = self.extraLoops[:] + while len( remainingFillLoops ) > 0: + transferClosestFillLoop(extrusionHalfWidth, oldOrderedLocation, remainingFillLoops, skein) + + def transferInfillPaths(self, extrusionHalfWidth, oldOrderedLocation, skein, threadSequence): + 'Transfer the infill paths.' + if len(self.infillBoundaries) == 0 and len(self.infillPaths) == 0: + return + skein.distanceFeedRate.addLine('()') + for infillBoundary in self.infillBoundaries: + skein.distanceFeedRate.addLine('()') + for infillPoint in infillBoundary: + infillPointVector3 = Vector3(infillPoint.real, infillPoint.imag, self.z) + skein.distanceFeedRate.addLine(skein.distanceFeedRate.getInfillBoundaryLine(infillPointVector3)) + skein.distanceFeedRate.addLine('()') + transferClosestPaths(oldOrderedLocation, self.infillPaths[:], skein) + skein.distanceFeedRate.addLine('()') + + def transferPaths(self, paths): + 'Transfer paths.' + for nestedRing in self.innerNestedRings: + transferPathsToNestedRings(nestedRing.innerNestedRings, paths) + self.infillPaths = getTransferredPaths(paths, self.boundary) + + +class PathZ: + 'Complex path with a z.' + def __init__( self, z ): + self.path = [] + self.z = z + + def __repr__(self): + 'Get the string representation of this path z.' + return '%s, %s' % ( self.z, self.path ) + + +class ProjectiveSpace: + 'Class to define a projective space.' + def __init__( self, basisX = Vector3(1.0, 0.0, 0.0), basisY = Vector3( 0.0, 1.0, 0.0 ), basisZ = Vector3(0.0, 0.0, 1.0) ): + 'Initialize the basis vectors.' + self.basisX = basisX + self.basisY = basisY + self.basisZ = basisZ + + def __repr__(self): + 'Get the string representation of this ProjectivePlane.' + return '%s, %s, %s' % ( self.basisX, self.basisY, self.basisZ ) + + def getByBasisXZ( self, basisX, basisZ ): + 'Get by x basis x and y basis.' + self.basisX = basisX + self.basisZ = basisZ + self.basisX.normalize() + self.basisY = basisZ.cross(self.basisX) + self.basisY.normalize() + return self + + def getByBasisZFirst(self, basisZ, firstVector3): + 'Get by basisZ and first.' + self.basisZ = basisZ + self.basisY = basisZ.cross(firstVector3) + self.basisY.normalize() + self.basisX = self.basisY.cross(self.basisZ) + self.basisX.normalize() + return self + + def getByBasisZTop(self, basisZ, top): + 'Get by basisZ and top.' + return self.getByBasisXZ(top.cross(basisZ), basisZ) + + def getByLatitudeLongitude( self, viewpointLatitude, viewpointLongitude ): + 'Get by latitude and longitude.' + longitudeComplex = getWiddershinsUnitPolar( math.radians( 90.0 - viewpointLongitude ) ) + viewpointLatitudeRatio = getWiddershinsUnitPolar( math.radians( viewpointLatitude ) ) + basisZ = Vector3( viewpointLatitudeRatio.imag * longitudeComplex.real, viewpointLatitudeRatio.imag * longitudeComplex.imag, viewpointLatitudeRatio.real ) + return self.getByBasisXZ( Vector3( - longitudeComplex.imag, longitudeComplex.real, 0.0 ), basisZ ) + + def getByTilt( self, tilt ): + 'Get by latitude and longitude.' + xPlaneAngle = getWiddershinsUnitPolar( tilt.real ) + self.basisX = Vector3( xPlaneAngle.real, 0.0, xPlaneAngle.imag ) + yPlaneAngle = getWiddershinsUnitPolar( tilt.imag ) + self.basisY = Vector3( 0.0, yPlaneAngle.real, yPlaneAngle.imag ) + self.basisZ = self.basisX.cross(self.basisY) + return self + + def getComplexByComplex( self, pointComplex ): + 'Get complex by complex point.' + return self.basisX.dropAxis() * pointComplex.real + self.basisY.dropAxis() * pointComplex.imag + + def getCopy(self): + 'Get copy.' + return ProjectiveSpace( self.basisX, self.basisY, self.basisZ ) + + def getDotComplex(self, point): + 'Get the dot complex.' + return complex( point.dot(self.basisX), point.dot(self.basisY) ) + + def getDotVector3(self, point): + 'Get the dot vector3.' + return Vector3(point.dot(self.basisX), point.dot(self.basisY), point.dot(self.basisZ)) + + def getNextSpace( self, nextNormal ): + 'Get next space by next normal.' + nextSpace = self.getCopy() + nextSpace.normalize() + dotNext = nextSpace.basisZ.dot( nextNormal ) + if dotNext > 0.999999: + return nextSpace + if dotNext < - 0.999999: + nextSpace.basisX = - nextSpace.basisX + return nextSpace + crossNext = nextSpace.basisZ.cross( nextNormal ) + oldBasis = ProjectiveSpace().getByBasisZTop( nextSpace.basisZ, crossNext ) + newBasis = ProjectiveSpace().getByBasisZTop( nextNormal, crossNext ) + nextSpace.basisX = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisX ) ) + nextSpace.basisY = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisY ) ) + nextSpace.basisZ = newBasis.getVector3ByPoint( oldBasis.getDotVector3( nextSpace.basisZ ) ) + nextSpace.normalize() + return nextSpace + + def getSpaceByXYScaleAngle( self, angle, scale ): + 'Get space by angle and scale.' + spaceByXYScaleRotation = ProjectiveSpace() + planeAngle = getWiddershinsUnitPolar(angle) + spaceByXYScaleRotation.basisX = self.basisX * scale.real * planeAngle.real + self.basisY * scale.imag * planeAngle.imag + spaceByXYScaleRotation.basisY = - self.basisX * scale.real * planeAngle.imag + self.basisY * scale.imag * planeAngle.real + spaceByXYScaleRotation.basisZ = self.basisZ + return spaceByXYScaleRotation + + def getVector3ByPoint(self, point): + 'Get vector3 by point.' + return self.basisX * point.x + self.basisY * point.y + self.basisZ * point.z + + def normalize(self): + 'Normalize.' + self.basisX.normalize() + self.basisY.normalize() + self.basisZ.normalize() + + def unbuckle( self, maximumUnbuckling, normal ): + 'Unbuckle space.' + unbuckleBasis( self.basisX, maximumUnbuckling, normal ) + unbuckleBasis( self.basisY, maximumUnbuckling, normal ) + + +class XIntersectionIndex: + 'A class to hold the x intersection position and the index of the loop which intersected.' + def __init__( self, index, x ): + 'Initialize.' + self.index = index + self.x = x + + def __cmp__(self, other): + 'Get comparison in order to sort x intersections in ascending order of x.' + if self.x < other.x: + return - 1 + return int( self.x > other.x ) + + def __eq__(self, other): + 'Determine whether this XIntersectionIndex is identical to other one.' + if other == None: + return False + if other.__class__ != self.__class__: + return False + return self.index == other.index and self.x == other.x + + def __ne__(self, other): + 'Determine whether this XIntersectionIndex is not identical to other one.' + return not self.__eq__(other) + + def __repr__(self): + 'Get the string representation of this x intersection.' + return 'XIntersectionIndex index %s; x %s ' % ( self.index, self.x ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/__init__.py new file mode 100644 index 0000000..2dc8ddc --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 2 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/alphabetize.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/alphabetize.py new file mode 100644 index 0000000..379d2ef --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/alphabetize.py @@ -0,0 +1,232 @@ +""" +Alphabetize is a script to alphabetize functions and signatures. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +import cStringIO +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addTogetherList(functionList, togetherLists): + 'Add the togetherList to the togetherLists is the sorted is different.' + sortedList = functionList[:] + sortedList.sort(compareFunctionName) + togetherList = None + for functionIndex in xrange(len(functionList)): + function = functionList[functionIndex] + sorted = sortedList[functionIndex] + if function != sorted: + together = (function, sorted) + if togetherList == None: + togetherList = [] + togetherLists.append(togetherList) + togetherList.append(together) + +def compareFunctionName(first, second): + 'Compare the function names.' + first = getConvertedName(first) + second = getConvertedName(second) + if first < second: + return -1 + return first < second + +def getConvertedName(name): + 'Get converted name with init at the beginning and main at the endCompare the function names.' + if name == 'def __init__': + return 'def !__init__' + if name == 'def main': + return 'def |main' + return name.lower() + +def getFunctionLists(fileName): + 'Get the function lists in the file.' + fileText = archive.getFileText(fileName) + functionList = [] + functionLists = [functionList] + lines = archive.getTextLines(fileText) + for line in lines: + lineStripped = line.strip() + if lineStripped.startswith('def '): + bracketIndex = lineStripped.find('(') + if bracketIndex > -1: + lineStripped = lineStripped[: bracketIndex] + functionList.append(lineStripped) + elif line.startswith('class'): + functionList = [] + functionLists.append(functionList) + return functionLists + +def getFunctionsWithStringByFileName(fileName, searchString): + 'Get the functions with the search string in the file.' + fileText = archive.getFileText(fileName) + functions = [] + lines = archive.getTextLines(fileText) + for line in lines: + lineStripped = line.strip() +# if lineStripped.startswith('def ') and searchString in lineStripped and '=' in lineStripped: + if lineStripped.startswith('def ') and searchString in lineStripped: + if '(self, ' not in lineStripped or lineStripped.count(',') > 1: + functions.append(lineStripped[len('def ') :].strip()) + functions.sort() + return functions + +def getFunctionsWithStringByFileNames(fileNames, searchString): + 'Get the functions with the search string in the files.' + functions = [] + for fileName in fileNames: + functions += getFunctionsWithStringByFileName(fileName, searchString) + functions.sort() + return functions + +def getParameterSequence(functionName): + 'Get the parameter sequence.' + parameterDictionary = {} + parameterSequence = [] + parameterText = functionName[functionName.find('(') + 1 :].replace('xmlElement', 'elementNode') + snippet = Snippet(0, parameterText) + strippedParameters = [] + for parameter in snippet.parameters: + strippedParameter = parameter.strip() + if strippedParameter != 'self': + strippedParameters.append(strippedParameter) + for parameterIndex, parameter in enumerate(strippedParameters): + parameterDictionary[parameter] = parameterIndex + sortedParameters = strippedParameters[:] + sortedParameters.sort() + for sortedParameter in sortedParameters: + parameterSequence.append(parameterDictionary[sortedParameter]) + return parameterSequence + +def getSnippetsByFileName(fileName, functionName): + 'Get the function signature snippets by the file name.' + fileText = archive.getFileText(fileName) + snippets = [] + functionStart = functionName[: functionName.find('(') + 1] + tokenEnd = getTokenEnd(0, fileText, functionStart) + while tokenEnd != -1: + snippet = Snippet(tokenEnd, fileText) + snippets.append(snippet) + tokenEnd = getTokenEnd(snippet.characterIndex, fileText, functionStart) + return snippets + +def getTogetherLists(fileName): + 'Get the lists of the unsorted and sorted functions in the file.' + functionLists = getFunctionLists(fileName) + togetherLists = [] + for functionList in functionLists: + addTogetherList(functionList, togetherLists) + return togetherLists + +def getTokenEnd(characterIndex, fileText, token): + 'Get the token end index for the file text and token.' + tokenIndex = fileText.find(token, characterIndex) + if tokenIndex == -1: + return -1 + return tokenIndex + len(token) + +def printTogetherListsByFileNames(fileNames): + 'Print the together lists of the file names, if the file name has a together list.' + for fileName in fileNames: + togetherLists = getTogetherLists(fileName) + if len(togetherLists) > 0: + for togetherList in togetherLists: + for together in togetherList: + function = together[0] + sorted = together[1] + return + + +class EndCharacterMonad: + 'A monad to return the parent monad when it encounters the end character.' + def __init__(self, endCharacter, parentMonad): + 'Initialize.' + self.endCharacter = endCharacter + self.parentMonad = parentMonad + + def getNextMonad(self, character): + 'Get the next monad.' + self.getSnippet().input.write(character) + if character == self.endCharacter: + return self.parentMonad + return self + + def getSnippet(self): + 'Get the snippet.' + return self.parentMonad.getSnippet() + + +class ParameterMonad: + 'A monad to handle parameters.' + def __init__(self, snippet): + 'Initialize.' + self.snippet = snippet + + def addParameter(self): + 'Add parameter to the snippet.' + parameterString = self.snippet.input.getvalue() + if len(parameterString) != 0: + self.snippet.input = cStringIO.StringIO() + self.snippet.parameters.append(parameterString) + + def getNextMonad(self, character): + 'Get the next monad.' + if character == '"': + self.snippet.input.write(character) + return EndCharacterMonad('"', self) + if character == '"': + self.snippet.input.write(character) + return EndCharacterMonad('"', self) + if character == '(': + self.snippet.input.write(character) + return EndCharacterMonad(')', self) + if character == ')': + self.addParameter() + return None + if character == ',': + self.addParameter() + return self + self.snippet.input.write(character) + return self + + def getSnippet(self): + 'Get the snippet.' + return self.snippet + + +class Snippet: + 'A class to get the variables for a function.' + def __init__(self, characterIndex, fileText): + 'Initialize.' + self.characterIndex = characterIndex + self.input = cStringIO.StringIO() + self.parameters = [] + monad = ParameterMonad(self) + for characterIndex in xrange(self.characterIndex, len(fileText)): + character = fileText[characterIndex] + monad = monad.getNextMonad(character) + if monad == None: + return + + def __repr__(self): + 'Get the string representation of this Snippet.' + return '%s %s' % (self.characterIndex, self.parameters) + + +def main(): + 'Run main function.' +# printTogetherListsByFileNames(archive.getPythonFileNamesExceptInitRecursively('/home/enrique/Desktop/fabmetheus')) + functions = getFunctionsWithStringByFileNames(archive.getPythonFileNamesExceptInitRecursively('/home/enrique/Desktop/fabmetheus'), ', xmlElement') + print(functions) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/fabmetheus_interpret.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/fabmetheus_interpret.py new file mode 100644 index 0000000..65de7cf --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/fabmetheus_interpret.py @@ -0,0 +1,132 @@ +""" +Fabmetheus interpret is a fabmetheus utility to interpret a file, turning it into fabmetheus constructive solid geometry xml. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCarving(fileName): + "Get carving." + pluginModule = getInterpretPlugin(fileName) + if pluginModule == None: + return None + return pluginModule.getCarving(fileName) + +def getGNUTranslatorFilesUnmodified(): + "Get the file types from the translators in the import plugins folder." + return archive.getFilesWithFileTypesWithoutWords(getImportPluginFileNames()) + +def getGNUTranslatorGcodeFileTypeTuples(): + "Get the file type tuples from the translators in the import plugins folder plus gcode." + fileTypeTuples = getTranslatorFileTypeTuples() + fileTypeTuples.append( ('Gcode text files', '*.gcode') ) + fileTypeTuples.sort() + return fileTypeTuples + +def getImportPluginFileNames(): + "Get interpret plugin fileNames." + return archive.getPluginFileNamesFromDirectoryPath( getPluginsDirectoryPath() ) + +def getInterpretPlugin(fileName): + "Get the interpret plugin for the file." + importPluginFileNames = getImportPluginFileNames() + for importPluginFileName in importPluginFileNames: + fileTypeDot = '.' + importPluginFileName + if fileName[ - len(fileTypeDot) : ].lower() == fileTypeDot: + importPluginsDirectoryPath = getPluginsDirectoryPath() + pluginModule = archive.getModuleWithDirectoryPath( importPluginsDirectoryPath, importPluginFileName ) + if pluginModule != None: + return pluginModule + print('Could not find plugin to handle ' + fileName ) + return None + +def getNewRepository(): + 'Get new repository.' + return InterpretRepository() + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getInterpretPluginsPath() + +def getTranslatorFileTypeTuples(): + "Get the file types from the translators in the import plugins folder." + importPluginFileNames = getImportPluginFileNames() + fileTypeTuples = [] + for importPluginFileName in importPluginFileNames: + fileTypeTitle = importPluginFileName.upper() + ' files' + fileType = ( fileTypeTitle, '*.' + importPluginFileName ) + fileTypeTuples.append( fileType ) + fileTypeTuples.sort() + return fileTypeTuples + +def getWindowAnalyzeFile(fileName): + "Get file interpretion." + startTime = time.time() + carving = getCarving(fileName) + if carving == None: + return None + interpretGcode = str( carving ) + if interpretGcode == '': + return None + repository = settings.getReadRepository( InterpretRepository() ) + if repository.printInterpretion.value: + print(interpretGcode) + suffixFileName = fileName[ : fileName.rfind('.') ] + '_interpret.' + carving.getInterpretationSuffix() + suffixDirectoryName = os.path.dirname(suffixFileName) + suffixReplacedBaseName = os.path.basename(suffixFileName).replace(' ', '_') + suffixFileName = os.path.join( suffixDirectoryName, suffixReplacedBaseName ) + archive.writeFileText( suffixFileName, interpretGcode ) + print('The interpret file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + print('It took %s to interpret the file.' % euclidean.getDurationString( time.time() - startTime ) ) + textProgram = repository.textProgram.value + if textProgram == '': + return None + if textProgram == 'webbrowser': + settings.openWebPage(suffixFileName) + return None + textFilePath = '"' + os.path.normpath(suffixFileName) + '"' # " to send in file name with spaces + shellCommand = textProgram + ' ' + textFilePath + print('Sending the shell command:') + print(shellCommand) + commandResult = os.system(shellCommand) + if commandResult != 0: + print('It may be that the system could not find the %s program.' % textProgram ) + print('If so, try installing the %s program or look for another one, like Open Office which can be found at:' % textProgram ) + print('http://www.openoffice.org/') + print('Open office writer can then be started from the command line with the command "soffice -writer".') + + +class InterpretRepository: + "A class to handle the interpret settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.analyze_plugins.interpret.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Interpret', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Interpret') + self.activateInterpret = settings.BooleanSetting().getFromValue('Activate Interpret', self, False ) + self.printInterpretion = settings.BooleanSetting().getFromValue('Print Interpretion', self, False ) + self.textProgram = settings.StringSetting().getFromValue('Text Program:', self, 'webbrowser') + self.executeTitle = 'Interpret' + + def execute(self): + "Write button has been clicked." + fileNames = skeinforge_polyfile.getFileOrGcodeDirectory( self.fileNameInput.value, self.fileNameInput.wasCancelled ) + for fileName in fileNames: + getWindowAnalyzeFile(fileName) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/__init__.py new file mode 100644 index 0000000..cefa3e7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/__init__.py @@ -0,0 +1,12 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/csv.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/csv.py new file mode 100644 index 0000000..95886ed --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/csv.py @@ -0,0 +1,159 @@ +""" +This page is in the table of contents. +The csv.py script is an import translator plugin to get a carving from an csv file. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an csv file and returns the carving. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import xml_simple_reader +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCarving(fileName=''): + "Get the carving for the csv file." + csvText = archive.getFileText(fileName) + if csvText == '': + return None + csvParser = CSVSimpleParser( fileName, None, csvText ) + lowerLocalName = csvParser.getDocumentElement().getNodeName().lower() + pluginModule = archive.getModuleWithDirectoryPath( getPluginsDirectoryPath(), lowerLocalName ) + if pluginModule == None: + return None + return pluginModule.getCarvingFromParser( csvParser ) + +def getLineDictionary(line): + "Get the line dictionary." + lineDictionary = {} + splitLine = line.split('\t') + for splitLineIndex in xrange( len(splitLine) ): + word = splitLine[ splitLineIndex ] + if word != '': + lineDictionary[ splitLineIndex ] = word + return lineDictionary + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getInterpretPluginsPath('xml_plugins') + + +class CSVElement( xml_simple_reader.XMLElement ): + "A csv element." + def continueParsingObject( self, line, lineStripped ): + "Parse replaced line." + splitLineStripped = lineStripped.split('\t') + key = splitLineStripped[0] + value = splitLineStripped[1] + self.attributes[key] = value + self.addToIdentifierDictionaries() + + def continueParsingTable( self, line, lineStripped ): + "Parse replaced line." + if self.headingDictionary == None: + self.headingDictionary = getLineDictionary(line) + return + csvElement = self + oldAttributesLength = len( self.attributes ) + if oldAttributesLength > 0: + csvElement = CSVElement() + csvElement.parentNode = self.parentNode + csvElement.localName = self.localName + lineDictionary = getLineDictionary(line) + for columnIndex in lineDictionary.keys(): + if columnIndex in self.headingDictionary: + key = self.headingDictionary[ columnIndex ] + value = lineDictionary[ columnIndex ] + csvElement.attributes[key] = value + csvElement.addToIdentifierDictionaries() + if len( csvElement.attributes ) == 0 or oldAttributesLength == 0 or self.parentNode == None: + return + self.parentNode.childNodes.append( csvElement ) + + def getElementFromObject( self, leadingTabCount, lineStripped, oldElement ): + "Parse replaced line." + splitLine = lineStripped.split('\t') + self.localName = splitLine[1] + if leadingTabCount == 0: + return self + self.parentNode = oldElement + while leadingTabCount <= self.parentNode.getNumberOfParents(): + self.parentNode = self.parentNode.parentNode + self.parentNode.childNodes.append(self) + return self + + def getElementFromTable( self, leadingTabCount, lineStripped, oldElement ): + "Parse replaced line." + self.headingDictionary = None + return self.getElementFromObject( leadingTabCount, lineStripped, oldElement ) + + def getNumberOfParents(self): + "Get the number of parent nodes." + if self.parentNode == None: + return 0 + return self.parentNode.getNumberOfParents() + 1 + + +class CSVSimpleParser( xml_simple_reader.DocumentNode ): + "A simple csv parser." + def __init__( self, parentNode, csvText ): + "Add empty lists." + self.continueFunction = None + self.extraLeadingTabCount = None + self.lines = archive.getTextLines( csvText ) + self.oldCSVElement = None + self.documentElement = None + for line in self.lines: + self.parseLine(line) + + def getNewCSVElement( self, leadingTabCount, lineStripped ): + "Get a new csv element." + if self.documentElement != None and self.extraLeadingTabCount == None: + self.extraLeadingTabCount = 1 - leadingTabCount + if self.extraLeadingTabCount != None: + leadingTabCount += self.extraLeadingTabCount + if lineStripped[ : len('_table') ] == '_table' or lineStripped[ : len('_t') ] == '_t': + self.oldCSVElement = CSVElement().getElementFromTable( leadingTabCount, lineStripped, self.oldCSVElement ) + self.continueFunction = self.oldCSVElement.continueParsingTable + return + self.oldCSVElement = CSVElement().getElementFromObject( leadingTabCount, lineStripped, self.oldCSVElement ) + self.continueFunction = self.oldCSVElement.continueParsingObject + + def parseLine(self, line): + "Parse a gcode line and add it to the inset skein." + lineStripped = line.lstrip() + if len( lineStripped ) < 1: + return + leadingPart = line[ : line.find( lineStripped ) ] + leadingTabCount = leadingPart.count('\t') + if lineStripped[ : len('_') ] == '_': + self.getNewCSVElement( leadingTabCount, lineStripped ) + if self.documentElement == None: + self.documentElement = self.oldCSVElement + self.documentElement.document = self + return + if self.continueFunction != None: + self.continueFunction( line, lineStripped ) + + +def main(): + "Display the inset dialog." + if len(sys.argv) > 1: + getCarving(' '.join(sys.argv[1 :])) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/gts.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/gts.py new file mode 100644 index 0000000..50ed1d9 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/gts.py @@ -0,0 +1,80 @@ +""" +This page is in the table of contents. +The gts.py script is an import translator plugin to get a carving from an gts file. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an gts file and returns the carving. + +The GNU Triangulated Surface (.gts) format is described at: +http://gts.sourceforge.net/reference/gts-surfaces.html#GTS-SURFACE-WRITE + +Quoted from http://gts.sourceforge.net/reference/gts-surfaces.html#GTS-SURFACE-WRITE +"All the lines beginning with GTS_COMMENTS (#!) are ignored. The first line contains three unsigned integers separated by spaces. The first integer is the number of vertexes, nv, the second is the number of edges, ne and the third is the number of faces, nf. + +Follows nv lines containing the x, y and z coordinates of the vertexes. Follows ne lines containing the two indices (starting from one) of the vertexes of each edge. Follows nf lines containing the three ordered indices (also starting from one) of the edges of each face. + +The format described above is the least common denominator to all GTS files. Consistent with an object-oriented approach, the GTS file format is extensible. Each of the lines of the file can be extended with user-specific attributes accessible through the read() and write() virtual methods of each of the objects written (surface, vertexes, edges or faces). When read with different object classes, these extra attributes are just ignored." + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import face +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCarving(fileName): + "Get the carving for the gts file." + return getFromGNUTriangulatedSurfaceText( archive.getFileText(fileName), triangle_mesh.TriangleMesh() ) + +def getFromGNUTriangulatedSurfaceText( gnuTriangulatedSurfaceText, triangleMesh ): + "Initialize from a GNU Triangulated Surface Text." + if gnuTriangulatedSurfaceText == '': + return None + lines = archive.getTextLines( gnuTriangulatedSurfaceText ) + linesWithoutComments = [] + for line in lines: + if len(line) > 0: + firstCharacter = line[0] + if firstCharacter != '#' and firstCharacter != '!': + linesWithoutComments.append(line) + splitLine = linesWithoutComments[0].split() + numberOfVertexes = int( splitLine[0] ) + numberOfEdges = int(splitLine[1]) + numberOfFaces = int( splitLine[2] ) + faceTriples = [] + for vertexIndex in xrange( numberOfVertexes ): + line = linesWithoutComments[ vertexIndex + 1 ] + splitLine = line.split() + vertex = Vector3( float( splitLine[0] ), float(splitLine[1]), float( splitLine[2] ) ) + triangleMesh.vertexes.append(vertex) + edgeStart = numberOfVertexes + 1 + for edgeIndex in xrange( numberOfEdges ): + line = linesWithoutComments[ edgeIndex + edgeStart ] + splitLine = line.split() + vertexIndexes = [] + for word in splitLine[ : 2 ]: + vertexIndexes.append( int(word) - 1 ) + edge = face.Edge().getFromVertexIndexes( edgeIndex, vertexIndexes ) + triangleMesh.edges.append( edge ) + faceStart = edgeStart + numberOfEdges + for faceIndex in xrange( numberOfFaces ): + line = linesWithoutComments[ faceIndex + faceStart ] + splitLine = line.split() + edgeIndexes = [] + for word in splitLine[ : 3 ]: + edgeIndexes.append( int(word) - 1 ) + triangleMesh.faces.append( face.Face().getFromEdgeIndexes( edgeIndexes, triangleMesh.edges, faceIndex ) ) + return triangleMesh diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/obj.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/obj.py new file mode 100644 index 0000000..257d82f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/obj.py @@ -0,0 +1,78 @@ +""" +This page is in the table of contents. +The obj.py script is an import translator plugin to get a carving from an obj file. + +An example obj file is box.obj in the models folder. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an obj file and returns the carving. + +From wikipedia, OBJ (or .OBJ) is a geometry definition file format first developed by Wavefront Technologies for its Advanced Visualizer animation package: +http://en.wikipedia.org/wiki/Obj + +The Object File specification is at: +http://local.wasp.uwa.edu.au/~pbourke/dataformats/obj/ + +An excellent link page about obj files is at: +http://people.sc.fsu.edu/~burkardt/data/obj/obj.html + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import face +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from struct import unpack + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addFacesGivenText( objText, triangleMesh ): + "Add faces given obj text." + lines = archive.getTextLines( objText ) + for line in lines: + splitLine = line.split() + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'v': + triangleMesh.vertexes.append( getVertexGivenLine(line) ) + elif firstWord == 'f': + triangleMesh.faces.append( getFaceGivenLine( line, triangleMesh ) ) + +def getCarving(fileName=''): + "Get the triangle mesh for the obj file." + if fileName == '': + return None + objText = archive.getFileText(fileName, True, 'rb') + if objText == '': + return None + triangleMesh = triangle_mesh.TriangleMesh() + addFacesGivenText(objText, triangleMesh) + return triangleMesh + +def getFaceGivenLine( line, triangleMesh ): + "Add face given line index and lines." + faceGivenLine = face.Face() + faceGivenLine.index = len( triangleMesh.faces ) + splitLine = line.split() + for vertexStringIndex in xrange( 1, 4 ): + vertexString = splitLine[ vertexStringIndex ] + vertexStringWithSpaces = vertexString.replace('/', ' ') + vertexStringSplit = vertexStringWithSpaces.split() + vertexIndex = int( vertexStringSplit[0] ) - 1 + faceGivenLine.vertexIndexes.append(vertexIndex) + return faceGivenLine + +def getVertexGivenLine(line): + "Get vertex given obj vertex line." + splitLine = line.split() + return Vector3( float(splitLine[1]), float( splitLine[2] ), float( splitLine[3] ) ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/slc.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/slc.py new file mode 100644 index 0000000..23b2d19 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/slc.py @@ -0,0 +1,188 @@ +""" +This page is in the table of contents. +The slc.py script is an import translator plugin to get a carving from an [http://rapid.lpt.fi/archives/rp-ml-1999/0713.html slc file]. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an slc file and returns the carving. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import svg_writer +from struct import unpack +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCarving(fileName=''): + "Get the triangle mesh for the slc file." + carving = SLCCarving() + carving.readFile(fileName) + return carving + +def getLittleEndianFloatGivenFile( file ): + "Get little endian float given a file." + return unpack(' 2: + loopLayer.loops.append( getPointsFromFile( numPoints, file ) ) + + def readFile( self, fileName ): + "Read SLC and store the layers." + self.fileName = fileName + pslcfile = open( fileName, 'rb') + readHeader( pslcfile ) + pslcfile.read( 256 ) #Go past the 256 byte 3D Reserved Section. + self.readTableEntry( pslcfile ) + self.processContourLayers( pslcfile ) + pslcfile.close() + self.cornerMaximum = Vector3(-987654321.0, -987654321.0, self.maximumZ) + self.cornerMinimum = Vector3(987654321.0, 987654321.0, self.minimumZ) + for loopLayer in self.loopLayers: + for loop in loopLayer.loops: + for point in loop: + pointVector3 = Vector3(point.real, point.imag, loopLayer.z) + self.cornerMaximum.maximize(pointVector3) + self.cornerMinimum.minimize(pointVector3) + halfLayerThickness = 0.5 * self.layerHeight + self.cornerMaximum.z += halfLayerThickness + self.cornerMinimum.z -= halfLayerThickness + + def readTableEntry( self, file ): + "Read in the sampling table section. It contains a table length (byte) and the table entries." + tableEntrySize = ord( file.read( 1 ) ) + if tableEntrySize == 0: + print("Sampling table size is zero!") + exit() + for index in xrange( tableEntrySize ): + sampleTableEntry = SampleTableEntry( file ) + self.layerHeight = sampleTableEntry.layerHeight + + def setCarveImportRadius( self, importRadius ): + "Set the import radius." + pass + + def setCarveIsCorrectMesh( self, isCorrectMesh ): + "Set the is correct mesh flag." + pass + + def setCarveLayerHeight( self, layerHeight ): + "Set the layer height." + pass + + +def main(): + "Display the inset dialog." + if len(sys.argv) > 1: + getCarving(' '.join(sys.argv[1 :])) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/stl.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/stl.py new file mode 100644 index 0000000..7286297 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/stl.py @@ -0,0 +1,111 @@ +""" +This page is in the table of contents. +The stl.py script is an import translator plugin to get a carving from an stl file. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an stl file and returns the carving. + +STL is an inferior triangle surface format, described at: +http://en.wikipedia.org/wiki/STL_(file_format) + +A good triangle surface format is the GNU Triangulated Surface format which is described at: +http://gts.sourceforge.net/reference/gts-surfaces.html#GTS-SURFACE-WRITE + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import face +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from struct import unpack + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addFacesGivenBinary( stlData, triangleMesh, vertexIndexTable ): + "Add faces given stl binary." + numberOfVertexes = ( len( stlData ) - 84 ) / 50 + vertexes = [] + for vertexIndex in xrange( numberOfVertexes ): + byteIndex = 84 + vertexIndex * 50 + vertexes.append( getVertexGivenBinary( byteIndex + 12, stlData ) ) + vertexes.append( getVertexGivenBinary( byteIndex + 24, stlData ) ) + vertexes.append( getVertexGivenBinary( byteIndex + 36, stlData ) ) + addFacesGivenVertexes( triangleMesh, vertexIndexTable, vertexes ) + +def addFacesGivenText( stlText, triangleMesh, vertexIndexTable ): + "Add faces given stl text." + lines = archive.getTextLines( stlText ) + vertexes = [] + for line in lines: + if line.find('vertex') != - 1: + vertexes.append( getVertexGivenLine(line) ) + addFacesGivenVertexes( triangleMesh, vertexIndexTable, vertexes ) + +def addFacesGivenVertexes( triangleMesh, vertexIndexTable, vertexes ): + "Add faces given stl text." + for vertexIndex in xrange( 0, len(vertexes), 3 ): + triangleMesh.faces.append( getFaceGivenLines( triangleMesh, vertexIndex, vertexIndexTable, vertexes ) ) + +def getCarving(fileName=''): + "Get the triangle mesh for the stl file." + if fileName == '': + return None + stlData = archive.getFileText(fileName, True, 'rb') + if stlData == '': + return None + triangleMesh = triangle_mesh.TriangleMesh() + vertexIndexTable = {} + numberOfVertexStrings = stlData.count('vertex') + requiredVertexStringsForText = max( 2, len( stlData ) / 8000 ) + if numberOfVertexStrings > requiredVertexStringsForText: + addFacesGivenText( stlData, triangleMesh, vertexIndexTable ) + else: +# A binary stl should never start with the word "solid". Because this error is common the file is been parsed as binary regardless. + addFacesGivenBinary( stlData, triangleMesh, vertexIndexTable ) + return triangleMesh + +def getFaceGivenLines( triangleMesh, vertexStartIndex, vertexIndexTable, vertexes ): + "Add face given line index and lines." + faceGivenLines = face.Face() + faceGivenLines.index = len( triangleMesh.faces ) + for vertexIndex in xrange( vertexStartIndex, vertexStartIndex + 3 ): + vertex = vertexes[vertexIndex] + vertexUniqueIndex = len( vertexIndexTable ) + if str(vertex) in vertexIndexTable: + vertexUniqueIndex = vertexIndexTable[ str(vertex) ] + else: + vertexIndexTable[ str(vertex) ] = vertexUniqueIndex + triangleMesh.vertexes.append(vertex) + faceGivenLines.vertexIndexes.append( vertexUniqueIndex ) + return faceGivenLines + +def getFloat(floatString): + "Get the float, replacing commas if necessary because an inferior program is using a comma instead of a point for the decimal point." + try: + return float(floatString) + except: + return float( floatString.replace(',', '.') ) + +def getFloatGivenBinary( byteIndex, stlData ): + "Get vertex given stl vertex line." + return unpack('f', stlData[ byteIndex : byteIndex + 4 ] )[0] + +def getVertexGivenBinary( byteIndex, stlData ): + "Get vertex given stl vertex line." + return Vector3( getFloatGivenBinary( byteIndex, stlData ), getFloatGivenBinary( byteIndex + 4, stlData ), getFloatGivenBinary( byteIndex + 8, stlData ) ) + +def getVertexGivenLine(line): + "Get vertex given stl vertex line." + splitLine = line.split() + return Vector3( getFloat(splitLine[1]), getFloat( splitLine[2] ), getFloat( splitLine[3] ) ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/svg.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/svg.py new file mode 100644 index 0000000..bfe9e0d --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/interpret_plugins/svg.py @@ -0,0 +1,109 @@ +""" +This page is in the table of contents. +The svg.py script is an import translator plugin to get a carving from an svg file. This script will read an svg file made by skeinforge or by inkscape. + +An example inkscape svg file is inkscape_star.svg in the models folder. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an svg file and returns the carving. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.svg_reader import SVGReader +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import svg_writer +from fabmetheus_utilities import xml_simple_writer +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCarving(fileName=''): + 'Get the triangle mesh for the gts file.' + carving = SVGCarving() + carving.parseSVG(fileName, archive.getFileText(fileName)) + return carving + + +class SVGCarving: + 'An svg carving.' + def __init__(self): + 'Add empty lists.' + self.layerHeight = 1.0 + self.maximumZ = - 987654321.0 + self.minimumZ = 987654321.0 + self.svgReader = SVGReader() + + def __repr__(self): + 'Get the string representation of this carving.' + return self.getCarvedSVG() + + def addXML(self, depth, output): + 'Add xml for this object.' + xml_simple_writer.addXMLFromObjects(depth, self.svgReader.loopLayers, output) + + def getCarveBoundaryLayers(self): + 'Get the boundary layers.' + return self.svgReader.loopLayers + + def getCarveCornerMaximum(self): + 'Get the corner maximum of the vertexes.' + return self.cornerMaximum + + def getCarveCornerMinimum(self): + 'Get the corner minimum of the vertexes.' + return self.cornerMinimum + + def getCarvedSVG(self): + 'Get the carved svg text.' + return svg_writer.getSVGByLoopLayers(True, self, self.svgReader.loopLayers) + + def getCarveLayerHeight(self): + 'Get the layer height.' + return self.layerHeight + + def getFabmetheusXML(self): + 'Return the fabmetheus XML.' + return None + + def getInterpretationSuffix(self): + 'Return the suffix for a carving.' + return 'svg' + + def parseSVG(self, fileName, svgText): + 'Parse SVG text and store the layers.' + if svgText == '': + return + self.fileName = fileName + self.svgReader.parseSVG(fileName, svgText) + self.layerHeight = euclidean.getFloatDefaultByDictionary( + self.layerHeight, self.svgReader.sliceDictionary, 'layerHeight') + self.cornerMaximum = Vector3(-987654321.0, -987654321.0, self.maximumZ) + self.cornerMinimum = Vector3(987654321.0, 987654321.0, self.minimumZ) + svg_writer.setSVGCarvingCorners( + self.cornerMaximum, self.cornerMinimum, self.layerHeight, self.svgReader.loopLayers) + + def setCarveImportRadius(self, importRadius): + 'Set the import radius.' + pass + + def setCarveIsCorrectMesh(self, isCorrectMesh): + 'Set the is correct mesh flag.' + pass + + def setCarveLayerHeight(self, layerHeight): + 'Set the layer height.' + self.layerHeight = layerHeight diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/prepare.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/prepare.py new file mode 100644 index 0000000..bac2757 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/prepare.py @@ -0,0 +1,86 @@ +""" +Prepare is a script to remove the generated files, run wikifier, and finally zip the package. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities.fabmetheus_tools import wikifier +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def prepareWikify(): + 'Remove generated files, then wikify the file comments.' + removeGeneratedFiles() + wikifier.main() + removeZip() + +def removeCSVFile(csvFilePath): + 'Remove csv file.' + if 'alterations' in csvFilePath and 'example_' not in csvFilePath: + os.remove(csvFilePath) + print('removeGeneratedFiles deleted ' + csvFilePath) + +def removeGcodeFile(gcodeFilePath): + 'Remove gcode file.' + if 'alterations' not in gcodeFilePath: + os.remove(gcodeFilePath) + print('removeGeneratedFiles deleted ' + gcodeFilePath) + return + if 'example_' not in gcodeFilePath: + os.remove(gcodeFilePath) + print('removeGeneratedFiles deleted ' + gcodeFilePath) + +def removeGeneratedFiles(): + 'Remove generated files.' + csvFilePaths = archive.getFilesWithFileTypesWithoutWordsRecursively(['csv']) + for csvFilePath in csvFilePaths: + removeCSVFile(csvFilePath) + gcodeFilePaths = archive.getFilesWithFileTypesWithoutWordsRecursively(['gcode']) + for gcodeFilePath in gcodeFilePaths: + removeGcodeFile(gcodeFilePath) + svgFilePaths = archive.getFilesWithFileTypesWithoutWordsRecursively(['svg']) + for svgFilePath in svgFilePaths: + removeSVGFile(svgFilePath) + xmlFilePaths = archive.getFilesWithFileTypesWithoutWordsRecursively(['xml']) + for xmlFilePath in xmlFilePaths: + removeXMLFile(xmlFilePath) + archive.removeBackupFilesByTypes(['gcode', 'svg', 'xml']) + +def removeSVGFile(svgFilePath): + 'Remove svg file.' + if archive.getEndsWithList(svgFilePath, ['_bottom.svg', '_carve.svg', '_chop.svg', '_cleave.svg', '_scale.svg', '_vectorwrite.svg']): + os.remove(svgFilePath) + print('removeGeneratedFiles deleted ' + svgFilePath) + +def removeXMLFile(xmlFilePath): + 'Remove xml file.' + if archive.getEndsWithList(xmlFilePath, ['_interpret.xml']): + os.remove(xmlFilePath) + print('removeGeneratedFiles deleted ' + xmlFilePath) + +def removeZip(): + 'Remove the zip file, then generate a new one.zip -r reprap_python_beanshell * -x \*.pyc \*~' + zipName = 'reprap_python_beanshell' + zipNameExtension = zipName + '.zip' + if zipNameExtension in os.listdir(os.getcwd()): + os.remove(zipNameExtension) + shellCommand = 'zip -r %s * -x \*.pyc \*~' % zipName + if os.system(shellCommand) != 0: + print('Failed to execute the following command in removeZip in prepare.') + print(shellCommand) + +def main(): + 'Run main function.' + prepareWikify() + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/wikifier.py b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/wikifier.py new file mode 100644 index 0000000..50e0233 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fabmetheus_tools/wikifier.py @@ -0,0 +1,215 @@ +""" +Wikifier is a script to add spaces to the pydoc files and move them to the documentation folder. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +import cStringIO +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalWikiLinkStart = '[', linkStartIndex, squareEndBracketIndex) + greaterThanIndexPlusOne = greaterThanIndex + 1 + closeATagIndex = line.find('', greaterThanIndexPlusOne, squareEndBracketIndex) + linkText = line[closeATagIndex + len('') + 1: squareEndBracketIndex] + linkLine = line[: linkStartIndex] + line[linkStartIndex + 1: greaterThanIndexPlusOne] + linkText + '' + line[squareEndBracketIndex + 1 :] + return linkLine + +def getNavigationHypertext(fileText, transferredFileNameIndex, transferredFileNames): + 'Get the hypertext help with navigation lines.' + helpTextEnd = fileText.find('

') + helpTextStart = fileText.find('

') + helpText = fileText[helpTextStart : helpTextEnd] + lines = archive.getTextLines(helpText) + headings = [] + headingLineTable = {} + for line in lines: + addToHeadings(headingLineTable, headings, line) + headingsToBeenAdded = True + output = cStringIO.StringIO() + for line in lines: + if line[: 2] == '==': + if headingsToBeenAdded: + output.write('
\n') + for heading in headings: + heading.addToOutput(output) + output.write('
\n') + headingsToBeenAdded = False + if line in headingLineTable: + line = headingLineTable[line] + if '<a href=' in line: + line = line.replace('<', '<').replace('>', '>') + while globalWikiLinkStart in line and ']' in line: + line = getLinkLine(line) + output.write(line + '\n') + helpText = output.getvalue() + previousFileName = 'contents.html' + previousIndex = transferredFileNameIndex - 1 + if previousIndex >= 0: + previousFileName = transferredFileNames[previousIndex] + previousLinkText = 'Previous' % previousFileName + nextLinkText = getNextLinkText(transferredFileNames, transferredFileNameIndex + 1) + navigationLine = getNavigationLine('Contents', previousLinkText, nextLinkText) + helpText = navigationLine + helpText + '
\n
\n' + navigationLine + '


\n' + return fileText[: helpTextStart] + helpText + fileText[helpTextEnd :] + +def getNavigationLine(contentsLinkText, previousLinkText, nextLinkText): + 'Get the wrapped pydoc hypertext help.' + return '

\n%s / %s / %s\n

\n' % (previousLinkText, nextLinkText, contentsLinkText) + +def getNextLinkText(hypertextFiles, nextIndex): + 'Get the next link text.' + nextFileName = 'contents.html' + if nextIndex < len(hypertextFiles): + nextFileName = hypertextFiles[nextIndex] + return 'Next' % nextFileName + +def getWrappedHypertext(fileText, hypertextFileIndex, hypertextFiles): + 'Get the wrapped pydoc hypertext help.' + helpTextEnd = fileText.find('

') + if helpTextEnd < 0: + print('Failed to find the helpTextEnd in getWrappedHypertext in docwrap.') + helpTextStart = fileText.find('

') + if helpTextStart < 0: + print('Failed to find the helpTextStart in getWrappedHypertext in docwrap.') + helpText = fileText[helpTextStart : helpTextEnd] + helpText = helpText.replace(' ', ' ') + return fileText[: helpTextStart] + helpText + fileText[helpTextEnd :] + +def readWriteDeleteHypertextHelp(documentDirectoryPath, hypertextFileIndex, hypertextFiles, transferredFileNames): + 'Read the pydoc hypertext help documents, write them in the documentation folder then delete the originals.' + fileName = os.path.basename(hypertextFiles[hypertextFileIndex]) + print('readWriteDeleteHypertextHelp ' + fileName) + filePath = os.path.join(documentDirectoryPath, fileName) + fileText = archive.getFileText(fileName) + fileText = getWrappedHypertext(fileText, hypertextFileIndex, hypertextFiles) + if fileText.find('This page is in the table of contents.') > - 1: + fileText = fileText.replace('This page is in the table of contents.', '') + transferredFileNames.append(fileName) + archive.writeFileText(filePath, fileText) + os.remove(fileName) + +def readWriteNavigationHelp(documentDirectoryPath, transferredFileNameIndex, transferredFileNames): + 'Read the hypertext help documents, and add the navigation lines to them.' + fileName = os.path.basename(transferredFileNames[transferredFileNameIndex]) + print('readWriteNavigationHelp ' + fileName) + filePath = os.path.join(documentDirectoryPath, fileName) + fileText = archive.getFileText(filePath) + fileText = getNavigationHypertext(fileText, transferredFileNameIndex, transferredFileNames) + archive.writeFileText(filePath, fileText) + +def removeFilesInDirectory(directoryPath): + 'Remove all the files in a directory.' + fileNames = os.listdir(directoryPath) + for fileName in fileNames: + filePath = os.path.join(directoryPath, fileName) + os.remove(filePath) + +def writeContentsFile(documentDirectoryPath, hypertextFiles): + 'Write the contents file.' + output = cStringIO.StringIO() + output.write('\n \n Contents\n \n \n') + navigationLine = getNavigationLine('Contents', 'Previous', getNextLinkText(hypertextFiles, 0)) + output.write(navigationLine) + for hypertextFile in hypertextFiles: + writeContentsLine(hypertextFile, output) + output.write(navigationLine) + output.write(' \n\n') + filePath = os.path.join( documentDirectoryPath, 'contents.html') + archive.writeFileText(filePath, output.getvalue()) + +def writeContentsLine(hypertextFile, output): + 'Write a line of the contents file.' + summarizedFileName = hypertextFile[: hypertextFile.rfind('.')] + numberOfDots = summarizedFileName.count('.') + prefixSpaces = '  ' * numberOfDots + if numberOfDots > 0: + summarizedFileName = summarizedFileName[summarizedFileName.rfind('.') + 1 :] + capitalizedSummarizedFileName = settings.getEachWordCapitalized(summarizedFileName) + output.write('%s%s
\n' % (prefixSpaces, hypertextFile, capitalizedSummarizedFileName)) + +def writeHypertext(): + 'Run pydoc, then read, write and delete each of the files.' + shellCommand = 'pydoc -w ./' + commandResult = os.system(shellCommand) + if commandResult != 0: + print('Failed to execute the following command in writeHypertext in docwrap.') + print(shellCommand) + hypertextFiles = archive.getFilesWithFileTypeWithoutWords('html') + if len( hypertextFiles ) <= 0: + print('Failed to find any help files in writeHypertext in docwrap.') + return + documentDirectoryPath = archive.getAbsoluteFolderPath( hypertextFiles[0], 'documentation') + removeFilesInDirectory(documentDirectoryPath) + sortedReplaceFiles = [] + for hypertextFile in hypertextFiles: + sortedReplaceFiles.append(hypertextFile.replace('.html', '. html')) + sortedReplaceFiles.sort() + hypertextFiles = [] + for sortedReplaceFile in sortedReplaceFiles: + hypertextFiles.append(sortedReplaceFile.replace('. html', '.html')) + transferredFileNames = [] + for hypertextFileIndex in xrange(len(hypertextFiles)): + readWriteDeleteHypertextHelp(documentDirectoryPath, hypertextFileIndex, hypertextFiles, transferredFileNames) + for transferredFileNameIndex in xrange(len(transferredFileNames)): + readWriteNavigationHelp(documentDirectoryPath, transferredFileNameIndex, transferredFileNames) + writeContentsFile(documentDirectoryPath, transferredFileNames) + print('%s files were wrapped.' % len(transferredFileNames)) + + +class Heading: + 'A class to hold the heading and subheadings.' + def __init__(self, depth=0): + 'Initialize.' + self.depth = depth + + def addToOutput(self, output): + 'Add to the output.' + line = '  ' * self.depth + '%s
\n' % (self.name, self.name) + output.write(line) + + def getFromLine(self, headingLineTable, line): + 'Get the heading from a line.' + heading = 'h%s' % (self.depth + 2) + nextLine = '\n


\n' + if self.depth > 0: + nextLine = '\n' + self.name = line.replace('=', '').replace('
', '') + name = self.name + headingLine = '<%s>%s%s' % (name, name, heading, name, heading, nextLine) + headingLineTable[line] = headingLine + return self + + +def main(): + 'Display the craft dialog.' + writeHypertext() + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/fonts/gentium_basic_regular.svg b/SkeinPyPy_NewUI/fabmetheus_utilities/fonts/gentium_basic_regular.svg new file mode 100644 index 0000000..418f023 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/fonts/gentium_basic_regular.svg @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/gcodec.py b/SkeinPyPy_NewUI/fabmetheus_utilities/gcodec.py new file mode 100644 index 0000000..b348674 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/gcodec.py @@ -0,0 +1,442 @@ +""" +Gcodec is a collection of utilities to decode and encode gcode. + +To run gcodec, install python 2.x on your machine, which is avaliable from http://www.python.org/download/ + +Then in the folder which gcodec is in, type 'python' in a shell to run the python interpreter. Finally type 'from gcodec import *' to import this program. + +Below is an example of gcodec use. This example is run in a terminal in the folder which contains gcodec and Screw Holder Bottom_export.gcode. + +>>> from gcodec import * +>>> getFileText('Screw Holder Bottom_export.gcode') +'G90\nG21\nM103\nM105\nM106\nM110 S60.0\nM111 S30.0\nM108 S210.0\nM104 S235.0\nG1 X0.37 Y-4.07 Z1.9 F60.0\nM101\n +.. +many lines of text +.. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +import cStringIO +import math +import os +import sys +import traceback + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addLineAndNewlineIfNecessary(line, output): + 'Add the line and if the line does not end with a newline add a newline.' + output.write(line) + if len(line) < 1: + return + if not line.endswith('\n'): + output.write('\n') + +def addLinesToCString(cString, lines): + 'Add lines which have something to cStringIO.' + for line in lines: + if line != '': + cString.write(line + '\n') + +def getArcDistance(relativeLocation, splitLine): + 'Get arc distance.' + halfPlaneLineDistance = 0.5 * abs(relativeLocation.dropAxis()) + radius = getDoubleFromCharacterSplitLine('R', splitLine) + if radius == None: + iFloat = getDoubleFromCharacterSplitLine('I', splitLine) + jFloat = getDoubleFromCharacterSplitLine('J', splitLine) + radius = abs(complex(iFloat, jFloat)) + angle = 0.0 + if radius > 0.0: + halfPlaneLineDistanceOverRadius = halfPlaneLineDistance / radius + if halfPlaneLineDistance < radius: + angle = 2.0 * math.asin(halfPlaneLineDistanceOverRadius) + else: + angle = math.pi * halfPlaneLineDistanceOverRadius + return abs(complex(angle * radius, relativeLocation.z)) + +def getDoubleAfterFirstLetter(word): + 'Get the double value of the word after the first letter.' + return float(word[1 :]) + +def getDoubleForLetter(letter, splitLine): + 'Get the double value of the word after the first occurence of the letter in the split line.' + return getDoubleAfterFirstLetter(splitLine[getIndexOfStartingWithSecond(letter, splitLine)]) + +def getDoubleFromCharacterSplitLine(character, splitLine): + 'Get the double value of the string after the first occurence of the character in the split line.' + indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine) + if indexOfCharacter < 0: + return None + floatString = splitLine[indexOfCharacter][1 :] + try: + return float(floatString) + except ValueError: + return None + +def getDoubleFromCharacterSplitLineValue(character, splitLine, value): + 'Get the double value of the string after the first occurence of the character in the split line, if it does not exist return the value.' + splitLineFloat = getDoubleFromCharacterSplitLine(character, splitLine) + if splitLineFloat == None: + return value + return splitLineFloat + +def getFeedRateMinute(feedRateMinute, splitLine): + 'Get the feed rate per minute if the split line has a feed rate.' + indexOfF = getIndexOfStartingWithSecond('F', splitLine) + if indexOfF > 0: + return getDoubleAfterFirstLetter( splitLine[indexOfF] ) + return feedRateMinute + +def getFirstWord(splitLine): + 'Get the first word of a split line.' + if len(splitLine) > 0: + return splitLine[0] + return '' + +def getFirstWordFromLine(line): + 'Get the first word of a line.' + return getFirstWord(line.split()) + +def getFirstWordIndexReverse(firstWord, lines, startIndex): + 'Parse gcode in reverse order until the first word if there is one, otherwise return -1.' + for lineIndex in xrange(len(lines) - 1, startIndex - 1, -1): + if firstWord == getFirstWord(getSplitLineBeforeBracketSemicolon(lines[lineIndex])): + return lineIndex + return -1 + +def getGcodeFileText(fileName, gcodeText): + 'Get the gcode text from a file if it the gcode text is empty and if the file is a gcode file.' + if gcodeText != '': + return gcodeText + if fileName.endswith('.gcode'): + return archive.getFileText(fileName) + return '' + +def getGcodeWithoutDuplication(duplicateWord, gcodeText): + 'Get gcode text without duplicate first words.' + lines = archive.getTextLines(gcodeText) + oldWrittenLine = None + output = cStringIO.StringIO() + for line in lines: + firstWord = getFirstWordFromLine(line) + if firstWord == duplicateWord: + if line != oldWrittenLine: + output.write(line + '\n') + oldWrittenLine = line + else: + if len(line) > 0: + output.write(line + '\n') + return output.getvalue() + +def getIndexOfStartingWithSecond(letter, splitLine): + 'Get index of the first occurence of the given letter in the split line, starting with the second word. Return - 1 if letter is not found' + for wordIndex in xrange( 1, len(splitLine) ): + word = splitLine[ wordIndex ] + firstLetter = word[0] + if firstLetter == letter: + return wordIndex + return - 1 + +def getLineWithValueString(character, line, splitLine, valueString): + 'Get the line with a valueString.' + roundedValueString = character + valueString + indexOfValue = getIndexOfStartingWithSecond(character, splitLine) + if indexOfValue == -1: + return line + ' ' + roundedValueString + word = splitLine[indexOfValue] + return line.replace(word, roundedValueString) + +def getLocationFromSplitLine(oldLocation, splitLine): + 'Get the location from the split line.' + if oldLocation == None: + oldLocation = Vector3() + return Vector3( + getDoubleFromCharacterSplitLineValue('X', splitLine, oldLocation.x), + getDoubleFromCharacterSplitLineValue('Y', splitLine, oldLocation.y), + getDoubleFromCharacterSplitLineValue('Z', splitLine, oldLocation.z)) + +def getRotationBySplitLine(splitLine): + 'Get the complex rotation from the split gcode line.' + return complex(splitLine[1].replace('(', '').replace(')', '')) + +def getSplitLineBeforeBracketSemicolon(line): + 'Get the split line before a bracket or semicolon.' + if ';' in line: + line = line[: line.find(';')] + bracketIndex = line.find('(') + if bracketIndex > 0: + return line[: bracketIndex].split() + return line.split() + +def getStringFromCharacterSplitLine(character, splitLine): + 'Get the string after the first occurence of the character in the split line.' + indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine) + if indexOfCharacter < 0: + return None + return splitLine[indexOfCharacter][1 :] + +def getTagBracketedLine(tagName, value): + 'Get line with a begin tag, value and end tag.' + return '(<%s> %s )' % (tagName, value, tagName) + +def getTagBracketedProcedure(procedure): + 'Get line with a begin procedure tag, procedure and end procedure tag.' + return getTagBracketedLine('procedureName', procedure) + +def isProcedureDone(gcodeText, procedure): + 'Determine if the procedure has been done on the gcode text.' + if gcodeText == '': + return False + extruderInitializationIndex = gcodeText.find('()') + if extruderInitializationIndex == -1: + metadataBeginIndex = gcodeText.find('') + metadataEndIndex = gcodeText.find('') + if metadataBeginIndex != -1 and metadataEndIndex != -1: + attributeString = "procedureName='%s'" % procedure + return gcodeText.find(attributeString, metadataBeginIndex, metadataEndIndex) != -1 + return False + return gcodeText.find(getTagBracketedProcedure(procedure), 0, extruderInitializationIndex) != -1 + +def isProcedureDoneOrFileIsEmpty(gcodeText, procedure): + 'Determine if the procedure has been done on the gcode text or the file is empty.' + if gcodeText == '': + return True + return isProcedureDone(gcodeText, procedure) + +def isThereAFirstWord(firstWord, lines, startIndex): + 'Parse gcode until the first word if there is one.' + for lineIndex in xrange(startIndex, len(lines)): + line = lines[lineIndex] + splitLine = getSplitLineBeforeBracketSemicolon(line) + if firstWord == getFirstWord(splitLine): + return True + return False + + +class BoundingRectangle: + 'A class to get the corners of a gcode text.' + def getFromGcodeLines(self, lines, radius): + 'Parse gcode text and get the minimum and maximum corners.' + self.cornerMaximum = complex(-987654321.0, -987654321.0) + self.cornerMinimum = complex(987654321.0, 987654321.0) + self.oldLocation = None + self.cornerRadius = complex(radius, radius) + for line in lines: + self.parseCorner(line) + return self + + def isPointInside(self, point): + 'Determine if the point is inside the bounding rectangle.' + return point.imag >= self.cornerMinimum.imag and point.imag <= self.cornerMaximum.imag and point.real >= self.cornerMinimum.real and point.real <= self.cornerMaximum.real + + def parseCorner(self, line): + 'Parse a gcode line and use the location to update the bounding corners.' + splitLine = getSplitLineBeforeBracketSemicolon(line) + firstWord = getFirstWord(splitLine) + if firstWord == '(': + locationComplex = getLocationFromSplitLine(None, splitLine).dropAxis() + self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex) + self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex) + elif firstWord == 'G1': + location = getLocationFromSplitLine(self.oldLocation, splitLine) + locationComplex = location.dropAxis() + self.cornerMaximum = euclidean.getMaximum(self.cornerMaximum, locationComplex + self.cornerRadius) + self.cornerMinimum = euclidean.getMinimum(self.cornerMinimum, locationComplex - self.cornerRadius) + self.oldLocation = location + + +class DistanceFeedRate: + 'A class to limit the z feed rate and round values.' + def __init__(self): + 'Initialize.' + self.isAlteration = False + self.decimalPlacesCarried = 3 + self.output = cStringIO.StringIO() + + def addFlowRateLine(self, flowRate): + 'Add a flow rate line.' + self.output.write('M108 S%s\n' % euclidean.getFourSignificantFigures(flowRate)) + + def addGcodeFromFeedRateThreadZ(self, feedRateMinute, thread, travelFeedRateMinute, z): + 'Add a thread to the output.' + if len(thread) > 0: + self.addGcodeMovementZWithFeedRate(travelFeedRateMinute, thread[0], z) + else: + print('zero length vertex positions array which was skipped over, this should never happen.') + if len(thread) < 2: + print('thread of only one point in addGcodeFromFeedRateThreadZ in gcodec, this should never happen.') + print(thread) + return + self.output.write('M101\n') # Turn extruder on. + for point in thread[1 :]: + self.addGcodeMovementZWithFeedRate(feedRateMinute, point, z) + self.output.write('M103\n') # Turn extruder off. + + def addGcodeFromLoop(self, loop, z): + 'Add the gcode loop.' + euclidean.addNestedRingBeginning(self, loop, z) + self.addPerimeterBlock(loop, z) + self.addLine('()') + self.addLine('()') + + def addGcodeFromThreadZ(self, thread, z): + 'Add a thread to the output.' + if len(thread) > 0: + self.addGcodeMovementZ(thread[0], z) + else: + print('zero length vertex positions array which was skipped over, this should never happen.') + if len(thread) < 2: + print('thread of only one point in addGcodeFromThreadZ in gcodec, this should never happen.') + print(thread) + return + self.output.write('M101\n') # Turn extruder on. + for point in thread[1 :]: + self.addGcodeMovementZ(point, z) + self.output.write('M103\n') # Turn extruder off. + + def addGcodeMovementZ(self, point, z): + 'Add a movement to the output.' + self.output.write(self.getLinearGcodeMovement(point, z) + '\n') + + def addGcodeMovementZWithFeedRate(self, feedRateMinute, point, z): + 'Add a movement to the output.' + self.output.write(self.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z) + '\n') + + def addGcodeMovementZWithFeedRateVector3(self, feedRateMinute, vector3): + 'Add a movement to the output by Vector3.' + xRounded = self.getRounded(vector3.x) + yRounded = self.getRounded(vector3.y) + self.output.write('G1 X%s Y%s Z%s F%s\n' % (xRounded, yRounded, self.getRounded(vector3.z), self.getRounded(feedRateMinute))) + + def addLine(self, line): + 'Add a line of text and a newline to the output.' + if len(line) > 0: + self.output.write(line + '\n') + + def addLineCheckAlteration(self, line): + 'Add a line of text and a newline to the output and check to see if it is an alteration line.' + firstWord = getFirstWord(getSplitLineBeforeBracketSemicolon(line)) + if firstWord == '()': + self.isAlteration = True + elif firstWord == '()': + self.isAlteration = False + if len(line) > 0: + self.output.write(line + '\n') + + def addLines(self, lines): + 'Add lines of text to the output.' + addLinesToCString(self.output, lines) + + def addLinesSetAbsoluteDistanceMode(self, lines): + 'Add lines of text to the output and ensure the absolute mode is set.' + if len(lines) < 1: + return + if len(lines[0]) < 1: + return + absoluteDistanceMode = True + self.addLine('()') + for line in lines: + splitLine = getSplitLineBeforeBracketSemicolon(line) + firstWord = getFirstWord(splitLine) + if firstWord == 'G90': + absoluteDistanceMode = True + elif firstWord == 'G91': + absoluteDistanceMode = False + self.addLine('()' + line) + if not absoluteDistanceMode: + self.addLine('G90') + self.addLine('()') + + def addParameter(self, firstWord, parameter): + 'Add the parameter.' + self.addLine(firstWord + ' S' + euclidean.getRoundedToThreePlaces(parameter)) + + def addPerimeterBlock(self, loop, z): + 'Add the edge gcode block for the loop.' + if len(loop) < 2: + return + if euclidean.isWiddershins(loop): # Indicate that an edge is beginning. + self.addLine('( outer )') + else: + self.addLine('( inner )') + self.addGcodeFromThreadZ(loop + [loop[0]], z) + self.addLine('()') # Indicate that an edge is beginning. + + def addTagBracketedLine(self, tagName, value): + 'Add a begin tag, value and end tag.' + self.addLine(getTagBracketedLine(tagName, value)) + + def addTagRoundedLine(self, tagName, value): + 'Add a begin tag, rounded value and end tag.' + self.addLine('(<%s> %s )' % (tagName, self.getRounded(value), tagName)) + + def addTagBracketedProcedure(self, procedure): + 'Add a begin procedure tag, procedure and end procedure tag.' + self.addLine(getTagBracketedProcedure(procedure)) + + def getBoundaryLine(self, location): + 'Get boundary gcode line.' + return '( X%s Y%s Z%s )' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z)) + + def getFirstWordMovement(self, firstWord, location): + 'Get the start of the arc line.' + return '%s X%s Y%s Z%s' % (firstWord, self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z)) + + def getInfillBoundaryLine(self, location): + 'Get infill boundary gcode line.' + return '( X%s Y%s Z%s )' % (self.getRounded(location.x), self.getRounded(location.y), self.getRounded(location.z)) + + def getIsAlteration(self, line): + 'Determine if it is an alteration.' + if self.isAlteration: + self.addLineCheckAlteration(line) + return True + return False + + def getLinearGcodeMovement(self, point, z): + 'Get a linear gcode movement.' + return 'G1 X%s Y%s Z%s' % (self.getRounded(point.real), self.getRounded(point.imag), self.getRounded(z)) + + def getLinearGcodeMovementWithFeedRate(self, feedRateMinute, point, z): + 'Get a z limited gcode movement.' + linearGcodeMovement = self.getLinearGcodeMovement(point, z) + if feedRateMinute == None: + return linearGcodeMovement + return linearGcodeMovement + ' F' + self.getRounded(feedRateMinute) + + def getLineWithFeedRate(self, feedRateMinute, line, splitLine): + 'Get the line with a feed rate.' + return getLineWithValueString('F', line, splitLine, self.getRounded(feedRateMinute)) + + def getLineWithX(self, line, splitLine, x): + 'Get the line with an x.' + return getLineWithValueString('X', line, splitLine, self.getRounded(x)) + + def getLineWithY(self, line, splitLine, y): + 'Get the line with a y.' + return getLineWithValueString('Y', line, splitLine, self.getRounded(y)) + + def getLineWithZ(self, line, splitLine, z): + 'Get the line with a z.' + return getLineWithValueString('Z', line, splitLine, self.getRounded(z)) + + def getRounded(self, number): + 'Get number rounded to the number of carried decimal places as a string.' + return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number) + + def parseSplitLine(self, firstWord, splitLine): + 'Parse gcode split line and store the parameters.' + if firstWord == '(': + self.decimalPlacesCarried = int(splitLine[1]) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/__init__.py new file mode 100644 index 0000000..3345e96 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/__init__.py @@ -0,0 +1,12 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 2 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/__init__.py new file mode 100644 index 0000000..cefa3e7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/__init__.py @@ -0,0 +1,12 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_drill.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_drill.py new file mode 100644 index 0000000..72fb9dd --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_drill.py @@ -0,0 +1,59 @@ +""" +Drill negative solid. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.creation import teardrop +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = DrillDerivation(elementNode) + negatives = [] + teardrop.addNegativesByRadius(elementNode, derivation.end, negatives, derivation.radius, derivation.start) + return solid.getGeometryOutputByManipulation(elementNode, negatives[0]) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['radius', 'start', 'end'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return DrillDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(None, elementNode)) + + +class DrillDerivation: + "Class to hold drill variables." + def __init__(self, elementNode): + 'Set defaults.' + self.elementNode = elementNode + self.end = evaluate.getVector3ByPrefix(Vector3(0.0, 0.0, 1.0), elementNode, 'end') + self.start = evaluate.getVector3ByPrefix(Vector3(), elementNode, 'start') + self.radius = lineation.getFloatByPrefixBeginEnd(elementNode, 'radius', 'diameter', 1.0) + size = evaluate.getEvaluatedFloat(None, elementNode, 'size') + if size != None: + self.radius = 0.5 * size diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_svg.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_svg.py new file mode 100644 index 0000000..9904d56 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/_svg.py @@ -0,0 +1,60 @@ +""" +Svg reader. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import svg_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = SVGDerivation(elementNode) + return getGeometryOutputBySVGReader(elementNode, derivation.svgReader) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + derivation = SVGDerivation() + derivation.svgReader.parseSVG('', arguments[0]) + return getGeometryOutput(derivation, elementNode) + +def getGeometryOutputBySVGReader(elementNode, svgReader): + "Get vector3 vertexes from svgReader." + geometryOutput = [] + for loopLayer in svgReader.loopLayers: + for loop in loopLayer.loops: + vector3Path = euclidean.getVector3Path(loop, loopLayer.z) + sideLoop = lineation.SideLoop(vector3Path) + sideLoop.rotate(elementNode) + geometryOutput += lineation.getGeometryOutputByManipulation(elementNode, sideLoop) + return geometryOutput + +def getNewDerivation(elementNode): + 'Get new derivation.' + return SVGDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class SVGDerivation: + "Class to hold svg variables." + def __init__(self, elementNode): + 'Set defaults.' + self.svgReader = svg_reader.SVGReader() + self.svgReader.parseSVGByElementNode(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/circle.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/circle.py new file mode 100644 index 0000000..15651d7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/circle.py @@ -0,0 +1,76 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = CircleDerivation(elementNode) + angleTotal = math.radians(derivation.start) + loop = [] + sidesCeiling = int(math.ceil(abs(derivation.sides) * derivation.extent / 360.0)) + sideAngle = math.radians(derivation.extent) / sidesCeiling + if derivation.sides < 0.0: + sideAngle = -sideAngle + spiral = lineation.Spiral(derivation.spiral, 0.5 * sideAngle / math.pi) + for side in xrange(sidesCeiling + 1): + unitPolar = euclidean.getWiddershinsUnitPolar(angleTotal) + x = unitPolar.real * derivation.radiusArealized.real + y = unitPolar.imag * derivation.radiusArealized.imag + vertex = spiral.getSpiralPoint(unitPolar, Vector3(x, y)) + angleTotal += sideAngle + loop.append(vertex) + radiusMaximum = 0.000001 * max(derivation.radiusArealized.real, derivation.radiusArealized.imag) + loop = euclidean.getLoopWithoutCloseEnds(radiusMaximum, loop) + lineation.setClosedAttribute(elementNode, derivation.revolutions) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop, sideAngle)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['radius', 'start', 'end', 'revolutions'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return CircleDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class CircleDerivation: + "Class to hold circle variables." + def __init__(self, elementNode): + 'Set defaults.' + self.radius = lineation.getRadiusComplex(elementNode, complex(1.0, 1.0)) + self.sides = evaluate.getEvaluatedFloat(None, elementNode, 'sides') + if self.sides == None: + radiusMaximum = max(self.radius.real, self.radius.imag) + self.sides = evaluate.getSidesMinimumThreeBasedOnPrecisionSides(elementNode, radiusMaximum) + self.radiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNode, self.radius, self.sides) + self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start') + end = evaluate.getEvaluatedFloat(360.0, elementNode, 'end') + self.revolutions = evaluate.getEvaluatedFloat(1.0, elementNode, 'revolutions') + self.extent = evaluate.getEvaluatedFloat(end - self.start, elementNode, 'extent') + self.extent += 360.0 * (self.revolutions - 1.0) + self.spiral = evaluate.getVector3ByPrefix(None, elementNode, 'spiral') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/concatenate.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/concatenate.py new file mode 100644 index 0000000..62e7e6d --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/concatenate.py @@ -0,0 +1,54 @@ +""" +Boolean geometry concatenation. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + 'Get triangle mesh from attribute dictionary.' + if derivation == None: + derivation = ConcatenateDerivation(elementNode) + concatenatedList = euclidean.getConcatenatedList(derivation.target)[:] + if len(concatenatedList) == 0: + print('Warning, in concatenate there are no paths.') + print(elementNode.attributes) + return None + if 'closed' not in elementNode.attributes: + elementNode.attributes['closed'] = 'true' + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(concatenatedList)) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get triangle mesh from attribute dictionary by arguments.' + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return ConcatenateDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class ConcatenateDerivation: + 'Class to hold concatenate variables.' + def __init__(self, elementNode): + 'Initialize.' + self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/extrude.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/extrude.py new file mode 100644 index 0000000..665966f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/extrude.py @@ -0,0 +1,441 @@ +""" +Boolean geometry extrusion. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addLoop(derivation, endMultiplier, loopLists, path, portionDirectionIndex, portionDirections, vertexes): + 'Add an indexed loop to the vertexes.' + portionDirection = portionDirections[ portionDirectionIndex ] + if portionDirection.directionReversed == True: + loopLists.append([]) + loops = loopLists[-1] + interpolationOffset = derivation.interpolationDictionary['offset'] + offset = interpolationOffset.getVector3ByPortion( portionDirection ) + if endMultiplier != None: + if portionDirectionIndex == 0: + setOffsetByMultiplier( interpolationOffset.path[1], interpolationOffset.path[0], endMultiplier, offset ) + elif portionDirectionIndex == len( portionDirections ) - 1: + setOffsetByMultiplier( interpolationOffset.path[-2], interpolationOffset.path[-1], endMultiplier, offset ) + scale = derivation.interpolationDictionary['scale'].getComplexByPortion( portionDirection ) + twist = derivation.interpolationDictionary['twist'].getYByPortion( portionDirection ) + projectiveSpace = euclidean.ProjectiveSpace() + if derivation.tiltTop == None: + tilt = derivation.interpolationDictionary['tilt'].getComplexByPortion( portionDirection ) + projectiveSpace = projectiveSpace.getByTilt( tilt ) + else: + normals = getNormals( interpolationOffset, offset, portionDirection ) + normalFirst = normals[0] + normalAverage = getNormalAverage(normals) + if derivation.tiltFollow and derivation.oldProjectiveSpace != None: + projectiveSpace = derivation.oldProjectiveSpace.getNextSpace( normalAverage ) + else: + projectiveSpace = projectiveSpace.getByBasisZTop( normalAverage, derivation.tiltTop ) + derivation.oldProjectiveSpace = projectiveSpace + projectiveSpace.unbuckle( derivation.maximumUnbuckling, normalFirst ) + projectiveSpace = projectiveSpace.getSpaceByXYScaleAngle( twist, scale ) + loop = [] + if ( abs( projectiveSpace.basisX ) + abs( projectiveSpace.basisY ) ) < 0.0001: + vector3Index = Vector3Index(len(vertexes)) + addOffsetAddToLists( loop, offset, vector3Index, vertexes ) + loops.append(loop) + return + for point in path: + vector3Index = Vector3Index(len(vertexes)) + projectedVertex = projectiveSpace.getVector3ByPoint(point) + vector3Index.setToVector3( projectedVertex ) + addOffsetAddToLists( loop, offset, vector3Index, vertexes ) + loops.append(loop) + +def addNegatives(derivation, negatives, paths): + 'Add pillars output to negatives.' + portionDirections = getSpacedPortionDirections(derivation.interpolationDictionary) + for path in paths: + loopLists = getLoopListsByPath(derivation, 1.000001, path, portionDirections) + geometryOutput = triangle_mesh.getPillarsOutput(loopLists) + negatives.append(geometryOutput) + +def addNegativesPositives(derivation, negatives, paths, positives): + 'Add pillars output to negatives and positives.' + portionDirections = getSpacedPortionDirections(derivation.interpolationDictionary) + for path in paths: + endMultiplier = None + if not euclidean.getIsWiddershinsByVector3(path): + endMultiplier = 1.000001 + loopLists = getLoopListsByPath(derivation, endMultiplier, path, portionDirections) + geometryOutput = triangle_mesh.getPillarsOutput(loopLists) + if endMultiplier == None: + positives.append(geometryOutput) + else: + negatives.append(geometryOutput) + +def addOffsetAddToLists(loop, offset, vector3Index, vertexes): + 'Add an indexed loop to the vertexes.' + vector3Index += offset + loop.append(vector3Index) + vertexes.append(vector3Index) + +def addPositives(derivation, paths, positives): + 'Add pillars output to positives.' + portionDirections = getSpacedPortionDirections(derivation.interpolationDictionary) + for path in paths: + loopLists = getLoopListsByPath(derivation, None, path, portionDirections) + geometryOutput = triangle_mesh.getPillarsOutput(loopLists) + positives.append(geometryOutput) + +def addSpacedPortionDirection( portionDirection, spacedPortionDirections ): + 'Add spaced portion directions.' + lastSpacedPortionDirection = spacedPortionDirections[-1] + if portionDirection.portion - lastSpacedPortionDirection.portion > 0.003: + spacedPortionDirections.append( portionDirection ) + return + if portionDirection.directionReversed > lastSpacedPortionDirection.directionReversed: + spacedPortionDirections.append( portionDirection ) + +def addTwistPortions( interpolationTwist, remainderPortionDirection, twistPrecision ): + 'Add twist portions.' + lastPortionDirection = interpolationTwist.portionDirections[-1] + if remainderPortionDirection.portion == lastPortionDirection.portion: + return + lastTwist = interpolationTwist.getYByPortion( lastPortionDirection ) + remainderTwist = interpolationTwist.getYByPortion( remainderPortionDirection ) + twistSegments = int( math.floor( abs( remainderTwist - lastTwist ) / twistPrecision ) ) + if twistSegments < 1: + return + portionDifference = remainderPortionDirection.portion - lastPortionDirection.portion + twistSegmentsPlusOne = float( twistSegments + 1 ) + for twistSegment in xrange( twistSegments ): + additionalPortion = portionDifference * float( twistSegment + 1 ) / twistSegmentsPlusOne + portionDirection = PortionDirection( lastPortionDirection.portion + additionalPortion ) + interpolationTwist.portionDirections.append( portionDirection ) + +def comparePortionDirection( portionDirection, otherPortionDirection ): + 'Comparison in order to sort portion directions in ascending order of portion then direction.' + if portionDirection.portion > otherPortionDirection.portion: + return 1 + if portionDirection.portion < otherPortionDirection.portion: + return - 1 + if portionDirection.directionReversed < otherPortionDirection.directionReversed: + return - 1 + return portionDirection.directionReversed > otherPortionDirection.directionReversed + +def getGeometryOutput(derivation, elementNode): + 'Get triangle mesh from attribute dictionary.' + if derivation == None: + derivation = ExtrudeDerivation(elementNode) + if len(euclidean.getConcatenatedList(derivation.target)) == 0: + print('Warning, in extrude there are no paths.') + print(elementNode.attributes) + return None + return getGeometryOutputByLoops(derivation, derivation.target) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get triangle mesh from attribute dictionary by arguments.' + return getGeometryOutput(None, elementNode) + +def getGeometryOutputByLoops(derivation, loops): + 'Get geometry output by sorted, nested loops.' + loops.sort(key=euclidean.getAreaVector3LoopAbsolute, reverse=True) + complexLoops = euclidean.getComplexPaths(loops) + nestedRings = [] + for loopIndex, loop in enumerate(loops): + complexLoop = complexLoops[loopIndex] + leftPoint = euclidean.getLeftPoint(complexLoop) + isInFilledRegion = euclidean.getIsInFilledRegion(complexLoops[: loopIndex] + complexLoops[loopIndex + 1 :], leftPoint) + if isInFilledRegion == euclidean.isWiddershins(complexLoop): + loop.reverse() + nestedRing = euclidean.NestedRing() + nestedRing.boundary = complexLoop + nestedRing.vector3Loop = loop + nestedRings.append(nestedRing) + nestedRings = euclidean.getOrderedNestedRings(nestedRings) + nestedRings = euclidean.getFlattenedNestedRings(nestedRings) + portionDirections = getSpacedPortionDirections(derivation.interpolationDictionary) + if len(nestedRings) < 1: + return {} + if len(nestedRings) == 1: + geometryOutput = getGeometryOutputByNestedRing(derivation, nestedRings[0], portionDirections) + return solid.getGeometryOutputByManipulation(derivation.elementNode, geometryOutput) + shapes = [] + for nestedRing in nestedRings: + shapes.append(getGeometryOutputByNestedRing(derivation, nestedRing, portionDirections)) + return solid.getGeometryOutputByManipulation(derivation.elementNode, {'union' : {'shapes' : shapes}}) + +def getGeometryOutputByNegativesPositives(elementNode, negatives, positives): + 'Get triangle mesh from elementNode, negatives and positives.' + positiveOutput = triangle_mesh.getUnifiedOutput(positives) + if len(negatives) < 1: + return solid.getGeometryOutputByManipulation(elementNode, positiveOutput) + if len(positives) < 1: + negativeOutput = triangle_mesh.getUnifiedOutput(negatives) + return solid.getGeometryOutputByManipulation(elementNode, negativeOutput) + return solid.getGeometryOutputByManipulation(elementNode, {'difference' : {'shapes' : [positiveOutput] + negatives}}) + +def getGeometryOutputByNestedRing(derivation, nestedRing, portionDirections): + 'Get geometry output by sorted, nested loops.' + loopLists = getLoopListsByPath(derivation, None, nestedRing.vector3Loop, portionDirections) + outsideOutput = triangle_mesh.getPillarsOutput(loopLists) + if len(nestedRing.innerNestedRings) < 1: + return outsideOutput + shapes = [outsideOutput] + for nestedRing.innerNestedRing in nestedRing.innerNestedRings: + loopLists = getLoopListsByPath(derivation, 1.000001, nestedRing.innerNestedRing.vector3Loop, portionDirections) + shapes.append(triangle_mesh.getPillarsOutput(loopLists)) + return {'difference' : {'shapes' : shapes}} + +def getLoopListsByPath(derivation, endMultiplier, path, portionDirections): + 'Get loop lists from path.' + vertexes = [] + loopLists = [[]] + derivation.oldProjectiveSpace = None + for portionDirectionIndex in xrange(len(portionDirections)): + addLoop(derivation, endMultiplier, loopLists, path, portionDirectionIndex, portionDirections, vertexes) + return loopLists + +def getNewDerivation(elementNode): + 'Get new derivation.' + return ExtrudeDerivation(elementNode) + +def getNormalAverage(normals): + 'Get normal.' + if len(normals) < 2: + return normals[0] + return (normals[0] + normals[1]).getNormalized() + +def getNormals( interpolationOffset, offset, portionDirection ): + 'Get normals.' + normals = [] + portionFrom = portionDirection.portion - 0.0001 + portionTo = portionDirection.portion + 0.0001 + if portionFrom >= 0.0: + normals.append( ( offset - interpolationOffset.getVector3ByPortion( PortionDirection( portionFrom ) ) ).getNormalized() ) + if portionTo <= 1.0: + normals.append( ( interpolationOffset.getVector3ByPortion( PortionDirection( portionTo ) ) - offset ).getNormalized() ) + return normals + +def getSpacedPortionDirections( interpolationDictionary ): + 'Get sorted portion directions.' + portionDirections = [] + for interpolationDictionaryValue in interpolationDictionary.values(): + portionDirections += interpolationDictionaryValue.portionDirections + portionDirections.sort( comparePortionDirection ) + if len( portionDirections ) < 1: + return [] + spacedPortionDirections = [ portionDirections[0] ] + for portionDirection in portionDirections[1 :]: + addSpacedPortionDirection( portionDirection, spacedPortionDirections ) + return spacedPortionDirections + +def insertTwistPortions(derivation, elementNode): + 'Insert twist portions and radian the twist.' + interpolationDictionary = derivation.interpolationDictionary + interpolationTwist = Interpolation().getByPrefixX(elementNode, derivation.twistPathDefault, 'twist') + interpolationDictionary['twist'] = interpolationTwist + for point in interpolationTwist.path: + point.y = math.radians(point.y) + remainderPortionDirections = interpolationTwist.portionDirections[1 :] + interpolationTwist.portionDirections = [interpolationTwist.portionDirections[0]] + if elementNode != None: + twistPrecision = setting.getTwistPrecisionRadians(elementNode) + for remainderPortionDirection in remainderPortionDirections: + addTwistPortions(interpolationTwist, remainderPortionDirection, twistPrecision) + interpolationTwist.portionDirections.append(remainderPortionDirection) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(None, elementNode)) + +def setElementNodeToEndStart(elementNode, end, start): + 'Set elementNode attribute dictionary to a tilt following path from the start to end.' + elementNode.attributes['path'] = [start, end] + elementNode.attributes['tiltFollow'] = 'true' + elementNode.attributes['tiltTop'] = Vector3(0.0, 0.0, 1.0) + +def setOffsetByMultiplier(begin, end, multiplier, offset): + 'Set the offset by the multiplier.' + segment = end - begin + delta = segment * multiplier - segment + offset.setToVector3(offset + delta) + + +class ExtrudeDerivation: + 'Class to hold extrude variables.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + self.interpolationDictionary = {} + self.tiltFollow = evaluate.getEvaluatedBoolean(True, elementNode, 'tiltFollow') + self.tiltTop = evaluate.getVector3ByPrefix(None, elementNode, 'tiltTop') + self.maximumUnbuckling = evaluate.getEvaluatedFloat(5.0, elementNode, 'maximumUnbuckling') + scalePathDefault = [Vector3(1.0, 1.0, 0.0), Vector3(1.0, 1.0, 1.0)] + self.interpolationDictionary['scale'] = Interpolation().getByPrefixZ(elementNode, scalePathDefault, 'scale') + self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target') + if self.tiltTop == None: + offsetPathDefault = [Vector3(), Vector3(0.0, 0.0, 1.0)] + self.interpolationDictionary['offset'] = Interpolation().getByPrefixZ(elementNode, offsetPathDefault, '') + tiltPathDefault = [Vector3(), Vector3(0.0, 0.0, 1.0)] + self.interpolationDictionary['tilt'] = Interpolation().getByPrefixZ(elementNode, tiltPathDefault, 'tilt') + for point in self.interpolationDictionary['tilt'].path: + point.x = math.radians(point.x) + point.y = math.radians(point.y) + else: + offsetAlongDefault = [Vector3(), Vector3(1.0, 0.0, 0.0)] + self.interpolationDictionary['offset'] = Interpolation().getByPrefixAlong(elementNode, offsetAlongDefault, '') + self.twist = evaluate.getEvaluatedFloat(0.0, elementNode, 'twist') + self.twistPathDefault = [Vector3(), Vector3(1.0, self.twist) ] + insertTwistPortions(self, elementNode) + + +class Interpolation: + 'Class to interpolate a path.' + def __init__(self): + 'Set index.' + self.interpolationIndex = 0 + + def __repr__(self): + 'Get the string representation of this Interpolation.' + return str(self.__dict__) + + def getByDistances(self): + 'Get by distances.' + beginDistance = self.distances[0] + self.interpolationLength = self.distances[-1] - beginDistance + self.close = abs(0.000001 * self.interpolationLength) + self.portionDirections = [] + oldDistance = -self.interpolationLength # so the difference should not be close + for distance in self.distances: + deltaDistance = distance - beginDistance + portionDirection = PortionDirection(deltaDistance / self.interpolationLength) + if abs(deltaDistance - oldDistance) < self.close: + portionDirection.directionReversed = True + self.portionDirections.append(portionDirection) + oldDistance = deltaDistance + return self + + def getByPrefixAlong(self, elementNode, path, prefix): + 'Get interpolation from prefix and xml element along the path.' + if len(path) < 2: + print('Warning, path is too small in evaluate in Interpolation.') + return + if elementNode == None: + self.path = path + else: + self.path = evaluate.getTransformedPathByPrefix(elementNode, path, prefix) + self.distances = [0.0] + previousPoint = self.path[0] + for point in self.path[1 :]: + distanceDifference = abs(point - previousPoint) + self.distances.append(self.distances[-1] + distanceDifference) + previousPoint = point + return self.getByDistances() + + def getByPrefixX(self, elementNode, path, prefix): + 'Get interpolation from prefix and xml element in the z direction.' + if len(path) < 2: + print('Warning, path is too small in evaluate in Interpolation.') + return + if elementNode == None: + self.path = path + else: + self.path = evaluate.getTransformedPathByPrefix(elementNode, path, prefix) + self.distances = [] + for point in self.path: + self.distances.append(point.x) + return self.getByDistances() + + def getByPrefixZ(self, elementNode, path, prefix): + 'Get interpolation from prefix and xml element in the z direction.' + if len(path) < 2: + print('Warning, path is too small in evaluate in Interpolation.') + return + if elementNode == None: + self.path = path + else: + self.path = evaluate.getTransformedPathByPrefix(elementNode, path, prefix) + self.distances = [] + for point in self.path: + self.distances.append(point.z) + return self.getByDistances() + + def getComparison( self, first, second ): + 'Compare the first with the second.' + if abs( second - first ) < self.close: + return 0 + if second > first: + return 1 + return - 1 + + def getComplexByPortion( self, portionDirection ): + 'Get complex from z portion.' + self.setInterpolationIndexFromTo( portionDirection ) + return self.oneMinusInnerPortion * self.startVertex.dropAxis() + self.innerPortion * self.endVertex.dropAxis() + + def getInnerPortion(self): + 'Get inner x portion.' + fromDistance = self.distances[ self.interpolationIndex ] + innerLength = self.distances[ self.interpolationIndex + 1 ] - fromDistance + if abs( innerLength ) == 0.0: + return 0.0 + return ( self.absolutePortion - fromDistance ) / innerLength + + def getVector3ByPortion( self, portionDirection ): + 'Get vector3 from z portion.' + self.setInterpolationIndexFromTo( portionDirection ) + return self.oneMinusInnerPortion * self.startVertex + self.innerPortion * self.endVertex + + def getYByPortion( self, portionDirection ): + 'Get y from x portion.' + self.setInterpolationIndexFromTo( portionDirection ) + return self.oneMinusInnerPortion * self.startVertex.y + self.innerPortion * self.endVertex.y + + def setInterpolationIndex( self, portionDirection ): + 'Set the interpolation index.' + self.absolutePortion = self.distances[0] + self.interpolationLength * portionDirection.portion + interpolationIndexes = range( 0, len( self.distances ) - 1 ) + if portionDirection.directionReversed: + interpolationIndexes.reverse() + for self.interpolationIndex in interpolationIndexes: + begin = self.distances[ self.interpolationIndex ] + end = self.distances[ self.interpolationIndex + 1 ] + if self.getComparison( begin, self.absolutePortion ) != self.getComparison( end, self.absolutePortion ): + return + + def setInterpolationIndexFromTo( self, portionDirection ): + 'Set the interpolation index, the start vertex and the end vertex.' + self.setInterpolationIndex( portionDirection ) + self.innerPortion = self.getInnerPortion() + self.oneMinusInnerPortion = 1.0 - self.innerPortion + self.startVertex = self.path[ self.interpolationIndex ] + self.endVertex = self.path[ self.interpolationIndex + 1 ] + + +class PortionDirection: + 'Class to hold a portion and direction.' + def __init__( self, portion ): + 'Initialize.' + self.directionReversed = False + self.portion = portion + + def __repr__(self): + 'Get the string representation of this PortionDirection.' + return '%s: %s' % ( self.portion, self.directionReversed ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/gear.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/gear.py new file mode 100644 index 0000000..67a4ff6 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/gear.py @@ -0,0 +1,1152 @@ +""" +This page is in the table of contents. +The gear script can generate a spur gear couple, a bevel gear couple, a ring gear couple and a rack & pinion couple. + +A helix pattern can be added to each gear type. All the gear types have a clearance and all the teeth can be beveled. A keyway, shaft and lightening holes can be added to all the round gears, and rack holes can be added to the rack. The script can output solid gears or only the gear profiles. Both gears of the couple can be generated or just one. + +The couple has a pinion gear and a complement. + +==Examples== +The link text includes the distinguishing parameters. Each svg page was generated from an xml page of the same root name using carve. For example, gear.svg was generated by clicking 'Carve' on the carve tool panel and choosing gear.xml in the file chooser. + +Each generated svg file has the xml fabmetheus element without comments towards the end of the file. To see it, open the svg file in a text editor and search for 'fabmetheus' If you copy that into a new text document, add the line '' at the beginning and then give it a file name with the extension '.xml', you could then generate another svg file using carve. + +===Bevel=== +Bevel gear couple. + +gear operatingAngle=90 + +===Collar=== +Spur gear couple and each gear has a collar. + +gear complementCollarLengthOverFaceWidth='1' pinionCollarLengthOverFaceWidth='1' shaftRadius='5' + +===Gear=== +Default spur gear with no parameters. + +gear + +===Keyway=== +Spur gear couple and each gear has a collar and defined keyway. + +gear complementCollarLengthOverFaceWidth='1' keywayRadius='2' pinionCollarLengthOverFaceWidth='1' shaftRadius='5' + +===Rack=== +Rack and pinion couple. + +gear teethComplement='0' + +===Rack Hole=== +Rack and pinion couple, with holes in the rack. + +gear rackHoleRadiusOverWidth='0.2' rackWidthOverFaceWidth='2' teethComplement='0' + +===Ring=== +Pinion and ring gear. + +gear teethComplement='-23' + +===Shaft=== +Spur gear couple and each gear has a square shaft hole. + +gear shaftRadius='5' + +===Shaft Top=== +Spur gear couple and each gear has a round shaft hole, truncated on top. + +gear shaftRadius='5' shaftSides='13' shaftDepthTop='2' + +===Spur Helix=== +Spur gear couple with the gear teeth following a helix path. + +gear helixAngle='45' + +===Spur Herringbone=== +Spur gear couple with the gear teeth following a herringbone path. + +gear helixAngle='45' helixType='herringbone' + +===Spur Parabolic=== +Spur gear couple with the gear teeth following a parabolic path. + +gear helixAngle='45' helixType='parabolic' + +===Spur Profile=== +Spur gear couple profile. Since this is just a horizontal path, it can not be sliced, so the path is then extruded to create a solid which can be sliced and viewed. + +gear id='spurProfile' faceWidth='0' | extrude target='=document.getElementByID(spurProfile) + +==Parameters== +===Center Distance=== +Default is such that the pitch radius works out to twenty. + +Defines the distance between the gear centers. + +===Clearance Couplet=== +====Clearance Over Wavelength==== +Default is 0.1. + +Defines the ratio of the clearance over the wavelength of the gear profile. The wavelength is the arc distance between the gear teeth. + +====Clearance==== +Default is the 'Clearance Over Wavelength' times the wavelength. + +Defines the clearance between the gear tooth and the other gear of the couple. If the clearance is zero, the outside of the gear tooth will touch the other gear. If the clearance is too high, the gear teeth will be long and weak. + +===Collar Addendum Couplet=== +====Collar Addendum Over Radius==== +Default is one. + +Defines the ratio of the collar addendum over the shaft radius. + +====Collar Addendum==== +Default is the 'Collar Addendum Over Radius' times the shaft radius. + +Defines the collar addendum. + +===Complement Collar Length Couplet=== +====Complement Collar Length Over Face Width==== +Default is zero. + +Defines the ratio of the complement collar length over the face width. + +====Complement Collar Length==== +Default is the 'Complement Collar Length Over Face Width' times the face width. + +Defines the complement collar length. If the complement collar length is zero, there will not be a collar on the complement gear. + +===Creation Type=== +Default is 'both'. + +====Both==== +When selected, the pinion and complement will be generated. + +====Complement==== +When selected, only the complement gear or rack will be generated. + +====Pinion==== +When selected, only the pinion will be generated. + +===Face Width=== +Default is ten. + +Defines the face width. + +===Gear Hole Paths=== +Default is empty. + +Defines the centers of the gear holes. If the gear hole paths parameter is the default empty, then the centers of the gear holes will be generated from other parameters. + +===Helix Angle=== +Default is zero. + +===Helix Path=== +Default is empty. + +Defines the helix path of the gear teeth. If the helix path is the default empty, then the helix will be generated from the helix angle and helix type. + +===Helix Type=== +Default is 'basic'. + +====Basic==== +When selected, the helix will be basic. + +====Herringbone==== +When selected, the helix will have a herringbone pattern. + +====Parabolic==== +When selected, the helix will have a parabolic pattern. + +===Keyway Radius Couplet=== +====Keyway Radius Over Radius==== +Default is half. + +Defines the ratio of the keyway radius over the shaft radius. + +====Keyway Radius==== +Default is the 'Keyway Radius Over Radius' times the shaft radius. + +Defines the keyway radius. If the keyway radius is zero, there will not be a keyway on the collar. + +===Lightening Hole Margin Couplet=== +====Lightening Hole Margin Over Rim Dedendum==== +Default is one. + +Defines the ratio of the lightening hole margin over the rim dedendum. + +====Lightening Hole Margin==== +Default is the 'Lightening Hole Margin Over Rim Dedendum' times the rim dedendum. + +Defines the minimum margin between lightening holes. + +===Lightening Hole Minimum Radius=== +Default is one. + +Defines the minimum radius of the lightening holes. + +===Move Type=== +Default is 'separate'. + +====None==== +When selected, the gears will be not be moved and will therefore overlap. Afterwards the write plugin could be used to write each gear to a different file, so they can be fabricated in separate operations. + +====Mesh==== +When selected, the gears will be separated horizontally so that they just mesh. This is useful to test if the gears mesh properly. + +====Separate==== +When selected, the gears will be separated horizontally with a gap between them. + +====Vertical==== +When selected, the gears will be separated vertically. + +===Operating Angle=== +Default is 180 degrees. + +Defines the operating angle between the gear axes. If the operating angle is not 180 degrees, a bevel gear couple will be generated. + +===Pinion Collar Length Couplet=== +====Pinion Collar Length Over Face Width==== +Default is zero. + +Defines the ratio of the pinion collar length over the face width. + +====Pinion Collar Length==== +Default is the 'Pinion Collar Length Over Face Width' times the face width. + +Defines the pinion collar length. If the pinion collar length is zero, there will not be a collar on the pinion gear. + +===Pitch Radius=== +Default is twenty if the pitch radius has not been set. If the center distance is set, the default pitch radius is the center distance times the number of pinion teeth divided by the total number of gear teeth. + +Defines the pinion pitch radius. + +===Plate Clearance Couplet=== +====Plate Clearance Over Length==== +Default is 0.2. + +Defines the ratio of the plate clearance over the plate length. + +====Plate Clearance==== +Default is the 'Plate Clearance Over Length' times the plate length. + +Defines the clearance between the pinion and the plate of the ring gear. If the clearance is zero, they will touch. + +===Plate Length Couplet=== +====Plate Length Over Face Width==== +Default is half. + +Defines the ratio of the plate length over the face width. + +====Plate Length==== +Default is the 'Plate Length Over Face Width' times the face width. + +Defines the length of the plate of the ring gear. + +===Pressure Angle=== +Default is twenty degrees. + +Defines the pressure angle of the gear couple. + +===Profile Surfaces=== +Default is eleven. + +Defines the number of profile surfaces. + +===Rack Hole Below Over Width Couplet=== +====Rack Hole Below Over Width==== +Default is 0.6. + +Defines the ratio of the distance below the pitch of the rack holes over the rack width. + +====Rack Hole Below==== +Default is the 'Rack Hole Below Over Width' times the rack width. + +Defines the the distance below the pitch of the rack holes. + +===Rack Hole Radius Couplet=== +====Rack Hole Radius Over Width==== +Default is zero. + +Defines the ratio of the rack hole radius over the rack width. + +====Rack Hole Radius==== +Default is the 'Rack Hole Radius Over Width' times the rack width. + +Defines the radius of the rack holes. If the rack hole radius is zero, there won't be any rack holes. + +===Rack Hole Step Over Width Couplet=== +====Rack Hole Step Over Width==== +Default is one. + +Defines the ratio of the rack hole step over the rack width. + +====Rack Hole Step==== +Default is the 'Rack Hole Step Over Width' times the rack width. + +Defines the horizontal step distance between the rack holes. + +===Rack Length Over Radius Couplet=== +====Rack Length Over Radius==== +Default is two times pi. + +Defines the ratio of the rack length over the pitch radius. + +====Rack Length==== +Default is the 'Rack Length Over Radius' times the pitch radius. + +Defines the rack length. + +===Rack Width Couplet=== +====Rack Width Over Face Width==== +Default is one. + +Defines the ratio of the rack width over the face width. + +====Rack Width==== +Default is the 'Rack Width Over Face Width' times the face width. + +Defines the rack width. + +===Rim Dedendum Couplet=== +====Rim Dedendum Over Radius==== +Default is 0.2. + +Defines the ratio of the rim dedendum over the pitch radius. + +====Rim Dedendum==== +Default is the 'Rim Dedendum Over Radius' times the pitch radius. + +Defines the rim dedendum of the gear. + +===Root Bevel Couplet=== +====Root Bevel Over Clearance==== +Default is half. + +Defines the ratio of the root bevel over the clearance. + +====Root Bevel==== +Default is the 'Root Bevel Over Clearance' times the clearance. + +Defines the bevel at the root of the gear tooth. + +===Shaft Depth Bottom Couplet=== +====Shaft Depth Bottom Over Radius==== +Default is zero. + +Defines the ratio of the bottom shaft depth over the shaft radius. + +====Shaft Depth Bottom==== +Default is the 'Shaft Depth Bottom Over Radius' times the shaft radius. + +Defines the bottom shaft depth. + +===Shaft Depth Top Couplet=== +====Shaft Depth Top Over Radius==== +Default is zero. + +Defines the ratio of the top shaft depth over the shaft radius. + +====Shaft Depth Top==== +Default is the 'Shaft Depth Top Over Radius' times the shaft radius. + +Defines the top shaft depth. + +===Shaft Path=== +Default is empty. + +Defines the path of the shaft hole. If the shaft path is the default empty, then the shaft path will be generated from the shaft depth bottom, shaft depth top, shaft radius and shaft sides. + +===Shaft Radius Couplet=== +====Shaft Radius Over Pitch Radius==== +Default is zero. + +Defines the ratio of the shaft radius over the pitch radius. + +====Shaft Radius==== +Default is the 'Shaft Radius Over Pitch Radius' times the pitch radius. + +Defines the shaft radius. If the shaft radius is zero there will not be a shaft hole. + +===Shaft Sides=== +Default is four. + +Defines the number of shaft sides. + +===Teeth Pinion=== +Default is seven. + +Defines the number of teeth in the pinion. + +===Teeth Complement=== +Default is seventeen. + +Defines the number of teeth in the complement of the gear couple. If the number of teeth is positive, the gear couple will be a spur or bevel type. If the number of teeth is zero, the gear couple will be a rack and pinion. If the number of teeth is negative, the gear couple will be a spur and ring. + +===Tip Bevel Couplet=== +====Tip Bevel Over Clearance==== +Default is 0.1. + +Defines the ratio of the tip bevel over the clearance. + +====Tip Bevel==== +Default is the 'Tip Bevel Over Clearance' times the clearance. + +Defines the bevel at the tip of the gear tooth. + +===Tooth Thickness Multiplier=== +Default is 0.99999. + +Defines the amount the thickness of the tooth will multiplied. If when the gears are produced, they mesh too tightly, you can reduce the tooth thickness multiplier so that they mesh with reasonable tightness. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import shaft +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.creation import teardrop +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addBevelGear(derivation, extrudeDerivation, pitchRadius, positives, teeth, vector3GearProfile): + "Get extrude output for a cylinder gear." + totalPitchRadius = derivation.pitchRadiusComplement + derivation.pitchRadius + totalTeeth = derivation.teethPinion + derivation.teethComplement + portionDirections = extrude.getSpacedPortionDirections(extrudeDerivation.interpolationDictionary) + loopLists = extrude.getLoopListsByPath(extrudeDerivation, None, vector3GearProfile[0], portionDirections) + firstLoopList = loopLists[0] + gearOverPinion = float(totalTeeth - teeth) / float(teeth) + thirdLayerHeight = 0.33333333333 * setting.getLayerHeight(derivation.elementNode) + pitchRadian = math.atan(math.sin(derivation.operatingRadian) / (gearOverPinion + math.cos(derivation.operatingRadian))) + coneDistance = pitchRadius / math.sin(pitchRadian) + apex = Vector3(0.0, 0.0, math.sqrt(coneDistance * coneDistance - pitchRadius * pitchRadius)) + cosPitch = apex.z / coneDistance + sinPitch = math.sin(pitchRadian) + for loop in firstLoopList: + for point in loop: + alongWay = point.z / coneDistance + oneMinusAlongWay = 1.0 - alongWay + pointComplex = point.dropAxis() + pointComplexLength = abs(pointComplex) + deltaRadius = pointComplexLength - pitchRadius + cosDeltaRadius = cosPitch * deltaRadius + sinDeltaRadius = sinPitch * deltaRadius + pointComplex *= (cosDeltaRadius + pitchRadius) / pointComplexLength + point.x = pointComplex.real + point.y = pointComplex.imag + point.z += sinDeltaRadius + point.x *= oneMinusAlongWay + point.y *= oneMinusAlongWay + addBottomLoop(-thirdLayerHeight, firstLoopList) + topLoop = firstLoopList[-1] + topAddition = [] + topZ = euclidean.getTopPath(topLoop) + thirdLayerHeight + oldIndex = topLoop[-1].index + for point in topLoop: + oldIndex += 1 + topAddition.append(Vector3Index(oldIndex, 0.8 * point.x, 0.8 * point.y, topZ)) + firstLoopList.append(topAddition) + translation = Vector3(0.0, 0.0, -euclidean.getBottomByPaths(firstLoopList)) + euclidean.translateVector3Paths(firstLoopList, translation) + geometryOutput = triangle_mesh.getPillarsOutput(loopLists) + positives.append(geometryOutput) + +def addBottomLoop(deltaZ, loops): + "Add bottom loop to loops." + bottomLoop = loops[0] + bottomAddition = [] + bottomZ = euclidean.getBottomByPath(bottomLoop) + deltaZ + for point in bottomLoop: + bottomAddition.append(Vector3Index(len(bottomAddition), point.x, point.y, bottomZ)) + loops.insert(0, bottomAddition) + numberOfVertexes = 0 + for loop in loops: + for point in loop: + point.index = numberOfVertexes + numberOfVertexes += 1 + +def addCollarShaft(collarLength, derivation, elementNode, negatives, positives): + 'Add collar.' + if collarLength <= 0.0: + addShaft(derivation, negatives, positives) + return + connectionEnd = Vector3(0.0, 0.0, derivation.faceWidth + collarLength) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(0.0, 0.0, derivation.faceWidth), connectionEnd] + collarDerivation = extrude.ExtrudeDerivation(copyShallow) + addCollarShaftSetDerivation(collarDerivation, collarLength, derivation, elementNode, negatives, positives) + +def addCollarShaftSetDerivation(collarDerivation, collarLength, derivation, elementNode, negatives, positives): + 'Add collar and shaft.' + collarSides = evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, derivation.shaftRimRadius) + collarProfile = euclidean.getComplexPolygon(complex(), derivation.shaftRimRadius, collarSides) + vector3CollarProfile = euclidean.getVector3Path(collarProfile) + extrude.addPositives(collarDerivation, [vector3CollarProfile], positives) + addShaft(derivation, negatives, positives) + drillZ = derivation.faceWidth + 0.5 * collarLength + drillEnd = Vector3(0.0, derivation.shaftRimRadius, drillZ) + drillStart = Vector3(0.0, 0.0, drillZ) + teardrop.addNegativesByRadius(elementNode, drillEnd, negatives, derivation.keywayRadius, drillStart) + +def addLighteningHoles(derivation, gearHolePaths, negatives, pitchRadius, positives): + "Add lightening holes." + positiveVertexes = matrix.getVertexes(positives) + bottomPath = euclidean.getTopPath(positiveVertexes) + topPath = euclidean.getBottomByPath(positiveVertexes) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(0.0, 0.0, bottomPath), Vector3(0.0, 0.0, topPath)] + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + vector3LighteningHoles = getLighteningHoles(derivation, gearHolePaths, pitchRadius) + extrude.addNegativesPositives(extrudeDerivation, negatives, vector3LighteningHoles, positives) + +def addRackHole(derivation, elementNode, vector3RackProfiles, x): + "Add rack hole to vector3RackProfiles." + rackHole = euclidean.getComplexPolygon(complex(x, -derivation.rackHoleBelow), derivation.rackHoleRadius, -13) + vector3RackProfiles.append(euclidean.getVector3Path(rackHole)) + +def addRackHoles(derivation, elementNode, vector3RackProfiles): + "Add rack holes to vector3RackProfiles." + if len(derivation.gearHolePaths) > 0: + vector3RackProfiles += derivation.gearHolePaths + return + if derivation.rackHoleRadius <= 0.0: + return + addRackHole(derivation, elementNode, vector3RackProfiles, 0.0) + rackHoleMargin = derivation.rackHoleRadius + derivation.rackHoleRadius + rackHoleSteps = int(math.ceil((derivation.rackDemilength - rackHoleMargin) / derivation.rackHoleStep)) + for rackHoleIndex in xrange(1, rackHoleSteps): + x = float(rackHoleIndex) * derivation.rackHoleStep + addRackHole(derivation, elementNode, vector3RackProfiles, -x) + addRackHole(derivation, elementNode, vector3RackProfiles, x) + +def addShaft(derivation, negatives, positives): + "Add shaft." + if len(derivation.shaftPath) < 3: + return + positiveVertexes = matrix.getVertexes(positives) + bottomPath = euclidean.getTopPath(positiveVertexes) + topPath = euclidean.getBottomByPath(positiveVertexes) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(0.0, 0.0, bottomPath), Vector3(0.0, 0.0, topPath)] + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + extrude.addNegativesPositives(extrudeDerivation, negatives, [derivation.shaftPath], positives) + +def getAxialMargin(circleRadius, numberOfSides, polygonRadius): + 'Get axial margin.' + return polygonRadius * math.sin(math.pi / float(numberOfSides)) - circleRadius + +def getBevelPath(begin, bevel, center, end): + 'Get bevel path.' + centerMinusBegin = center - begin + centerMinusBeginLength = abs(centerMinusBegin) + endMinusCenter = end - center + endMinusCenterLength = abs(endMinusCenter) + endMinusCenter /= endMinusCenterLength + maximumExtensionLength = 0.333333333 * endMinusCenterLength + if centerMinusBeginLength <= bevel * 1.5: + extensionLength = min(maximumExtensionLength, centerMinusBeginLength) + return [complex(center.real, center.imag) + extensionLength * endMinusCenter] + centerMinusBegin *= (centerMinusBeginLength - bevel) / centerMinusBeginLength + extensionLength = min(maximumExtensionLength, bevel) + bevelPath = [complex(center.real, center.imag) + extensionLength * endMinusCenter] + bevelPath.append(begin + centerMinusBegin) + return bevelPath + +def getGearPaths(derivation, pitchRadius, teeth, toothProfile): + 'Get gear paths.' + if teeth < 0: + return getGearProfileAnnulus(derivation, pitchRadius, teeth, toothProfile) + if teeth == 0: + return [getGearProfileRack(derivation, toothProfile)] + return [getGearProfileCylinder(teeth, toothProfile)] + +def getGearProfileAnnulus(derivation, pitchRadius, teeth, toothProfile): + 'Get gear profile for an annulus gear.' + gearProfileCylinder = getGearProfileCylinder(teeth, toothProfile) + annulusRadius = derivation.dedendum + derivation.rimDedendum - pitchRadius + return [euclidean.getComplexPolygon(complex(), annulusRadius, -teeth, 0.5 * math.pi), gearProfileCylinder] + +def getGearProfileCylinder(teeth, toothProfile): + 'Get gear profile for a cylinder gear.' + gearProfile = [] + toothAngleRadian = 2.0 * math.pi / float(teeth) + totalToothAngle = 0.0 + for toothIndex in xrange(abs(teeth)): + for toothPoint in toothProfile: + gearProfile.append(toothPoint * euclidean.getWiddershinsUnitPolar(totalToothAngle)) + totalToothAngle += toothAngleRadian + return gearProfile + +def getGearProfileRack(derivation, toothProfile): + 'Get gear profile for rack.' + derivation.extraRackDemilength = 0.0 + for complexPoint in derivation.helixPath: + derivation.extraRackDemilength = max(abs(derivation.helixHeight * complexPoint.imag), derivation.extraRackDemilength) + rackDemilengthPlus = derivation.rackDemilength + if derivation.faceWidth > 0.0: + derivation.extraRackDemilength *= 1.1 + rackDemilengthPlus += derivation.extraRackDemilength + teethRack = int(math.ceil(rackDemilengthPlus / derivation.wavelength)) + gearProfile = [] + for toothIndex in xrange(-teethRack, teethRack + 1): + translateComplex = complex(-toothIndex * derivation.wavelength, 0.0) + translatedPath = euclidean.getTranslatedComplexPath(toothProfile, translateComplex) + gearProfile += translatedPath + gearProfile = euclidean.getHorizontallyBoundedPath(rackDemilengthPlus, -rackDemilengthPlus, gearProfile) + firstPoint = gearProfile[0] + lastPoint = gearProfile[-1] + rackWidth = derivation.rackWidth + minimumRackWidth = 1.1 * derivation.dedendum + if rackWidth < minimumRackWidth: + rackWidth = minimumRackWidth + print('Warning, rackWidth is too small in getGearProfileRack in gear.') + print('RackWidth will be set to a bit more than the dedendum.') + gearProfile += [complex(lastPoint.real, -rackWidth),complex(firstPoint.real, -rackWidth)] + return gearProfile + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = GearDerivation(elementNode) + creationFirst = derivation.creationType.lower()[: 1] + toothProfileComplement = getToothProfile(derivation, derivation.pitchRadiusComplement, derivation.teethComplement) + pinionProfile = getGearProfileCylinder(derivation.teethPinion, derivation.pinionToothProfile) + complementPaths = getGearPaths( + derivation, derivation.pitchRadiusComplement, derivation.teethComplement, toothProfileComplement) + vector3PinionProfile = euclidean.getVector3Path(pinionProfile) + vector3ComplementPaths = euclidean.getVector3Paths(complementPaths) + translation = Vector3() + moveFirst = derivation.moveType.lower()[: 1] + if moveFirst != 'n': + distance = derivation.pitchRadius + if moveFirst == 'm': + distance += derivation.pitchRadiusComplement + else: + distance += abs(derivation.pitchRadiusComplement) + decimalPlaces = 1 - int(math.floor(math.log10(distance))) + distance += derivation.halfWavelength + derivation.halfWavelength + distance = round(1.15 * distance, decimalPlaces) + translation = Vector3(0.0, -distance) + if derivation.faceWidth <=0.0: + return getPathOutput( + creationFirst, derivation, elementNode, translation, vector3ComplementPaths, vector3PinionProfile) + pitchRadius = derivation.pitchRadius + teeth = derivation.teethPinion + twist = derivation.helixHeight / derivation.pitchRadius + extrudeOutputPinion = getOutputCylinder( + derivation.pinionCollarLength, derivation, elementNode, None, pitchRadius, teeth, twist, [vector3PinionProfile]) + if creationFirst == 'p': + return extrudeOutputPinion + teeth = derivation.teethComplement + extrudeOutputSecond = None + if teeth == 0: + extrudeOutputSecond = getOutputRack(derivation, elementNode, vector3ComplementPaths[0]) + else: + twist = -derivation.helixHeight / derivation.pitchRadiusComplement + extrudeOutputSecond = getOutputCylinder( + derivation.complementCollarLength, + derivation, + elementNode, + derivation.gearHolePaths, + derivation.pitchRadiusComplement, + teeth, + twist, + vector3ComplementPaths) + if creationFirst == 'c': + return extrudeOutputSecond + gearVertexes = matrix.getVertexes(extrudeOutputSecond) + if moveFirst == 'v': + translation = Vector3(0.0, 0.0, euclidean.getTopPath(gearVertexes)) + euclidean.translateVector3Path(matrix.getVertexes(extrudeOutputPinion), translation) + else: + euclidean.translateVector3Path(gearVertexes, translation) + return {'group' : {'shapes' : [extrudeOutputPinion, extrudeOutputSecond]}} + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + return getGeometryOutput(None, elementNode) + +def getHalfwave(pitchRadius, teeth): + 'Get tooth halfwave.' + return pitchRadius * math.pi / float(teeth) + +def getHelixComplexPath(derivation, elementNode): + 'Set gear helix path.' + helixTypeFirstCharacter = derivation.helixType.lower()[: 1] + if helixTypeFirstCharacter == 'b': + return [complex(), complex(1.0, 1.0)] + if helixTypeFirstCharacter == 'h': + return [complex(), complex(0.5, 0.5), complex(1.0, 0.0)] + if helixTypeFirstCharacter == 'p': + helixComplexPath = [] + x = 0.0 + xStep = setting.getLayerHeight(elementNode) / derivation.faceWidth + justBelowOne = 1.0 - 0.5 * xStep + while x < justBelowOne: + distanceFromCenter = 0.5 - x + parabolicTwist = 0.25 - distanceFromCenter * distanceFromCenter + helixComplexPath.append(complex(x, parabolicTwist)) + x += xStep + helixComplexPath.append(complex(1.0, 0.0)) + return helixComplexPath + print('Warning, the helix type was not one of (basic, herringbone or parabolic) in getHelixComplexPath in gear for:') + print(derivation.helixType) + print(derivation.elementNode) + +def getLiftedOutput(derivation, geometryOutput): + "Get extrude output for a rack." + if derivation.moveType.lower()[: 1] == 'm': + return geometryOutput + geometryOutputVertexes = matrix.getVertexes(geometryOutput) + translation = Vector3(0.0, 0.0, -euclidean.getBottomByPath(geometryOutputVertexes)) + euclidean.translateVector3Path(geometryOutputVertexes, translation) + return geometryOutput + +def getLighteningHoles(derivation, gearHolePaths, pitchRadius): + 'Get cutout circles.' + if gearHolePaths != None: + if len(gearHolePaths) > 0: + return gearHolePaths + innerRadius = abs(pitchRadius) - derivation.dedendum + lighteningHoleOuterRadius = innerRadius - derivation.rimDedendum + shaftRimRadius = max(derivation.shaftRimRadius, (lighteningHoleOuterRadius) * (0.5 - math.sqrt(0.1875))) + lighteningHoleRadius = 0.5 * (lighteningHoleOuterRadius - derivation.shaftRimRadius) + if lighteningHoleRadius < derivation.lighteningHoleMinimumRadius: + return [] + lighteningHoles = [] + numberOfLighteningHoles = 3 + polygonRadius = lighteningHoleOuterRadius - lighteningHoleRadius + rimDemiwidth = 0.5 * derivation.lighteningHoleMargin + axialMargin = getAxialMargin(lighteningHoleRadius, numberOfLighteningHoles, polygonRadius) + if axialMargin < rimDemiwidth: + while axialMargin < rimDemiwidth: + lighteningHoleRadius *= 0.999 + if lighteningHoleRadius < derivation.lighteningHoleMinimumRadius: + return [] + axialMargin = getAxialMargin(lighteningHoleRadius, numberOfLighteningHoles, polygonRadius) + else: + newNumberOfLighteningHoles = numberOfLighteningHoles + while axialMargin > rimDemiwidth: + numberOfLighteningHoles = newNumberOfLighteningHoles + newNumberOfLighteningHoles += 2 + axialMargin = getAxialMargin(lighteningHoleRadius, newNumberOfLighteningHoles, polygonRadius) + sideAngle = 2.0 * math.pi / float(numberOfLighteningHoles) + startAngle = 0.0 + for lighteningHoleIndex in xrange(numberOfLighteningHoles): + unitPolar = euclidean.getWiddershinsUnitPolar(startAngle) + lighteningHole = euclidean.getComplexPolygon(unitPolar * polygonRadius, lighteningHoleRadius, -13) + lighteningHoles.append(lighteningHole) + startAngle += sideAngle + return euclidean.getVector3Paths(lighteningHoles) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return GearDerivation(elementNode) + +def getOutputCylinder( + collarLength, derivation, elementNode, gearHolePaths, pitchRadius, teeth, twist, vector3GearProfile): + "Get extrude output for a cylinder gear." + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(), Vector3(0.0, 0.0, derivation.faceWidth)] + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + negatives = [] + positives = [] + if twist != 0.0: + twistDegrees = math.degrees(twist) + extrudeDerivation.twistPathDefault = [] + for complexPoint in derivation.helixPath: + extrudeDerivation.twistPathDefault.append(Vector3(complexPoint.real, twistDegrees * complexPoint.imag)) + extrude.insertTwistPortions(extrudeDerivation, elementNode) + if derivation.operatingAngle != 180.0: + addBevelGear(derivation, extrudeDerivation, pitchRadius, positives, teeth, vector3GearProfile) + addCollarShaft(collarLength, derivation, elementNode, negatives, positives) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + if pitchRadius > 0: + extrude.addNegativesPositives(extrudeDerivation, negatives, vector3GearProfile, positives) + addLighteningHoles(derivation, gearHolePaths, negatives, pitchRadius, positives) + addCollarShaft(collarLength, derivation, elementNode, negatives, positives) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + if derivation.plateLength <= 0.0: + extrude.addNegativesPositives(extrudeDerivation, negatives, vector3GearProfile, positives) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + portionDirections = extrude.getSpacedPortionDirections(extrudeDerivation.interpolationDictionary) + outerGearProfile = vector3GearProfile[0] + outerLoopLists = extrude.getLoopListsByPath(extrudeDerivation, None, outerGearProfile, portionDirections) + addBottomLoop(-derivation.plateClearance, outerLoopLists[0]) + geometryOutput = triangle_mesh.getPillarsOutput(outerLoopLists) + positives.append(geometryOutput) + innerLoopLists = extrude.getLoopListsByPath(extrudeDerivation, None, vector3GearProfile[1], portionDirections) + addBottomLoop(-derivation.plateClearance, innerLoopLists[0]) + geometryOutput = triangle_mesh.getPillarsOutput(innerLoopLists) + negatives.append(geometryOutput) + connectionStart = Vector3(0.0, 0.0, -derivation.plateLength) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [connectionStart, Vector3(0.0, 0.0, -derivation.plateClearance)] + plateDerivation = extrude.ExtrudeDerivation(copyShallow) + extrude.addNegativesPositives(plateDerivation, negatives, [outerGearProfile], positives) + vector3LighteningHoles = getLighteningHoles(derivation, gearHolePaths, pitchRadius) + extrude.addNegativesPositives(plateDerivation, negatives, vector3LighteningHoles, positives) + addShaft(derivation, negatives, positives) + positiveOutput = triangle_mesh.getUnifiedOutput(positives) + annulusPlateOutput = {'difference' : {'shapes' : [positiveOutput] + negatives}} + if collarLength <= 0.0: + outputCylinder = solid.getGeometryOutputByManipulation(elementNode, annulusPlateOutput) + return getLiftedOutput(derivation, outputCylinder) + negatives = [] + positives = [] + connectionEnd = Vector3(0.0, 0.0, derivation.faceWidth + collarLength) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(0.0, 0.0, -derivation.plateClearance), connectionEnd] + collarDerivation = extrude.ExtrudeDerivation(copyShallow) + addCollarShaftSetDerivation(collarDerivation, collarLength, derivation, elementNode, negatives, positives) + collarOutput = {'difference' : {'shapes' : positives + negatives}} + cylinderOutput = {'union' : {'shapes' : [annulusPlateOutput, collarOutput]}} + outputCylinder = solid.getGeometryOutputByManipulation(elementNode, cylinderOutput) + return getLiftedOutput(derivation, outputCylinder) + +def getOutputRack(derivation, elementNode, vector3GearProfile): + "Get extrude output for a rack." + path = [] + for complexPoint in derivation.helixPath: + point = Vector3(derivation.helixHeight * complexPoint.imag, 0.0, derivation.faceWidth * complexPoint.real) + path.append(point) + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = path + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + negatives = [] + positives = [] + vector3RackProfiles = [vector3GearProfile] + if derivation.extraRackDemilength > 0.0: + yMaximum = -912345678.0 + yMinimum = 912345678.0 + for point in vector3GearProfile: + yMaximum = max(point.y, yMaximum) + yMinimum = min(point.y, yMinimum) + muchLessThanWidth = 0.01 * derivation.rackWidth + yMaximum += muchLessThanWidth + yMinimum -= muchLessThanWidth + extraRackLength = derivation.extraRackDemilength + derivation.extraRackDemilength + rackDemilengthPlus = derivation.rackDemilength + extraRackLength + leftNegative = [ + Vector3(-derivation.rackDemilength, yMaximum), + Vector3(-derivation.rackDemilength, yMinimum), + Vector3(-rackDemilengthPlus, yMinimum), + Vector3(-rackDemilengthPlus, yMaximum)] + vector3RackProfiles.append(leftNegative) + rightNegative = [ + Vector3(rackDemilengthPlus, yMaximum), + Vector3(rackDemilengthPlus, yMinimum), + Vector3(derivation.rackDemilength, yMinimum), + Vector3(derivation.rackDemilength, yMaximum)] + vector3RackProfiles.append(rightNegative) + addRackHoles(derivation, elementNode, vector3RackProfiles) + extrude.addNegativesPositives(extrudeDerivation, negatives, vector3RackProfiles, positives) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + +def getPathOutput(creationFirst, derivation, elementNode, translation, vector3ComplementPaths, vector3PinionProfile): + "Get gear path output." + vector3PinionProfile = lineation.getPackedGeometryOutputByLoop(elementNode, lineation.SideLoop(vector3PinionProfile)) + if creationFirst == 'p': + return vector3PinionProfile + packedGearGeometry = [] + for vector3ComplementPath in vector3ComplementPaths: + sideLoop = lineation.SideLoop(vector3ComplementPath) + packedGearGeometry += lineation.getPackedGeometryOutputByLoop(elementNode, sideLoop) + if creationFirst == 'c': + return packedGearGeometry + euclidean.translateVector3Paths(packedGearGeometry, translation) + return vector3PinionProfile + packedGearGeometry + +def getThicknessMultipliedPath(path, thicknessMultiplier): + "Get thickness multiplied path." + for pointIndex, point in enumerate(path): + path[pointIndex] = complex(point.real * thicknessMultiplier, point.imag) + return path + +def getToothProfile(derivation, pitchRadius, teeth): + 'Get profile for one tooth.' + if teeth < 0: + return getToothProfileAnnulus(derivation, pitchRadius, teeth) + if teeth == 0: + return getToothProfileRack(derivation) + return getToothProfileCylinder(derivation, pitchRadius, teeth) + +def getToothProfileAnnulus(derivation, pitchRadius, teeth): + 'Get profile for one tooth of an annulus.' + toothProfileHalf = [] + toothProfileHalfCylinder = getToothProfileHalfCylinder(derivation, pitchRadius) + pitchRadius = -pitchRadius + innerRadius = pitchRadius - derivation.addendum + # tooth is multiplied by 1.02 because at around 1.01 for a 7/-23/20.0 test case, there is intersection since the paths are bending together + for point in getThicknessMultipliedPath(toothProfileHalfCylinder, 1.02 / derivation.toothThicknessMultiplier): + if abs(point) >= innerRadius: + toothProfileHalf.append(point) + profileFirst = toothProfileHalf[0] + profileSecond = toothProfileHalf[1] + firstMinusSecond = profileFirst - profileSecond + remainingAddendum = abs(profileFirst) - innerRadius + firstMinusSecond *= remainingAddendum / abs(firstMinusSecond) + extensionPoint = profileFirst + firstMinusSecond + if derivation.tipBevel > 0.0: + unitPolar = euclidean.getWiddershinsUnitPolar(2.0 / float(teeth) * math.pi) + mirrorPoint = complex(-extensionPoint.real, extensionPoint.imag) * unitPolar + bevelPath = getBevelPath(profileFirst, derivation.tipBevel, extensionPoint, mirrorPoint) + toothProfileHalf = bevelPath + toothProfileHalf + else: + toothProfileHalf.insert(0, extensionPoint) + profileLast = toothProfileHalf[-1] + profilePenultimate = toothProfileHalf[-2] + lastMinusPenultimate = profileLast - profilePenultimate + remainingDedendum = pitchRadius - abs(profileLast) + derivation.dedendum + lastMinusPenultimate *= remainingDedendum / abs(lastMinusPenultimate) + extensionPoint = profileLast + lastMinusPenultimate + if derivation.rootBevel > 0.0: + mirrorPoint = complex(-extensionPoint.real, extensionPoint.imag) + bevelPath = getBevelPath(profileLast, derivation.rootBevel, extensionPoint, mirrorPoint) + bevelPath.reverse() + toothProfileHalf += bevelPath + else: + toothProfileHalf.append(extensionPoint) + toothProfileAnnulus = euclidean.getMirrorPath(toothProfileHalf) + toothProfileAnnulus.reverse() + return toothProfileAnnulus + +def getToothProfileCylinder(derivation, pitchRadius, teeth): + 'Get profile for one tooth of a cylindrical gear.' + toothProfileHalfCylinder = getToothProfileHalfCylinder(derivation, pitchRadius) + toothProfileHalfCylinder = getThicknessMultipliedPath(toothProfileHalfCylinder, derivation.toothThicknessMultiplier) + toothProfileHalf = [] + innerRadius = pitchRadius - derivation.dedendum + for point in toothProfileHalfCylinder: + if abs(point) >= innerRadius: + toothProfileHalf.append(point) + return getToothProfileCylinderByProfile(derivation, pitchRadius, teeth, toothProfileHalf) + +def getToothProfileCylinderByProfile(derivation, pitchRadius, teeth, toothProfileHalf): + 'Get profile for one tooth of a cylindrical gear.' + profileFirst = toothProfileHalf[0] + profileSecond = toothProfileHalf[1] + firstMinusSecond = profileFirst - profileSecond + remainingDedendum = abs(profileFirst) - pitchRadius + derivation.dedendum + firstMinusSecond *= remainingDedendum / abs(firstMinusSecond) + extensionPoint = profileFirst + firstMinusSecond + if derivation.rootBevel > 0.0: + unitPolar = euclidean.getWiddershinsUnitPolar(-2.0 / float(teeth) * math.pi) + mirrorPoint = complex(-extensionPoint.real, extensionPoint.imag) * unitPolar + bevelPath = getBevelPath(profileFirst, derivation.rootBevel, extensionPoint, mirrorPoint) + toothProfileHalf = bevelPath + toothProfileHalf + else: + toothProfileHalf.insert(0, extensionPoint) + if derivation.tipBevel > 0.0: + profileLast = toothProfileHalf[-1] + profilePenultimate = toothProfileHalf[-2] + mirrorPoint = complex(-profileLast.real, profileLast.imag) + bevelPath = getBevelPath(profilePenultimate, derivation.tipBevel, profileLast, mirrorPoint) + bevelPath.reverse() + toothProfileHalf = toothProfileHalf[: -1] + bevelPath + return euclidean.getMirrorPath(toothProfileHalf) + +def getToothProfileHalfCylinder(derivation, pitchRadius): + 'Get profile for half of a one tooth of a cylindrical gear.' + toothProfile=[] +# x = -y * tan(p) + 1 +# x*x + y*y = (2-cos(p))^2 +# y*y*t*t-2yt+1+y*y=4-4c-c*c +# y*y*(t*t+1)-2yt=3-4c-c*c +# y*y*(t*t+1)-2yt-3+4c-c*c=0 +# a=tt+1 +# b=-2t +# c=c(4-c)-3 + a = derivation.tanPressure * derivation.tanPressure + 1.0 + b = -derivation.tanPressure - derivation.tanPressure + cEnd = derivation.cosPressure * (4.0 - derivation.cosPressure) - 3.0 + yEnd = (-b - math.sqrt(b*b - 4 * a * cEnd)) * 0.5 / a + yEnd *= derivation.pitchRadius / abs(pitchRadius) + yEnd -= derivation.clearance / abs(pitchRadius) + # to prevent intersections, yBegin is moved towards the base circle, giving a thinner tooth + yBegin = -yEnd + if pitchRadius > 0.0: + yBegin = 0.5 * derivation.sinPressure + 0.5 * yBegin + beginComplex = complex(1.0 - yBegin * derivation.tanPressure, yBegin) + endComplex = complex(1.0 - yEnd * derivation.tanPressure, yEnd) + endMinusBeginComplex = endComplex - beginComplex + wholeAngle = -abs(endMinusBeginComplex) / derivation.cosPressure + wholeAngleIncrement = wholeAngle / float(derivation.profileSurfaces) + stringStartAngle = abs(beginComplex - complex(1.0, 0.0)) / derivation.cosPressure + wholeDepthIncrementComplex = endMinusBeginComplex / float(derivation.profileSurfaces) + for profileIndex in xrange(derivation.profileSurfaces + 1): + contactPoint = beginComplex + wholeDepthIncrementComplex * float(profileIndex) + stringAngle = stringStartAngle + wholeAngleIncrement * float(profileIndex) + angle = math.atan2(contactPoint.imag, contactPoint.real) - stringAngle + angle += 0.5 * math.pi - derivation.quarterWavelength / abs(pitchRadius) + toothPoint = abs(contactPoint) * euclidean.getWiddershinsUnitPolar(angle) * abs(pitchRadius) + toothProfile.append(toothPoint) + return toothProfile + +def getToothProfileRack(derivation): + 'Get profile for one rack tooth.' + addendumSide = derivation.quarterWavelength - derivation.addendum * derivation.tanPressure + addendumComplex = complex(addendumSide, derivation.addendum) + dedendumSide = derivation.quarterWavelength + derivation.dedendum * derivation.tanPressure + dedendumComplex = complex(dedendumSide, -derivation.dedendum) + toothProfile = [dedendumComplex] + if derivation.rootBevel > 0.0: + mirrorPoint = complex(derivation.wavelength - dedendumSide, -derivation.dedendum) + toothProfile = getBevelPath(addendumComplex, derivation.rootBevel, dedendumComplex, mirrorPoint) + if derivation.tipBevel > 0.0: + mirrorPoint = complex(-addendumComplex.real, addendumComplex.imag) + bevelPath = getBevelPath(dedendumComplex, derivation.tipBevel, addendumComplex, mirrorPoint) + bevelPath.reverse() + toothProfile += bevelPath + else: + toothProfile.append(addendumComplex) + return euclidean.getMirrorPath(getThicknessMultipliedPath(toothProfile, derivation.toothThicknessMultiplier)) + +def processElementNode(elementNode): + "Process the xml element." + geometryOutput = getGeometryOutput(None, elementNode) + if geometryOutput.__class__ == list: + path.convertElementNode(elementNode, geometryOutput) + else: + solid.processElementNodeByGeometry(elementNode, geometryOutput) + + +class GearDerivation: + "Class to hold gear variables." + def __init__(self, elementNode): + 'Set defaults.' + self.clearanceOverWavelength = evaluate.getEvaluatedFloat(0.1, elementNode, 'clearanceOverWavelength') + self.collarAddendumOverRadius = evaluate.getEvaluatedFloat(1.0, elementNode, 'collarAddendumOverRadius') + self.complementCollarLengthOverFaceWidth = evaluate.getEvaluatedFloat( + 0.0, elementNode, 'complementCollarLengthOverFaceWidth') + self.copyShallow = elementNode.getCopyShallow() + self.creationType = evaluate.getEvaluatedString('both', elementNode, 'creationType') + self.creationTypeMenuRadioStrings = 'both complement pinion'.split() + self.elementNode = elementNode + self.faceWidth = evaluate.getEvaluatedFloat(10.0, elementNode, 'faceWidth') + self.helixAngle = evaluate.getEvaluatedFloat(0.0, elementNode, 'helixAngle') + self.helixType = evaluate.getEvaluatedString('basic', elementNode, 'helixType') + self.helixTypeMenuRadioStrings = 'basic herringbone parabolic'.split() + self.keywayRadiusOverRadius = evaluate.getEvaluatedFloat(0.5, elementNode, 'keywayRadiusOverRadius') + self.lighteningHoleMarginOverRimDedendum = evaluate.getEvaluatedFloat( + 1.0, elementNode, 'lighteningHoleMarginOverRimDedendum') + self.lighteningHoleMinimumRadius = evaluate.getEvaluatedFloat( + 1.0, elementNode, 'lighteningHoleMinimumRadius') + self.moveType = evaluate.getEvaluatedString('separate', elementNode, 'moveType') + self.moveTypeMenuRadioStrings = 'mesh none separate vertical'.split() + self.operatingAngle = evaluate.getEvaluatedFloat(180.0, elementNode, 'operatingAngle') + self.pinionCollarLengthOverFaceWidth = evaluate.getEvaluatedFloat( + 0.0, elementNode, 'pinionCollarLengthOverFaceWidth') + self.plateClearanceOverLength = evaluate.getEvaluatedFloat(0.2, elementNode, 'plateClearanceOverLength') + self.plateLengthOverFaceWidth = evaluate.getEvaluatedFloat(0.5, elementNode, 'plateLengthOverFaceWidth') + self.pressureAngle = evaluate.getEvaluatedFloat(20.0, elementNode, 'pressureAngle') + self.profileSurfaces = evaluate.getEvaluatedInt(11, elementNode, 'profileSurfaces') + self.rackHoleBelowOverWidth = evaluate.getEvaluatedFloat(0.6, elementNode, 'rackHoleBelowOverWidth') + self.rackHoleRadiusOverWidth = evaluate.getEvaluatedFloat(0.0, elementNode, 'rackHoleRadiusOverWidth') + self.rackHoleStepOverWidth = evaluate.getEvaluatedFloat(1.0, elementNode, 'rackHoleStepOverWidth') + self.rackLengthOverRadius = evaluate.getEvaluatedFloat(math.pi + math.pi, elementNode, 'rackLengthOverRadius') + self.rackWidthOverFaceWidth = evaluate.getEvaluatedFloat(1.0, elementNode, 'rackWidthOverFaceWidth') + self.rimDedendumOverRadius = evaluate.getEvaluatedFloat(0.2, elementNode, 'rimDedendumOverRadius') + self.rootBevelOverClearance = evaluate.getEvaluatedFloat(0.5, elementNode, 'rootBevelOverClearance') + self.shaftDepthBottomOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'shaftDepthBottomOverRadius') + self.shaftDepthTopOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'shaftDepthOverRadius') + self.shaftRadiusOverPitchRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'shaftRadiusOverPitchRadius') + self.shaftSides = evaluate.getEvaluatedInt(4, elementNode, 'shaftSides') + self.teethComplement = evaluate.getEvaluatedInt(17, elementNode, 'teethComplement') + self.teethPinion = evaluate.getEvaluatedInt(7, elementNode, 'teeth') + totalTeethOverPinionTeeth = float(self.teethComplement + self.teethPinion) / float(self.teethPinion) + self.centerDistance = evaluate.getEvaluatedFloat(20.0 * totalTeethOverPinionTeeth, elementNode, 'centerDistance') + derivedPitchRadius = self.centerDistance / totalTeethOverPinionTeeth + self.pitchRadius = evaluate.getEvaluatedFloat(derivedPitchRadius, elementNode, 'pitchRadius') + self.tipBevelOverClearance = evaluate.getEvaluatedFloat(0.1, elementNode, 'tipBevelOverClearance') + # tooth multiplied by 0.99999 to avoid an intersection + self.toothThicknessMultiplier = evaluate.getEvaluatedFloat(0.99999, elementNode, 'toothThicknessMultiplier') + # Set derived variables. + self.wavelength = self.pitchRadius * 2.0 * math.pi / float(self.teethPinion) + self.clearance = self.wavelength * self.clearanceOverWavelength + self.clearance = evaluate.getEvaluatedFloat(self.clearance, elementNode, 'clearance') + self.complementCollarLength = self.faceWidth * self.complementCollarLengthOverFaceWidth + self.complementCollarLength = evaluate.getEvaluatedFloat(self.complementCollarLength, elementNode, 'complementCollarLength') + self.gearHolePaths = evaluate.getTransformedPathsByKey([], elementNode, 'gearHolePaths') + self.pinionCollarLength = self.faceWidth * self.pinionCollarLengthOverFaceWidth + self.pinionCollarLength = evaluate.getEvaluatedFloat(self.pinionCollarLength, elementNode, 'pinionCollarLength') + self.plateLength = self.faceWidth * self.plateLengthOverFaceWidth + self.plateLength = evaluate.getEvaluatedFloat(self.plateLength, elementNode, 'plateLength') + self.plateClearance = self.plateLength * self.plateClearanceOverLength + self.plateClearance = evaluate.getEvaluatedFloat(self.plateClearance, elementNode, 'plateClearance') + self.rackLength = self.pitchRadius * self.rackLengthOverRadius + self.rackLength = evaluate.getEvaluatedFloat(self.rackLength, elementNode, 'rackLength') + self.rackDemilength = 0.5 * self.rackLength + self.rackWidth = self.faceWidth * self.rackWidthOverFaceWidth + self.rackWidth = evaluate.getEvaluatedFloat(self.rackWidth, elementNode, 'rackWidth') + self.rimDedendum = self.pitchRadius * self.rimDedendumOverRadius + self.rimDedendum = evaluate.getEvaluatedFloat(self.rimDedendum, elementNode, 'rimDedendum') + self.rootBevel = self.clearance * self.rootBevelOverClearance + self.rootBevel = evaluate.getEvaluatedFloat(self.rootBevel, elementNode, 'rootBevel') + self.shaftRadius = self.pitchRadius * self.shaftRadiusOverPitchRadius + self.shaftRadius = evaluate.getEvaluatedFloat(self.shaftRadius, elementNode, 'shaftRadius') + self.collarAddendum = self.shaftRadius * self.collarAddendumOverRadius + self.collarAddendum = evaluate.getEvaluatedFloat(self.collarAddendum, elementNode, 'collarWidth') + self.keywayRadius = self.shaftRadius * self.keywayRadiusOverRadius + self.keywayRadius = lineation.getFloatByPrefixBeginEnd(elementNode, 'keywayRadius', 'keywayDiameter', self.keywayRadius) + self.lighteningHoleMargin = self.rimDedendum * self.lighteningHoleMarginOverRimDedendum + self.lighteningHoleMargin = evaluate.getEvaluatedFloat( + self.lighteningHoleMargin, elementNode, 'lighteningHoleMargin') + self.rackHoleBelow = self.rackWidth * self.rackHoleBelowOverWidth + self.rackHoleBelow = evaluate.getEvaluatedFloat(self.rackHoleBelow, elementNode, 'rackHoleBelow') + self.rackHoleRadius = self.rackWidth * self.rackHoleRadiusOverWidth + self.rackHoleRadius = lineation.getFloatByPrefixBeginEnd(elementNode, 'rackHoleRadius', 'rackHoleDiameter', self.rackHoleRadius) + self.rackHoleStep = self.rackWidth * self.rackHoleStepOverWidth + self.rackHoleStep = evaluate.getEvaluatedFloat(self.rackHoleStep, elementNode, 'rackHoleStep') + self.shaftDepthBottom = self.shaftRadius * self.shaftDepthBottomOverRadius + self.shaftDepthBottom = evaluate.getEvaluatedFloat(self.shaftDepthBottom, elementNode, 'shaftDepthBottom') + self.shaftDepthTop = self.shaftRadius * self.shaftDepthTopOverRadius + self.shaftDepthTop = evaluate.getEvaluatedFloat(self.shaftDepthTop, elementNode, 'shaftDepthTop') + self.shaftPath = evaluate.getTransformedPathByKey([], elementNode, 'shaftPath') + if len(self.shaftPath) < 3: + self.shaftPath = shaft.getShaftPath(self.shaftDepthBottom, self.shaftDepthTop, self.shaftRadius, -self.shaftSides) + self.tipBevel = self.clearance * self.tipBevelOverClearance + self.tipBevel = evaluate.getEvaluatedFloat(self.tipBevel, elementNode, 'tipBevel') + # Set derived values. + self.helixRadian = math.radians(self.helixAngle) + if self.teethComplement <= 0.0 and self.operatingAngle != 180.0: + print('Warning, an operatingAngle other than 180 degrees can only work with a positive number of gear teeth.') + print('Therefore the operatingAngle will be reset to 180 degrees.') + self.operatingAngle = 180.0 + self.tanHelix = math.tan(self.helixRadian) + self.helixHeight = self.tanHelix * self.faceWidth + self.operatingRadian = math.radians(self.operatingAngle) + self.pitchRadiusComplement = self.pitchRadius * float(self.teethComplement) / float(self.teethPinion) + self.pressureRadian = math.radians(self.pressureAngle) + self.cosPressure = math.cos(self.pressureRadian) + self.sinPressure = math.sin(self.pressureRadian) + self.tanPressure = math.tan(self.pressureRadian) + self.halfWavelength = 0.5 * self.wavelength + self.helixPath = euclidean.getComplexPath(evaluate.getTransformedPathByKey([], elementNode, 'helixPath')) + if len(self.helixPath) < 1: + self.helixPath = getHelixComplexPath(self, elementNode) + self.quarterWavelength = 0.25 * self.wavelength + self.shaftRimRadius = self.shaftRadius + self.collarAddendum + self.toothProfileHalf = getToothProfileHalfCylinder(self, self.pitchRadius) + self.toothProfileHalf = getThicknessMultipliedPath(self.toothProfileHalf, self.toothThicknessMultiplier) + self.addendum = self.toothProfileHalf[-1].imag - self.pitchRadius + self.dedendum = abs(self.toothProfileHalf[-1]) - self.pitchRadius + self.clearance + self.pinionToothProfile = getToothProfileCylinderByProfile(self, self.pitchRadius, self.teethPinion, self.toothProfileHalf) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/grid.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/grid.py new file mode 100644 index 0000000..78625f6 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/grid.py @@ -0,0 +1,166 @@ +""" +Grid path points. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math +import random + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, x, y, zigzag): + 'Add grid row.' + row = [] + while x < maximumComplex.real: + point = complex(x, y) + if euclidean.getIsInFilledRegion(loopsComplex, point): + row.append(point) + x += diameter.real + if zigzag and rowIndex % 2 == 1: + row.reverse() + gridPath += row + +def getGeometryOutput(elementNode): + 'Get vector3 vertexes from attribute dictionary.' + derivation = GridDerivation(elementNode) + diameter = derivation.radius + derivation.radius + typeStringTwoCharacters = derivation.typeString.lower()[: 2] + typeStringFirstCharacter = typeStringTwoCharacters[: 1] + topRight = complex(derivation.demiwidth, derivation.demiheight) + loopsComplex = [euclidean.getSquareLoopWiddershins(-topRight, topRight)] + if len(derivation.target) > 0: + loopsComplex = euclidean.getComplexPaths(derivation.target) + maximumComplex = euclidean.getMaximumByComplexPaths(loopsComplex) + minimumComplex = euclidean.getMinimumByComplexPaths(loopsComplex) + gridPath = None + if typeStringTwoCharacters == 'he': + gridPath = getHexagonalGrid(diameter, loopsComplex, maximumComplex, minimumComplex, derivation.zigzag) + elif typeStringTwoCharacters == 'ra' or typeStringFirstCharacter == 'a': + gridPath = getRandomGrid(derivation, diameter, elementNode, loopsComplex, maximumComplex, minimumComplex) + elif typeStringTwoCharacters == 're' or typeStringFirstCharacter == 'e': + gridPath = getRectangularGrid(diameter, loopsComplex, maximumComplex, minimumComplex, derivation.zigzag) + if gridPath == None: + print('Warning, the step type was not one of (hexagonal, random or rectangular) in getGeometryOutput in grid for:') + print(derivation.typeString) + print(elementNode) + return [] + loop = euclidean.getVector3Path(gridPath) + elementNode.attributes['closed'] = 'false' + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop, 0.5 * math.pi)) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + if len(arguments) < 1: + return getGeometryOutput(elementNode) + inradius = 0.5 * euclidean.getFloatFromValue(arguments[0]) + elementNode.attributes['inradius.x'] = str(inradius) + if len(arguments) > 1: + inradius = 0.5 * euclidean.getFloatFromValue(arguments[1]) + elementNode.attributes['inradius.y'] = str(inradius) + return getGeometryOutput(elementNode) + +def getHexagonalGrid(diameter, loopsComplex, maximumComplex, minimumComplex, zigzag): + 'Get hexagonal grid.' + diameter = complex(diameter.real, math.sqrt(0.75) * diameter.imag) + demiradius = 0.25 * diameter + xRadius = 0.5 * diameter.real + xStart = minimumComplex.real - demiradius.real + y = minimumComplex.imag - demiradius.imag + gridPath = [] + rowIndex = 0 + while y < maximumComplex.imag: + x = xStart + if rowIndex % 2 == 1: + x -= xRadius + addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, x, y, zigzag) + y += diameter.imag + rowIndex += 1 + return gridPath + +def getIsPointInsideZoneAwayOthers(diameterReciprocal, loopsComplex, point, pixelDictionary): + 'Determine if the point is inside the loops zone and and away from the other points.' + if not euclidean.getIsInFilledRegion(loopsComplex, point): + return False + pointOverDiameter = complex(point.real * diameterReciprocal.real, point.imag * diameterReciprocal.imag) + squareValues = euclidean.getSquareValuesFromPoint(pixelDictionary, pointOverDiameter) + for squareValue in squareValues: + if abs(squareValue - pointOverDiameter) < 1.0: + return False + euclidean.addElementToPixelListFromPoint(pointOverDiameter, pixelDictionary, pointOverDiameter) + return True + +def getNewDerivation(elementNode): + 'Get new derivation.' + return GridDerivation(elementNode) + +def getRandomGrid(derivation, diameter, elementNode, loopsComplex, maximumComplex, minimumComplex): + 'Get rectangular grid.' + gridPath = [] + diameterReciprocal = complex(1.0 / diameter.real, 1.0 / diameter.imag) + diameterSquared = diameter.real * diameter.real + diameter.imag * diameter.imag + elements = int(math.ceil(derivation.density * euclidean.getAreaLoops(loopsComplex) / diameterSquared / math.sqrt(0.75))) + elements = evaluate.getEvaluatedInt(elements, elementNode, 'elements') + failedPlacementAttempts = 0 + pixelDictionary = {} + if derivation.seed != None: + random.seed(derivation.seed) + successfulPlacementAttempts = 0 + while failedPlacementAttempts < 100: + point = euclidean.getRandomComplex(minimumComplex, maximumComplex) + if getIsPointInsideZoneAwayOthers(diameterReciprocal, loopsComplex, point, pixelDictionary): + gridPath.append(point) + euclidean.addElementToPixelListFromPoint(point, pixelDictionary, point) + successfulPlacementAttempts += 1 + else: + failedPlacementAttempts += 1 + if successfulPlacementAttempts >= elements: + return gridPath + return gridPath + +def getRectangularGrid(diameter, loopsComplex, maximumComplex, minimumComplex, zigzag): + 'Get rectangular grid.' + demiradius = 0.25 * diameter + xStart = minimumComplex.real - demiradius.real + y = minimumComplex.imag - demiradius.imag + gridPath = [] + rowIndex = 0 + while y < maximumComplex.imag: + addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, xStart, y, zigzag) + y += diameter.imag + rowIndex += 1 + return gridPath + +def processElementNode(elementNode): + 'Process the xml element.' + path.convertElementNode(elementNode, getGeometryOutput(elementNode)) + + +class GridDerivation: + 'Class to hold grid variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.inradius = lineation.getInradius(complex(10.0, 10.0), elementNode) + self.demiwidth = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiwidth', 'width', self.inradius.real) + self.demiheight = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiheight', 'height', self.inradius.imag) + self.density = evaluate.getEvaluatedFloat(0.2, elementNode, 'density') + self.radius = lineation.getComplexByPrefixBeginEnd(elementNode, 'elementRadius', 'elementDiameter', complex(1.0, 1.0)) + self.radius = lineation.getComplexByPrefixBeginEnd(elementNode, 'radius', 'diameter', self.radius) + self.seed = evaluate.getEvaluatedInt(None, elementNode, 'seed') + self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target') + self.typeMenuRadioStrings = 'hexagonal random rectangular'.split() + self.typeString = evaluate.getEvaluatedString('rectangular', elementNode, 'type') + self.zigzag = evaluate.getEvaluatedBoolean(True, elementNode, 'zigzag') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/heightmap.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/heightmap.py new file mode 100644 index 0000000..1f7c908 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/heightmap.py @@ -0,0 +1,208 @@ +""" +Heightmap. +http://www.cs.otago.ac.nz/graphics/Mirage/node59.html +http://en.wikipedia.org/wiki/Heightmap +http://en.wikipedia.org/wiki/Netpbm_format + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +import math +import random + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addHeightsByBitmap(heights, textLines): + 'Add heights by bitmap.' + for line in textLines[3:]: + for integerWord in line.split(): + heights.append(float(integerWord)) + +def addHeightsByGraymap(heights, textLines): + 'Add heights by graymap.' + divisor = float(textLines[3]) + for line in textLines[4:]: + for integerWord in line.split(): + heights.append(float(integerWord) / divisor) + +def getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes): + 'Get and add an indexed heightGrid.' + indexedHeightGrid = [] + for rowIndex, row in enumerate(heightGrid): + indexedRow = [] + indexedHeightGrid.append(indexedRow) + rowOffset = step.imag * float(rowIndex) + minimumXY.imag + for columnIndex, element in enumerate(row): + columnOffset = step.real * float(columnIndex) + minimumXY.real + vector3index = Vector3Index(len(vertexes), columnOffset, rowOffset, top * element) + indexedRow.append(vector3index) + vertexes.append(vector3index) + return indexedHeightGrid + +def getAddIndexedSegmentedPerimeter(heightGrid, maximumXY, minimumXY, step, vertexes, z=0.0): + 'Get and add an indexed segmented perimeter.' + indexedSegmentedPerimeter = [] + firstRow = heightGrid[0] + columnOffset = minimumXY.real + numberOfRowsMinusTwo = len(heightGrid) - 2 + for column in firstRow: + vector3index = Vector3Index(len(vertexes), columnOffset, minimumXY.imag, z) + vertexes.append(vector3index) + indexedSegmentedPerimeter.append(vector3index) + columnOffset += step.real + rowOffset = minimumXY.imag + for rowIndex in xrange(numberOfRowsMinusTwo): + rowOffset += step.imag + vector3index = Vector3Index(len(vertexes), maximumXY.real, rowOffset, z) + vertexes.append(vector3index) + indexedSegmentedPerimeter.append(vector3index) + columnOffset = maximumXY.real + for column in firstRow: + vector3index = Vector3Index(len(vertexes), columnOffset, maximumXY.imag, z) + vertexes.append(vector3index) + indexedSegmentedPerimeter.append(vector3index) + columnOffset -= step.real + rowOffset = maximumXY.imag + for rowIndex in xrange(numberOfRowsMinusTwo): + rowOffset -= step.imag + vector3index = Vector3Index(len(vertexes), minimumXY.real, rowOffset, z) + vertexes.append(vector3index) + indexedSegmentedPerimeter.append(vector3index) + return indexedSegmentedPerimeter + +def getGeometryOutput(elementNode): + 'Get vector3 vertexes from attribute dictionary.' + derivation = HeightmapDerivation(elementNode) + heightGrid = derivation.heightGrid + if derivation.fileName != '': + heightGrid = getHeightGrid(archive.getAbsoluteFolderPath(elementNode.getOwnerDocument().fileName, derivation.fileName)) + return getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + evaluate.setAttributesByArguments(['file', 'start'], arguments, elementNode) + return getGeometryOutput(elementNode) + +def getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid): + 'Get vector3 vertexes from attribute dictionary.' + numberOfColumns = len(heightGrid) + if numberOfColumns < 2: + print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two rows for:') + print(heightGrid) + print(elementNode) + return None + numberOfRows = len(heightGrid[0]) + if numberOfRows < 2: + print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two columns for:') + print(heightGrid) + print(elementNode) + return None + for row in heightGrid: + if len(row) != numberOfRows: + print('Warning, in getGeometryOutputByHeightGrid in heightmap the heightgrid is not rectangular for:') + print(heightGrid) + print(elementNode) + return None + inradiusComplex = derivation.inradius.dropAxis() + minimumXY = -inradiusComplex + step = complex(derivation.inradius.x / float(numberOfRows - 1), derivation.inradius.y / float(numberOfColumns - 1)) + step += step + faces = [] + heightGrid = getRaisedHeightGrid(heightGrid, derivation.start) + top = derivation.inradius.z + derivation.inradius.z + vertexes = [] + indexedBottomLoop = getAddIndexedSegmentedPerimeter(heightGrid, inradiusComplex, minimumXY, step, vertexes) + indexedLoops = [indexedBottomLoop] + indexedGridTop = getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes) + indexedLoops.append(triangle_mesh.getIndexedLoopFromIndexedGrid(indexedGridTop)) + vertexes = triangle_mesh.getUniqueVertexes(indexedLoops + indexedGridTop) + triangle_mesh.addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops) + return triangle_mesh.getGeometryOutputByFacesVertexes(faces, vertexes) + +def getHeightGrid(fileName): + 'Get heightGrid by fileName.' + if 'models/' not in fileName: + print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:') + print(fileName) + print('The heightmap tool can only read a file which has models/ in the file path.') + print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.') + return + pgmText = archive.getFileText(fileName) + textLines = archive.getTextLines(pgmText) + format = textLines[0].lower() + sizeWords = textLines[2].split() + numberOfColumns = int(sizeWords[0]) + numberOfRows = int(sizeWords[1]) + heights = [] + if format == 'p1': + addHeightsByBitmap(heights, textLines) + elif format == 'p2': + addHeightsByGraymap(heights, textLines) + else: + print('Warning, the file format was not recognized for:') + print(fileName) + print('Heightmap can only read the Netpbm Portable bitmap format and the Netpbm Portable graymap format.') + print('The Netpbm formats are described at:') + print('http://en.wikipedia.org/wiki/Netpbm_format') + return [] + heightGrid = [] + heightIndex = 0 + for rowIndex in xrange(numberOfRows): + row = [] + heightGrid.append(row) + for columnIndex in xrange(numberOfColumns): + row.append(heights[heightIndex]) + heightIndex += 1 + return heightGrid + +def getNewDerivation(elementNode): + 'Get new derivation.' + return HeightmapDerivation(elementNode) + +def getRaisedHeightGrid(heightGrid, start): + 'Get heightGrid raised above start.' + raisedHeightGrid = [] + remainingHeight = 1.0 - start + for row in heightGrid: + raisedRow = [] + raisedHeightGrid.append(raisedRow) + for element in row: + raisedElement = remainingHeight * element + start + raisedRow.append(raisedElement) + return raisedHeightGrid + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode)) + + +class HeightmapDerivation: + 'Class to hold heightmap variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.fileName = evaluate.getEvaluatedString('', elementNode, 'file') + self.heightGrid = evaluate.getEvaluatedValue([], elementNode, 'heightGrid') + self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(10.0, 10.0, 5.0)) + self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius) + self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start') + + def __repr__(self): + 'Get the string representation of this HeightmapDerivation.' + return euclidean.getDictionaryString(self.__dict__) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lathe.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lathe.py new file mode 100644 index 0000000..b61cfaa --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lathe.py @@ -0,0 +1,176 @@ +""" +Boolean geometry extrusion. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +def addLoopByComplex(derivation, endMultiplier, loopLists, path, pointComplex, vertexes): + "Add an indexed loop to the vertexes." + loops = loopLists[-1] + loop = [] + loops.append(loop) + for point in path: + pointMinusBegin = point - derivation.axisStart + dotVector3 = derivation.axisProjectiveSpace.getDotVector3(pointMinusBegin) + dotVector3Complex = dotVector3.dropAxis() + dotPointComplex = pointComplex * dotVector3Complex + dotPoint = Vector3(dotPointComplex.real, dotPointComplex.imag, dotVector3.z) + projectedVector3 = derivation.axisProjectiveSpace.getVector3ByPoint(dotPoint) + derivation.axisStart + loop.append(projectedVector3) + +def addNegatives(derivation, negatives, paths): + "Add pillars output to negatives." + for path in paths: + loopListsByPath = getLoopListsByPath(derivation, 1.000001, path) + geometryOutput = triangle_mesh.getPillarsOutput(loopListsByPath) + negatives.append(geometryOutput) + +def addNegativesPositives(derivation, negatives, paths, positives): + "Add pillars output to negatives and positives." + for path in paths: + endMultiplier = None + normal = euclidean.getNormalByPath(path) + if normal.dot(derivation.normal) < 0.0: + endMultiplier = 1.000001 + loopListsByPath = getLoopListsByPath(derivation, endMultiplier, path) + geometryOutput = triangle_mesh.getPillarsOutput(loopListsByPath) + if endMultiplier == None: + positives.append(geometryOutput) + else: + negatives.append(geometryOutput) + +def addOffsetAddToLists( loop, offset, vector3Index, vertexes ): + "Add an indexed loop to the vertexes." + vector3Index += offset + loop.append( vector3Index ) + vertexes.append( vector3Index ) + +def addPositives(derivation, paths, positives): + "Add pillars output to positives." + for path in paths: + loopListsByPath = getLoopListsByPath(derivation, None, path) + geometryOutput = triangle_mesh.getPillarsOutput(loopListsByPath) + positives.append(geometryOutput) + +def getGeometryOutput(derivation, elementNode): + "Get triangle mesh from attribute dictionary." + if derivation == None: + derivation = LatheDerivation(elementNode) + if len(euclidean.getConcatenatedList(derivation.target)) == 0: + print('Warning, in lathe there are no paths.') + print(elementNode.attributes) + return None + negatives = [] + positives = [] + addNegativesPositives(derivation, negatives, derivation.target, positives) + return getGeometryOutputByNegativesPositives(derivation, elementNode, negatives, positives) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get triangle mesh from attribute dictionary by arguments." + return getGeometryOutput(None, elementNode) + +def getGeometryOutputByNegativesPositives(derivation, elementNode, negatives, positives): + "Get triangle mesh from derivation, elementNode, negatives and positives." + positiveOutput = triangle_mesh.getUnifiedOutput(positives) + if len(negatives) < 1: + return solid.getGeometryOutputByManipulation(elementNode, positiveOutput) + return solid.getGeometryOutputByManipulation(elementNode, {'difference' : {'shapes' : [positiveOutput] + negatives}}) + +def getLoopListsByPath(derivation, endMultiplier, path): + "Get loop lists from path." + vertexes = [] + loopLists = [[]] + if len(derivation.loop) < 2: + return loopLists + for pointIndex, pointComplex in enumerate(derivation.loop): + if endMultiplier != None and not derivation.isEndCloseToStart: + if pointIndex == 0: + nextPoint = derivation.loop[1] + pointComplex = endMultiplier * (pointComplex - nextPoint) + nextPoint + elif pointIndex == len(derivation.loop) - 1: + previousPoint = derivation.loop[pointIndex - 1] + pointComplex = endMultiplier * (pointComplex - previousPoint) + previousPoint + addLoopByComplex(derivation, endMultiplier, loopLists, path, pointComplex, vertexes) + if derivation.isEndCloseToStart: + loopLists[-1].append([]) + return loopLists + +def getNewDerivation(elementNode): + 'Get new derivation.' + return LatheDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(None, elementNode)) + + +class LatheDerivation: + "Class to hold lathe variables." + def __init__(self, elementNode): + 'Set defaults.' + self.axisEnd = evaluate.getVector3ByPrefix(None, elementNode, 'axisEnd') + self.axisStart = evaluate.getVector3ByPrefix(None, elementNode, 'axisStart') + self.end = evaluate.getEvaluatedFloat(360.0, elementNode, 'end') + self.loop = evaluate.getTransformedPathByKey([], elementNode, 'loop') + self.sides = evaluate.getEvaluatedInt(None, elementNode, 'sides') + self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start') + self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target') + if len(self.target) < 1: + print('Warning, no target in derive in lathe for:') + print(elementNode) + return + firstPath = self.target[0] + if len(firstPath) < 3: + print('Warning, firstPath length is less than three in derive in lathe for:') + print(elementNode) + self.target = [] + return + if self.axisStart == None: + if self.axisEnd == None: + self.axisStart = firstPath[0] + self.axisEnd = firstPath[-1] + else: + self.axisStart = Vector3() + self.axis = self.axisEnd - self.axisStart + axisLength = abs(self.axis) + if axisLength <= 0.0: + print('Warning, axisLength is zero in derive in lathe for:') + print(elementNode) + self.target = [] + return + self.axis /= axisLength + firstVector3 = firstPath[1] - self.axisStart + firstVector3Length = abs(firstVector3) + if firstVector3Length <= 0.0: + print('Warning, firstVector3Length is zero in derive in lathe for:') + print(elementNode) + self.target = [] + return + firstVector3 /= firstVector3Length + self.axisProjectiveSpace = euclidean.ProjectiveSpace().getByBasisZFirst(self.axis, firstVector3) + if self.sides == None: + distanceToLine = euclidean.getDistanceToLineByPaths(self.axisStart, self.axisEnd, self.target) + self.sides = evaluate.getSidesMinimumThreeBasedOnPrecisionSides(elementNode, distanceToLine) + endRadian = math.radians(self.end) + startRadian = math.radians(self.start) + self.isEndCloseToStart = euclidean.getIsRadianClose(endRadian, startRadian) + if len(self.loop) < 1: + self.loop = euclidean.getComplexPolygonByStartEnd(endRadian, 1.0, self.sides, startRadian) + self.normal = euclidean.getNormalByPath(firstPath) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/line.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/line.py new file mode 100644 index 0000000..c0eae8b --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/line.py @@ -0,0 +1,104 @@ +""" +Square path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = LineDerivation(elementNode) + endMinusStart = derivation.end - derivation.start + endMinusStartLength = abs(endMinusStart) + if endMinusStartLength <= 0.0: + print('Warning, end is the same as start in getGeometryOutput in line for:') + print(derivation.start) + print(derivation.end) + print(elementNode) + return None + typeStringTwoCharacters = derivation.typeString.lower()[: 2] + elementNode.attributes['closed'] = str(derivation.closed) + if derivation.step == None and derivation.steps == None: + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop([derivation.start, derivation.end])) + loop = [derivation.start] + if derivation.step != None and derivation.steps != None: + stepVector = derivation.step / endMinusStartLength * endMinusStart + derivation.end = derivation.start + stepVector * derivation.steps + return getGeometryOutputByStep(elementNode, derivation.end, loop, derivation.steps, stepVector) + if derivation.step == None: + stepVector = endMinusStart / derivation.steps + return getGeometryOutputByStep(elementNode, derivation.end, loop, derivation.steps, stepVector) + endMinusStartLengthOverStep = endMinusStartLength / derivation.step + if typeStringTwoCharacters == 'av': + derivation.steps = max(1.0, round(endMinusStartLengthOverStep)) + stepVector = derivation.step / endMinusStartLength * endMinusStart + derivation.end = derivation.start + stepVector * derivation.steps + return getGeometryOutputByStep(elementNode, derivation.end, loop, derivation.steps, stepVector) + if typeStringTwoCharacters == 'ma': + derivation.steps = math.ceil(endMinusStartLengthOverStep) + if derivation.steps < 1.0: + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop([derivation.start, derivation.end])) + stepVector = endMinusStart / derivation.steps + return getGeometryOutputByStep(elementNode, derivation.end, loop, derivation.steps, stepVector) + if typeStringTwoCharacters == 'mi': + derivation.steps = math.floor(endMinusStartLengthOverStep) + if derivation.steps < 1.0: + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop)) + stepVector = endMinusStart / derivation.steps + return getGeometryOutputByStep(elementNode, derivation.end, loop, derivation.steps, stepVector) + print('Warning, the step type was not one of (average, maximum or minimum) in getGeometryOutput in line for:') + print(derivation.typeString) + print(elementNode) + loop.append(derivation.end) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['start', 'end', 'step'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getGeometryOutputByStep(elementNode, end, loop, steps, stepVector): + "Get line geometry output by the end, loop, steps and stepVector." + stepsFloor = int(math.floor(abs(steps))) + for stepIndex in xrange(1, stepsFloor): + loop.append(loop[stepIndex - 1] + stepVector) + loop.append(end) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop)) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return LineDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class LineDerivation: + "Class to hold line variables." + def __init__(self, elementNode): + 'Set defaults.' + self.closed = evaluate.getEvaluatedBoolean(False, elementNode, 'closed') + self.end = evaluate.getVector3ByPrefix(Vector3(), elementNode, 'end') + self.start = evaluate.getVector3ByPrefix(Vector3(), elementNode, 'start') + self.step = evaluate.getEvaluatedFloat(None, elementNode, 'step') + self.steps = evaluate.getEvaluatedFloat(None, elementNode, 'steps') + self.typeMenuRadioStrings = 'average maximum minimum'.split() + self.typeString = evaluate.getEvaluatedString('minimum', elementNode, 'type') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/linear_bearing_cage.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/linear_bearing_cage.py new file mode 100644 index 0000000..d972f63 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/linear_bearing_cage.py @@ -0,0 +1,247 @@ +""" +Linear bearing cage. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import peg +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.manipulation_matrix import translate +from fabmetheus_utilities.geometry.solids import cylinder +from fabmetheus_utilities.geometry.solids import sphere +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addAssemblyCage(derivation, negatives, positives): + 'Add assembly linear bearing cage.' + addCageGroove(derivation, negatives, positives) + for pegCenterX in derivation.pegCenterXs: + addPositivePeg(derivation, positives, pegCenterX, -derivation.pegY) + addPositivePeg(derivation, positives, pegCenterX, derivation.pegY) + translate.translateNegativesPositives(negatives, positives, Vector3(0.0, -derivation.halfSeparationWidth)) + femaleNegatives = [] + femalePositives = [] + addCageGroove(derivation, femaleNegatives, femalePositives) + for pegCenterX in derivation.pegCenterXs: + addNegativePeg(derivation, femaleNegatives, pegCenterX, -derivation.pegY) + addNegativePeg(derivation, femaleNegatives, pegCenterX, derivation.pegY) + translate.translateNegativesPositives(femaleNegatives, femalePositives, Vector3(0.0, derivation.halfSeparationWidth)) + negatives += femaleNegatives + positives += femalePositives + +def addCage(derivation, height, negatives, positives): + 'Add linear bearing cage.' + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(), Vector3(0.0, 0.0, height)] + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + roundedExtendedRectangle = getRoundedExtendedRectangle(derivation.demiwidth, derivation.rectangleCenterX, 14) + outsidePath = euclidean.getVector3Path(roundedExtendedRectangle) + extrude.addPositives(extrudeDerivation, [outsidePath], positives) + for bearingCenterX in derivation.bearingCenterXs: + addNegativeSphere(derivation, negatives, bearingCenterX) + +def addCageGroove(derivation, negatives, positives): + 'Add cage and groove.' + addCage(derivation, derivation.demiheight, negatives, positives) + addGroove(derivation, negatives) + +def addGroove(derivation, negatives): + 'Add groove on each side of cage.' + copyShallow = derivation.elementNode.getCopyShallow() + extrude.setElementNodeToEndStart(copyShallow, Vector3(-derivation.demilength), Vector3(derivation.demilength)) + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + bottom = derivation.demiheight - 0.5 * derivation.grooveWidth + outside = derivation.demiwidth + top = derivation.demiheight + leftGroove = [ + complex(-outside, bottom), + complex(-derivation.innerDemiwidth, derivation.demiheight), + complex(-outside, top)] + rightGroove = [ + complex(outside, top), + complex(derivation.innerDemiwidth, derivation.demiheight), + complex(outside, bottom)] + extrude.addNegatives(extrudeDerivation, negatives, euclidean.getVector3Paths([leftGroove, rightGroove])) + +def addNegativePeg(derivation, negatives, x, y): + 'Add negative cylinder at x and y.' + negativePegRadius = derivation.pegRadiusArealized + derivation.halfPegClearance + inradius = complex(negativePegRadius, negativePegRadius) + copyShallow = derivation.elementNode.getCopyShallow() + start = Vector3(x, y, derivation.height) + sides = evaluate.getSidesMinimumThreeBasedOnPrecision(copyShallow, negativePegRadius) + cylinder.addCylinderOutputByEndStart(0.0, inradius, negatives, sides, start, derivation.topOverBottom) + +def addNegativeSphere(derivation, negatives, x): + 'Add negative sphere at x.' + radius = Vector3(derivation.radiusPlusClearance, derivation.radiusPlusClearance, derivation.radiusPlusClearance) + sphereOutput = sphere.getGeometryOutput(derivation.elementNode.getCopyShallow(), radius) + euclidean.translateVector3Path(matrix.getVertexes(sphereOutput), Vector3(x, 0.0, derivation.demiheight)) + negatives.append(sphereOutput) + +def addPositivePeg(derivation, positives, x, y): + 'Add positive cylinder at x and y.' + positivePegRadius = derivation.pegRadiusArealized - derivation.halfPegClearance + radiusArealized = complex(positivePegRadius, positivePegRadius) + copyShallow = derivation.elementNode.getCopyShallow() + start = Vector3(x, y, derivation.demiheight) + endZ = derivation.height + peg.addPegOutput(derivation.pegBevel, endZ, positives, radiusArealized, derivation.sides, start, derivation.topOverBottom) + +def getBearingCenterXs(bearingCenterX, numberOfSteps, stepX): + 'Get the bearing center x list.' + bearingCenterXs = [] + for stepIndex in xrange(numberOfSteps + 1): + bearingCenterXs.append(bearingCenterX) + bearingCenterX += stepX + return bearingCenterXs + +def getGeometryOutput(elementNode): + 'Get vector3 vertexes from attribute dictionary.' + derivation = LinearBearingCageDerivation(elementNode) + negatives = [] + positives = [] + if derivation.typeStringFirstCharacter == 'a': + addAssemblyCage(derivation, negatives, positives) + else: + addCage(derivation, derivation.height, negatives, positives) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + evaluate.setAttributesByArguments(['length', 'radius'], arguments, elementNode) + return getGeometryOutput(elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return LinearBearingCageDerivation(elementNode) + +def getPegCenterXs(numberOfSteps, pegCenterX, stepX): + 'Get the peg center x list.' + pegCenterXs = [] + for stepIndex in xrange(numberOfSteps): + pegCenterXs.append(pegCenterX) + pegCenterX += stepX + return pegCenterXs + +def getRoundedExtendedRectangle(radius, rectangleCenterX, sides): + 'Get the rounded extended rectangle.' + roundedExtendedRectangle = [] + halfSides = int(sides / 2) + halfSidesPlusOne = abs(halfSides + 1) + sideAngle = math.pi / float(halfSides) + extensionMultiplier = 1.0 / math.cos(0.5 * sideAngle) + center = complex(rectangleCenterX, 0.0) + startAngle = 0.5 * math.pi + for halfSide in xrange(halfSidesPlusOne): + unitPolar = euclidean.getWiddershinsUnitPolar(startAngle) + unitPolarExtended = complex(unitPolar.real * extensionMultiplier, unitPolar.imag) + roundedExtendedRectangle.append(unitPolarExtended * radius + center) + startAngle += sideAngle + center = complex(-rectangleCenterX, 0.0) + startAngle = -0.5 * math.pi + for halfSide in xrange(halfSidesPlusOne): + unitPolar = euclidean.getWiddershinsUnitPolar(startAngle) + unitPolarExtended = complex(unitPolar.real * extensionMultiplier, unitPolar.imag) + roundedExtendedRectangle.append(unitPolarExtended * radius + center) + startAngle += sideAngle + return roundedExtendedRectangle + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode)) + + +class LinearBearingCageDerivation: + 'Class to hold linear bearing cage variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.length = evaluate.getEvaluatedFloat(50.0, elementNode, 'length') + self.demilength = 0.5 * self.length + self.elementNode = elementNode + self.radius = lineation.getFloatByPrefixBeginEnd(elementNode, 'radius', 'diameter', 5.0) + self.cageClearanceOverRadius = evaluate.getEvaluatedFloat(0.05, elementNode, 'cageClearanceOverRadius') + self.cageClearance = self.cageClearanceOverRadius * self.radius + self.cageClearance = evaluate.getEvaluatedFloat(self.cageClearance, elementNode, 'cageClearance') + self.racewayClearanceOverRadius = evaluate.getEvaluatedFloat(0.1, elementNode, 'racewayClearanceOverRadius') + self.racewayClearance = self.racewayClearanceOverRadius * self.radius + self.racewayClearance = evaluate.getEvaluatedFloat(self.racewayClearance, elementNode, 'racewayClearance') + self.typeMenuRadioStrings = 'assembly integral'.split() + self.typeString = evaluate.getEvaluatedString('assembly', elementNode, 'type') + self.typeStringFirstCharacter = self.typeString[: 1 ].lower() + self.wallThicknessOverRadius = evaluate.getEvaluatedFloat(0.5, elementNode, 'wallThicknessOverRadius') + self.wallThickness = self.wallThicknessOverRadius * self.radius + self.wallThickness = evaluate.getEvaluatedFloat(self.wallThickness, elementNode, 'wallThickness') + self.zenithAngle = evaluate.getEvaluatedFloat(45.0, elementNode, 'zenithAngle') + self.zenithRadian = math.radians(self.zenithAngle) + self.demiheight = self.radius * math.cos(self.zenithRadian) - self.racewayClearance + self.height = self.demiheight + self.demiheight + self.radiusPlusClearance = self.radius + self.cageClearance + self.cageRadius = self.radiusPlusClearance + self.wallThickness + self.demiwidth = self.cageRadius + self.bearingCenterX = self.cageRadius - self.demilength + separation = self.cageRadius + self.radiusPlusClearance + bearingLength = -self.bearingCenterX - self.bearingCenterX + self.numberOfSteps = int(math.floor(bearingLength / separation)) + self.stepX = bearingLength / float(self.numberOfSteps) + self.bearingCenterXs = getBearingCenterXs(self.bearingCenterX, self.numberOfSteps, self.stepX) + if self.typeStringFirstCharacter == 'a': + self.setAssemblyCage() + self.rectangleCenterX = self.demiwidth - self.demilength + + def setAssemblyCage(self): + 'Set two piece assembly parameters.' + self.grooveDepthOverRadius = evaluate.getEvaluatedFloat(0.15, self.elementNode, 'grooveDepthOverRadius') + self.grooveDepth = self.grooveDepthOverRadius * self.radius + self.grooveDepth = evaluate.getEvaluatedFloat(self.grooveDepth, self.elementNode, 'grooveDepth') + self.grooveWidthOverRadius = evaluate.getEvaluatedFloat(0.6, self.elementNode, 'grooveWidthOverRadius') + self.grooveWidth = self.grooveWidthOverRadius * self.radius + self.grooveWidth = evaluate.getEvaluatedFloat(self.grooveWidth, self.elementNode, 'grooveWidth') + self.pegClearanceOverRadius = evaluate.getEvaluatedFloat(0.0, self.elementNode, 'pegClearanceOverRadius') + self.pegClearance = self.pegClearanceOverRadius * self.radius + self.pegClearance = evaluate.getEvaluatedFloat(self.pegClearance, self.elementNode, 'pegClearance') + self.halfPegClearance = 0.5 * self.pegClearance + self.pegRadiusOverRadius = evaluate.getEvaluatedFloat(0.5, self.elementNode, 'pegRadiusOverRadius') + self.pegRadius = self.pegRadiusOverRadius * self.radius + self.pegRadius = evaluate.getEvaluatedFloat(self.pegRadius, self.elementNode, 'pegRadius') + self.sides = evaluate.getSidesMinimumThreeBasedOnPrecision(self.elementNode, self.pegRadius) + self.pegRadiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(self.elementNode, self.pegRadius, self.sides) + self.pegBevelOverPegRadius = evaluate.getEvaluatedFloat(0.25, self.elementNode, 'pegBevelOverPegRadius') + self.pegBevel = self.pegBevelOverPegRadius * self.pegRadiusArealized + self.pegBevel = evaluate.getEvaluatedFloat(self.pegBevel, self.elementNode, 'pegBevel') + self.pegMaximumRadius = self.pegRadiusArealized + abs(self.halfPegClearance) + self.separationOverRadius = evaluate.getEvaluatedFloat(0.5, self.elementNode, 'separationOverRadius') + self.separation = self.separationOverRadius * self.radius + self.separation = evaluate.getEvaluatedFloat(self.separation, self.elementNode, 'separation') + self.topOverBottom = evaluate.getEvaluatedFloat(0.8, self.elementNode, 'topOverBottom') + peg.setTopOverBottomByRadius(self, 0.0, self.pegRadiusArealized, self.height) + self.quarterHeight = 0.5 * self.demiheight + self.pegY = 0.5 * self.wallThickness + self.pegMaximumRadius + cagePegRadius = self.cageRadius + self.pegMaximumRadius + halfStepX = 0.5 * self.stepX + pegHypotenuse = math.sqrt(self.pegY * self.pegY + halfStepX * halfStepX) + if cagePegRadius > pegHypotenuse: + self.pegY = math.sqrt(cagePegRadius * cagePegRadius - halfStepX * halfStepX) + self.demiwidth = max(self.pegY + self.pegMaximumRadius + self.wallThickness, self.demiwidth) + self.innerDemiwidth = self.demiwidth + self.demiwidth += self.grooveDepth + self.halfSeparationWidth = self.demiwidth + 0.5 * self.separation + if self.pegRadiusArealized <= 0.0: + self.pegCenterXs = [] + else: + self.pegCenterXs = getPegCenterXs(self.numberOfSteps, self.bearingCenterX + halfStepX, self.stepX) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lineation.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lineation.py new file mode 100644 index 0000000..29f7bbb --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/lineation.py @@ -0,0 +1,308 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getComplexByDictionary(dictionary, valueComplex): + 'Get complex by dictionary.' + if 'x' in dictionary: + valueComplex = complex(euclidean.getFloatFromValue(dictionary['x']),valueComplex.imag) + if 'y' in dictionary: + valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(dictionary['y'])) + return valueComplex + +def getComplexByDictionaryListValue(value, valueComplex): + 'Get complex by dictionary, list or value.' + if value.__class__ == complex: + return value + if value.__class__ == dict: + return getComplexByDictionary(value, valueComplex) + if value.__class__ == list: + return getComplexByFloatList(value, valueComplex) + floatFromValue = euclidean.getFloatFromValue(value) + if floatFromValue == None: + return valueComplex + return complex( floatFromValue, floatFromValue ) + +def getComplexByFloatList( floatList, valueComplex ): + 'Get complex by float list.' + if len(floatList) > 0: + valueComplex = complex(euclidean.getFloatFromValue(floatList[0]), valueComplex.imag) + if len(floatList) > 1: + valueComplex = complex(valueComplex.real, euclidean.getFloatFromValue(floatList[1])) + return valueComplex + +def getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex): + 'Get complex from multiplier, prefix and xml element.' + if multiplier == 0.0: + return valueComplex + oldMultipliedValueComplex = valueComplex * multiplier + complexByPrefix = getComplexByPrefix(elementNode, prefix, oldMultipliedValueComplex) + if complexByPrefix == oldMultipliedValueComplex: + return valueComplex + return complexByPrefix / multiplier + +def getComplexByMultiplierPrefixes(elementNode, multiplier, prefixes, valueComplex): + 'Get complex from multiplier, prefixes and xml element.' + for prefix in prefixes: + valueComplex = getComplexByMultiplierPrefix(elementNode, multiplier, prefix, valueComplex) + return valueComplex + +def getComplexByPrefix(elementNode, prefix, valueComplex): + 'Get complex from prefix and xml element.' + value = evaluate.getEvaluatedValue(None, elementNode, prefix) + if value != None: + valueComplex = getComplexByDictionaryListValue(value, valueComplex) + x = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.x') + if x != None: + valueComplex = complex( x, getComplexIfNone( valueComplex ).imag ) + y = evaluate.getEvaluatedFloat(None, elementNode, prefix + '.y') + if y != None: + valueComplex = complex( getComplexIfNone( valueComplex ).real, y ) + return valueComplex + +def getComplexByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueComplex): + 'Get complex from element node, prefixBegin and prefixEnd.' + valueComplex = getComplexByPrefix(elementNode, prefixBegin, valueComplex) + if prefixEnd in elementNode.attributes: + return 0.5 * getComplexByPrefix(elementNode, valueComplex + valueComplex, prefixEnd) + else: + return valueComplex + +def getComplexByPrefixes(elementNode, prefixes, valueComplex): + 'Get complex from prefixes and xml element.' + for prefix in prefixes: + valueComplex = getComplexByPrefix(elementNode, prefix, valueComplex) + return valueComplex + +def getComplexIfNone( valueComplex ): + 'Get new complex if the original complex is none.' + if valueComplex == None: + return complex() + return valueComplex + +def getFloatByPrefixBeginEnd(elementNode, prefixBegin, prefixEnd, valueFloat): + 'Get float from prefixBegin, prefixEnd and xml element.' + valueFloat = evaluate.getEvaluatedFloat(valueFloat, elementNode, prefixBegin) + if prefixEnd in elementNode.attributes: + return 0.5 * evaluate.getEvaluatedFloat(valueFloat + valueFloat, elementNode, prefixEnd) + return valueFloat + +def getFloatByPrefixSide(defaultValue, elementNode, prefix, side): + 'Get float by prefix and side.' + if elementNode == None: + return defaultValue + if side != None: + key = prefix + 'OverSide' + if key in elementNode.attributes: + defaultValue = euclidean.getFloatFromValue(evaluate.getEvaluatedValueObliviously(elementNode, key)) * side + return evaluate.getEvaluatedFloat(defaultValue, elementNode, prefix) + +def getGeometryOutput(derivation, elementNode): + 'Get geometry output from paths.' + if derivation == None: + derivation = LineationDerivation(elementNode) + geometryOutput = [] + for path in derivation.target: + sideLoop = SideLoop(path) + geometryOutput += getGeometryOutputByLoop(elementNode, sideLoop) + return geometryOutput + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + return getGeometryOutput(None, elementNode) + +def getGeometryOutputByLoop(elementNode, sideLoop): + 'Get geometry output by side loop.' + sideLoop.rotate(elementNode) + return getGeometryOutputByManipulation(elementNode, sideLoop) + +def getGeometryOutputByManipulation(elementNode, sideLoop): + 'Get geometry output by manipulation.' + sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop ) + return sideLoop.getManipulationPluginLoops(elementNode) + +def getInradius(defaultInradius, elementNode): + 'Get inradius.' + defaultInradius = getComplexByPrefixes(elementNode, ['demisize', 'inradius'], defaultInradius) + return getComplexByMultiplierPrefix(elementNode, 2.0, 'size', defaultInradius) + +def getMinimumRadius(beginComplexSegmentLength, endComplexSegmentLength, radius): + 'Get minimum radius.' + return min(abs(radius), 0.5 * min(beginComplexSegmentLength, endComplexSegmentLength)) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return LineationDerivation(elementNode) + +def getNumberOfBezierPoints(begin, elementNode, end): + 'Get the numberOfBezierPoints.' + numberOfBezierPoints = int(math.ceil(0.5 * evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, abs(end - begin)))) + return evaluate.getEvaluatedInt(numberOfBezierPoints, elementNode, 'sides') + +def getPackedGeometryOutputByLoop(elementNode, sideLoop): + 'Get packed geometry output by side loop.' + sideLoop.rotate(elementNode) + return getGeometryOutputByManipulation(elementNode, sideLoop) + +def getRadiusAverage(radiusComplex): + 'Get average radius from radiusComplex.' + return math.sqrt(radiusComplex.real * radiusComplex.imag) + +def getRadiusComplex(elementNode, radius): + 'Get radius complex for elementNode.' + radius = getComplexByPrefixes(elementNode, ['demisize', 'radius'], radius) + return getComplexByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], radius) + +def getStrokeRadiusByPrefix(elementNode, prefix): + 'Get strokeRadius by prefix.' + strokeRadius = getFloatByPrefixBeginEnd(elementNode, prefix + 'strokeRadius', prefix + 'strokeWidth', 1.0) + return getFloatByPrefixBeginEnd(elementNode, prefix + 'radius', prefix + 'diameter', strokeRadius) + +def processElementNode(elementNode): + 'Process the xml element.' + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + +def processElementNodeByFunction(elementNode, manipulationFunction): + 'Process the xml element by the manipulationFunction.' + elementAttributesCopy = elementNode.attributes.copy() + targets = evaluate.getElementNodesByKey(elementNode, 'target') + for target in targets: + targetAttributesCopy = target.attributes.copy() + target.attributes = elementAttributesCopy + processTargetByFunction(manipulationFunction, target) + target.attributes = targetAttributesCopy + +def processTargetByFunction(manipulationFunction, target): + 'Process the target by the manipulationFunction.' + if target.xmlObject == None: + print('Warning, there is no object in processTargetByFunction in lineation for:') + print(target) + return + geometryOutput = [] + transformedPaths = target.xmlObject.getTransformedPaths() + for transformedPath in transformedPaths: + sideLoop = SideLoop(transformedPath) + sideLoop.rotate(target) + sideLoop.loop = euclidean.getLoopWithoutCloseSequentialPoints( sideLoop.close, sideLoop.loop ) + geometryOutput += manipulationFunction(sideLoop.close, target, sideLoop.loop, '', sideLoop.sideLength) + if len(geometryOutput) < 1: + print('Warning, there is no geometryOutput in processTargetByFunction in lineation for:') + print(target) + return + removeChildNodesFromElementObject(target) + path.convertElementNode(target, geometryOutput) + +def removeChildNodesFromElementObject(elementNode): + 'Process the xml element by manipulationFunction.' + elementNode.removeChildNodesFromIDNameParent() + if elementNode.xmlObject != None: + if elementNode.parentNode.xmlObject != None: + if elementNode.xmlObject in elementNode.parentNode.xmlObject.archivableObjects: + elementNode.parentNode.xmlObject.archivableObjects.remove(elementNode.xmlObject) + +def setClosedAttribute(elementNode, revolutions): + 'Set the closed attribute of the elementNode.' + closedBoolean = evaluate.getEvaluatedBoolean(revolutions <= 1, elementNode, 'closed') + elementNode.attributes['closed'] = str(closedBoolean).lower() + + +class LineationDerivation: + 'Class to hold lineation variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target') + + +class SideLoop: + 'Class to handle loop, side angle and side length.' + def __init__(self, loop, sideAngle=None, sideLength=None): + 'Initialize.' + if sideAngle == None: + if len(loop) > 0: + sideAngle = 2.0 * math.pi / float(len(loop)) + else: + sideAngle = 1.0 + print('Warning, loop has no sides in SideLoop in lineation.') + if sideLength == None: + if len(loop) > 0: + sideLength = euclidean.getLoopLength(loop) / float(len(loop)) + else: + sideLength = 1.0 + print('Warning, loop has no length in SideLoop in lineation.') + self.loop = loop + self.sideAngle = abs(sideAngle) + self.sideLength = abs(sideLength) + self.close = 0.001 * sideLength + + def getManipulationPluginLoops(self, elementNode): + 'Get loop manipulated by the plugins in the manipulation paths folder.' + xmlProcessor = elementNode.getXMLProcessor() + matchingPlugins = evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationMatrixDictionary) + matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationPathDictionary) + matchingPlugins += evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationShapeDictionary) + matchingPlugins.sort(evaluate.compareExecutionOrderAscending) + loops = [self.loop] + for matchingPlugin in matchingPlugins: + matchingLoops = [] + prefix = matchingPlugin.__name__.replace('_', '') + '.' + for loop in loops: + matchingLoops += matchingPlugin.getManipulatedPaths(self.close, elementNode, loop, prefix, self.sideLength) + loops = matchingLoops + return loops + + def rotate(self, elementNode): + 'Rotate.' + rotation = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, 'rotation')) + rotation += evaluate.getEvaluatedFloat(0.0, elementNode, 'rotationOverSide') * self.sideAngle + if rotation != 0.0: + planeRotation = euclidean.getWiddershinsUnitPolar( rotation ) + for vertex in self.loop: + rotatedComplex = vertex.dropAxis() * planeRotation + vertex.x = rotatedComplex.real + vertex.y = rotatedComplex.imag + if 'clockwise' in elementNode.attributes: + isClockwise = euclidean.getBooleanFromValue(evaluate.getEvaluatedValueObliviously(elementNode, 'clockwise')) + if isClockwise == euclidean.getIsWiddershinsByVector3( self.loop ): + self.loop.reverse() + + +class Spiral: + 'Class to add a spiral.' + def __init__(self, spiral, stepRatio): + 'Initialize.' + self.spiral = spiral + if self.spiral == None: + return + self.spiralIncrement = self.spiral * stepRatio + self.spiralTotal = Vector3() + + def __repr__(self): + 'Get the string representation of this Spiral.' + return self.spiral + + def getSpiralPoint(self, unitPolar, vector3): + 'Add spiral to the vector.' + if self.spiral == None: + return vector3 + vector3 += Vector3(unitPolar.real * self.spiralTotal.x, unitPolar.imag * self.spiralTotal.y, self.spiralTotal.z) + self.spiralTotal += self.spiralIncrement + return vector3 diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/mechaslab.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/mechaslab.py new file mode 100644 index 0000000..4a9922e --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/mechaslab.py @@ -0,0 +1,259 @@ +""" +Mechaslab. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import peg +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.manipulation_matrix import translate +from fabmetheus_utilities.geometry.solids import cylinder +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addAlongWay(begin, distance, end, loop): + 'Get the beveled rectangle.' + endMinusBegin = end - begin + endMinusBeginLength = abs(endMinusBegin) + if endMinusBeginLength <= 0.0: + return + alongWayMultiplier = distance / endMinusBeginLength + loop.append(begin + alongWayMultiplier * endMinusBegin) + +def addGroove(derivation, negatives): + 'Add groove on each side of cage.' + copyShallow = derivation.elementNode.getCopyShallow() + extrude.setElementNodeToEndStart(copyShallow, Vector3(-derivation.demilength), Vector3(derivation.demilength)) + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + bottom = derivation.demiheight - 0.5 * derivation.grooveWidth + outside = derivation.demiwidth + top = derivation.demiheight + leftGroove = [ + complex(-outside, bottom), + complex(-derivation.innerDemiwidth, derivation.demiheight), + complex(-outside, top)] + rightGroove = [ + complex(outside, top), + complex(derivation.innerDemiwidth, derivation.demiheight), + complex(outside, bottom)] + groovesComplex = [leftGroove, rightGroove] + groovesVector3 = euclidean.getVector3Paths(groovesComplex) + extrude.addPositives(extrudeDerivation, groovesVector3, negatives) + +def addHollowPegSocket(derivation, hollowPegSocket, negatives, positives): + 'Add the socket and hollow peg.' + pegHeight = derivation.pegHeight + pegRadians = derivation.pegRadians + pegRadiusComplex = complex(derivation.pegRadiusArealized, derivation.pegRadiusArealized) + pegTip = 0.8 * derivation.pegRadiusArealized + sides = derivation.pegSides + start = Vector3(hollowPegSocket.center.real, hollowPegSocket.center.imag, derivation.height) + tinyHeight = 0.0001 * pegHeight + topRadians = 0.25 * math.pi + boltTop = derivation.height + if hollowPegSocket.shouldAddPeg: + boltTop = peg.getTopAddBiconicOutput( + pegRadians, pegHeight, positives, pegRadiusComplex, sides, start, pegTip, topRadians) + sides = derivation.socketSides + socketHeight = 1.05 * derivation.pegHeight + socketRadiusComplex = complex(derivation.socketRadiusArealized, derivation.socketRadiusArealized) + socketTip = 0.5 * derivation.overhangSpan + start = Vector3(hollowPegSocket.center.real, hollowPegSocket.center.imag, -tinyHeight) + topRadians = derivation.interiorOverhangRadians + if hollowPegSocket.shouldAddSocket: + peg.getTopAddBiconicOutput(pegRadians, socketHeight, negatives, socketRadiusComplex, sides, start, socketTip, topRadians) + if derivation.boltRadius <= 0.0: + return + if (not hollowPegSocket.shouldAddPeg) and (not hollowPegSocket.shouldAddSocket): + return + boltRadiusComplex = complex(derivation.boltRadius, derivation.boltRadius) + cylinder.addCylinderOutputByEndStart(boltTop + tinyHeight, boltRadiusComplex, negatives, derivation.boltSides, start) + +def addSlab(derivation, positives): + 'Add slab.' + copyShallow = derivation.elementNode.getCopyShallow() + copyShallow.attributes['path'] = [Vector3(), Vector3(0.0, 0.0, derivation.height)] + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + beveledRectangle = getBeveledRectangle(derivation.bevel, -derivation.topRight) + outsidePath = euclidean.getVector3Path(beveledRectangle) + extrude.addPositives(extrudeDerivation, [outsidePath], positives) + +def addXGroove(derivation, negatives, y): + 'Add x groove.' + if derivation.topBevel <= 0.0: + return + bottom = derivation.height - derivation.topBevel + top = derivation.height + groove = [complex(y, bottom), complex(y - derivation.topBevel, top), complex(y + derivation.topBevel, top)] + triangle_mesh.addSymmetricXPath(negatives, groove, 1.0001 * derivation.topRight.real) + +def addYGroove(derivation, negatives, x): + 'Add y groove' + if derivation.topBevel <= 0.0: + return + bottom = derivation.height - derivation.topBevel + top = derivation.height + groove = [complex(x, bottom), complex(x - derivation.topBevel, top), complex(x + derivation.topBevel, top)] + triangle_mesh.addSymmetricYPath(negatives, groove, 1.0001 * derivation.topRight.imag) + +def getBeveledRectangle(bevel, bottomLeft): + 'Get the beveled rectangle.' + bottomRight = complex(-bottomLeft.real, bottomLeft.imag) + rectangle = [bottomLeft, bottomRight, -bottomLeft, -bottomRight] + if bevel <= 0.0: + return rectangle + beveledRectangle = [] + for pointIndex, point in enumerate(rectangle): + begin = rectangle[(pointIndex + len(rectangle) - 1) % len(rectangle)] + end = rectangle[(pointIndex + 1) % len(rectangle)] + addAlongWay(point, bevel, begin, beveledRectangle) + addAlongWay(point, bevel, end, beveledRectangle) + return beveledRectangle + +def getGeometryOutput(elementNode): + 'Get vector3 vertexes from attribute dictionary.' + derivation = MechaslabDerivation(elementNode) + negatives = [] + positives = [] + addSlab(derivation, positives) + for hollowPegSocket in derivation.hollowPegSockets: + addHollowPegSocket(derivation, hollowPegSocket, negatives, positives) + if 's' in derivation.topBevelPositions: + addXGroove(derivation, negatives, -derivation.topRight.imag) + if 'n' in derivation.topBevelPositions: + addXGroove(derivation, negatives, derivation.topRight.imag) + if 'w' in derivation.topBevelPositions: + addYGroove(derivation, negatives, -derivation.topRight.real) + if 'e' in derivation.topBevelPositions: + addYGroove(derivation, negatives, derivation.topRight.real) + return extrude.getGeometryOutputByNegativesPositives(elementNode, negatives, positives) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + evaluate.setAttributesByArguments(['length', 'radius'], arguments, elementNode) + return getGeometryOutput(elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return MechaslabDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode)) + + +class CellExistence: + 'Class to determine if a cell exists.' + def __init__(self, columns, rows, value): + 'Initialize.' + self.existenceSet = None + if value == None: + return + self.existenceSet = set() + for element in value: + if element.__class__ == int: + columnIndex = (element + columns) % columns + for rowIndex in xrange(rows): + keyTuple = (columnIndex, rowIndex) + self.existenceSet.add(keyTuple) + else: + keyTuple = (element[0], element[1]) + self.existenceSet.add(keyTuple) + + def __repr__(self): + 'Get the string representation of this CellExistence.' + return euclidean.getDictionaryString(self.__dict__) + + def getIsInExistence(self, columnIndex, rowIndex): + 'Detremine if the cell at the column and row exists.' + if self.existenceSet == None: + return True + return (columnIndex, rowIndex) in self.existenceSet + + +class HollowPegSocket: + 'Class to hold hollow peg socket variables.' + def __init__(self, center): + 'Initialize.' + self.center = center + self.shouldAddPeg = True + self.shouldAddSocket = True + + def __repr__(self): + 'Get the string representation of this HollowPegSocket.' + return euclidean.getDictionaryString(self.__dict__) + + +class MechaslabDerivation: + 'Class to hold mechaslab variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.bevelOverRadius = evaluate.getEvaluatedFloat(0.2, elementNode, 'bevelOverRadius') + self.boltRadiusOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'boltRadiusOverRadius') + self.columns = evaluate.getEvaluatedInt(2, elementNode, 'columns') + self.elementNode = elementNode + self.heightOverRadius = evaluate.getEvaluatedFloat(2.0, elementNode, 'heightOverRadius') + self.interiorOverhangRadians = setting.getInteriorOverhangRadians(elementNode) + self.overhangSpan = setting.getOverhangSpan(elementNode) + self.pegClearanceOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'pegClearanceOverRadius') + self.pegRadians = math.radians(evaluate.getEvaluatedFloat(2.0, elementNode, 'pegAngle')) + self.pegHeightOverHeight = evaluate.getEvaluatedFloat(0.4, elementNode, 'pegHeightOverHeight') + self.pegRadiusOverRadius = evaluate.getEvaluatedFloat(0.7, elementNode, 'pegRadiusOverRadius') + self.radius = lineation.getFloatByPrefixBeginEnd(elementNode, 'radius', 'width', 5.0) + self.rows = evaluate.getEvaluatedInt(1, elementNode, 'rows') + self.topBevelOverRadius = evaluate.getEvaluatedFloat(0.2, elementNode, 'topBevelOverRadius') + # Set derived values. + self.bevel = evaluate.getEvaluatedFloat(self.bevelOverRadius * self.radius, elementNode, 'bevel') + self.boltRadius = evaluate.getEvaluatedFloat(self.boltRadiusOverRadius * self.radius, elementNode, 'boltRadius') + self.boltSides = evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, self.boltRadius) + self.bottomLeftCenter = complex(-float(self.columns - 1), -float(self.rows - 1)) * self.radius + self.height = evaluate.getEvaluatedFloat(self.heightOverRadius * self.radius, elementNode, 'height') + self.hollowPegSockets = [] + centerY = self.bottomLeftCenter.imag + diameter = self.radius + self.radius + self.pegExistence = CellExistence(self.columns, self.rows, evaluate.getEvaluatedValue(None, elementNode, 'pegs')) + self.socketExistence = CellExistence(self.columns, self.rows, evaluate.getEvaluatedValue(None, elementNode, 'sockets')) + for rowIndex in xrange(self.rows): + centerX = self.bottomLeftCenter.real + for columnIndex in xrange(self.columns): + hollowPegSocket = HollowPegSocket(complex(centerX, centerY)) + hollowPegSocket.shouldAddPeg = self.pegExistence.getIsInExistence(columnIndex, rowIndex) + hollowPegSocket.shouldAddSocket = self.socketExistence.getIsInExistence(columnIndex, rowIndex) + self.hollowPegSockets.append(hollowPegSocket) + centerX += diameter + centerY += diameter + self.pegClearance = evaluate.getEvaluatedFloat(self.pegClearanceOverRadius * self.radius, elementNode, 'pegClearance') + halfPegClearance = 0.5 * self.pegClearance + self.pegHeight = evaluate.getEvaluatedFloat(self.pegHeightOverHeight * self.height, elementNode, 'pegHeight') + self.pegRadius = evaluate.getEvaluatedFloat(self.pegRadiusOverRadius * self.radius, elementNode, 'pegRadius') + sides = 24 * max(1, math.floor(evaluate.getSidesBasedOnPrecision(elementNode, self.pegRadius) / 24)) + self.socketRadius = self.pegRadius + halfPegClearance + self.pegSides = evaluate.getEvaluatedInt(sides, elementNode, 'pegSides') + self.pegRadius -= halfPegClearance + self.pegRadiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNode, self.pegRadius, self.pegSides) + self.socketSides = evaluate.getEvaluatedInt(sides, elementNode, 'socketSides') + self.socketRadiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNode, self.socketRadius, self.socketSides) + self.topBevel = evaluate.getEvaluatedFloat(self.topBevelOverRadius * self.radius, elementNode, 'topBevel') + self.topBevelPositions = evaluate.getEvaluatedString('nwse', elementNode, 'topBevelPositions').lower() + self.topRight = complex(float(self.columns), float(self.rows)) * self.radius + + def __repr__(self): + 'Get the string representation of this MechaslabDerivation.' + return euclidean.getDictionaryString(self.__dict__) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/peg.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/peg.py new file mode 100644 index 0000000..01f3126 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/peg.py @@ -0,0 +1,103 @@ +""" +Peg. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import cylinder +from fabmetheus_utilities.vector3 import Vector3 +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + + +def addPegOutput(bevel, endZ, outputs, radiusArealized, sides, start, topOverBottom): + 'Add beveled cylinder to outputs given bevel, endZ, radiusArealized and start.' + height = abs(start.z - endZ) + bevelStartRatio = max(1.0 - bevel / height, 0.5) + oneMinusBevelStartRatio = 1.0 - bevelStartRatio + trunkEndZ = bevelStartRatio * endZ + oneMinusBevelStartRatio * start.z + trunkTopOverBottom = bevelStartRatio * topOverBottom + oneMinusBevelStartRatio + cylinder.addCylinderOutputByEndStart(trunkEndZ, radiusArealized, outputs, sides, start, trunkTopOverBottom) + capRadius = radiusArealized * trunkTopOverBottom + capStart = bevelStartRatio * Vector3(start.x, start.y, endZ) + oneMinusBevelStartRatio * start + radiusMaximum = max(radiusArealized.real, radiusArealized.imag) + endRadiusMaximum = radiusMaximum * topOverBottom - bevel + trunkRadiusMaximum = radiusMaximum * trunkTopOverBottom + capTopOverBottom = endRadiusMaximum / trunkRadiusMaximum + cylinder.addCylinderOutputByEndStart(endZ, capRadius, outputs, sides, capStart, capTopOverBottom) + +def getGeometryOutput(derivation, elementNode): + 'Get vector3 vertexes from attribute dictionary.' + if derivation == None: + derivation = PegDerivation(elementNode) + positives = [] + radiusArealized = complex(derivation.radiusArealized, derivation.radiusArealized) + addPegOutput(derivation.bevel, derivation.endZ, positives, radiusArealized, derivation.sides, derivation.start, derivation.topOverBottom) + return extrude.getGeometryOutputByNegativesPositives(elementNode, [], positives) + +def getGeometryOutputByArguments(arguments, elementNode): + 'Get vector3 vertexes from attribute dictionary by arguments.' + evaluate.setAttributesByArguments(['radius', 'endZ', 'start'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return PegDerivation(elementNode) + +def getTopAddBiconicOutput(bottomRadians, height, outputs, radius, sides, start, tipRadius, topRadians): + 'Get top and add biconic cylinder to outputs.' + radiusMaximum = max(radius.real, radius.imag) + topRadiusMaximum = radiusMaximum - height * math.tan(bottomRadians) + trunkEndZ = start.z + height + trunkTopOverBottom = topRadiusMaximum / radiusMaximum + topRadiusComplex = trunkTopOverBottom * radius + cylinder.addCylinderOutputByEndStart(trunkEndZ, radius, outputs, sides, start, trunkTopOverBottom) + tipOverTop = tipRadius / topRadiusMaximum + if tipOverTop >= 1.0: + return trunkEndZ + capStart = Vector3(start.x, start.y, trunkEndZ) + capEndZ = trunkEndZ + (topRadiusMaximum - tipRadius) / math.tan(topRadians) + cylinder.addCylinderOutputByEndStart(capEndZ, topRadiusComplex, outputs, sides, capStart, tipOverTop) + return capEndZ + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByGeometry(elementNode, getGeometryOutput(None, elementNode)) + +def setTopOverBottomByRadius(derivation, endZ, radius, startZ): + 'Set the derivation topOverBottom by the angle of the elementNode, the endZ, float radius and startZ.' + angleDegrees = evaluate.getEvaluatedFloat(None, derivation.elementNode, 'angle') + if angleDegrees != None: + derivation.topOverBottom = cylinder.getTopOverBottom(math.radians(angleDegrees), endZ, complex(radius, radius), startZ) + + +class PegDerivation: + 'Class to hold peg variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.bevelOverRadius = evaluate.getEvaluatedFloat(0.25, elementNode, 'bevelOverRadius') + self.clearanceOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'clearanceOverRadius') + self.elementNode = elementNode + self.endZ = evaluate.getEvaluatedFloat(10.0, elementNode, 'endZ') + self.start = evaluate.getVector3ByPrefix(Vector3(), elementNode, 'start') + self.radius = lineation.getFloatByPrefixBeginEnd(elementNode, 'radius', 'diameter', 2.0) + self.sides = evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, max(self.radius.real, self.radius.imag)) + self.radiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNode, self.radius, self.sides) + self.topOverBottom = evaluate.getEvaluatedFloat(0.8, elementNode, 'topOverBottom') + setTopOverBottomByRadius(self, self.endZ, self.radiusArealized, self.start.z) + # Set derived variables. + self.bevel = evaluate.getEvaluatedFloat(self.bevelOverRadius * self.radiusArealized, elementNode, 'bevel') + self.clearance = evaluate.getEvaluatedFloat(self.clearanceOverRadius * self.radiusArealized, elementNode, 'clearance') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/polygon.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/polygon.py new file mode 100644 index 0000000..26e4dc9 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/polygon.py @@ -0,0 +1,69 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = PolygonDerivation(elementNode) + loop = [] + spiral = lineation.Spiral(derivation.spiral, 0.5 * derivation.sideAngle / math.pi) + for side in xrange(derivation.start, derivation.start + derivation.extent + 1): + angle = float(side) * derivation.sideAngle + unitPolar = euclidean.getWiddershinsUnitPolar(angle) + vertex = spiral.getSpiralPoint(unitPolar, Vector3(unitPolar.real * derivation.radius.real, unitPolar.imag * derivation.radius.imag)) + loop.append(vertex) + loop = euclidean.getLoopWithoutCloseEnds(0.000001 * max(derivation.radius.real, derivation.radius.imag), loop) + lineation.setClosedAttribute(elementNode, derivation.revolutions) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop, derivation.sideAngle)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['sides', 'radius'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return PolygonDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class PolygonDerivation: + "Class to hold polygon variables." + def __init__(self, elementNode): + 'Set defaults.' + self.sides = evaluate.getEvaluatedFloat(4.0, elementNode, 'sides') + self.sideAngle = 2.0 * math.pi / self.sides + cosSide = math.cos(0.5 * self.sideAngle) + self.radius = lineation.getComplexByMultiplierPrefixes(elementNode, cosSide, ['apothem', 'inradius'], complex(1.0, 1.0)) + self.radius = lineation.getComplexByPrefixes(elementNode, ['demisize', 'radius'], self.radius) + self.radius = lineation.getComplexByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], self.radius) + self.sidesCeiling = int(math.ceil(abs(self.sides))) + self.start = evaluate.getEvaluatedInt(0, elementNode, 'start') + end = evaluate.getEvaluatedInt(self.sidesCeiling, elementNode, 'end') + self.revolutions = evaluate.getEvaluatedInt(1, elementNode, 'revolutions') + self.extent = evaluate.getEvaluatedInt(end - self.start, elementNode, 'extent') + self.extent += self.sidesCeiling * (self.revolutions - 1) + self.spiral = evaluate.getVector3ByPrefix(None, elementNode, 'spiral') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/shaft.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/shaft.py new file mode 100644 index 0000000..457c2de --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/shaft.py @@ -0,0 +1,82 @@ +""" +Shaft path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = ShaftDerivation(elementNode) + shaftPath = getShaftPath(derivation.depthBottom, derivation.depthTop, derivation.radius, derivation.sides) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(shaftPath)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['radius', 'sides'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return ShaftDerivation(elementNode) + +def getShaftPath(depthBottom, depthTop, radius, sides): + 'Get shaft with the option of a flat on the top and/or bottom.' + if radius <= 0.0: + return [] + sideAngle = 2.0 * math.pi / float(abs(sides)) + startAngle = 0.5 * sideAngle + endAngle = math.pi - 0.1 * sideAngle + shaftProfile = [] + while startAngle < endAngle: + unitPolar = euclidean.getWiddershinsUnitPolar(startAngle) + shaftProfile.append(unitPolar * radius) + startAngle += sideAngle + if abs(sides) % 2 == 1: + shaftProfile.append(complex(-radius, 0.0)) + horizontalBegin = radius - depthTop + horizontalEnd = depthBottom - radius + shaftProfile = euclidean.getHorizontallyBoundedPath(horizontalBegin, horizontalEnd, shaftProfile) + for shaftPointIndex, shaftPoint in enumerate(shaftProfile): + shaftProfile[shaftPointIndex] = complex(shaftPoint.imag, shaftPoint.real) + shaftPath = euclidean.getVector3Path(euclidean.getMirrorPath(shaftProfile)) + if sides > 0: + shaftPath.reverse() + return shaftPath + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class ShaftDerivation: + "Class to hold shaft variables." + def __init__(self, elementNode): + 'Set defaults.' + self.depthBottomOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'depthBottomOverRadius') + self.depthTopOverRadius = evaluate.getEvaluatedFloat(0.0, elementNode, 'depthOverRadius') + self.depthTopOverRadius = evaluate.getEvaluatedFloat( + self.depthTopOverRadius, elementNode, 'depthTopOverRadius') + self.radius = evaluate.getEvaluatedFloat(1.0, elementNode, 'radius') + self.sides = evaluate.getEvaluatedInt(4, elementNode, 'sides') + self.depthBottom = self.radius * self.depthBottomOverRadius + self.depthBottom = evaluate.getEvaluatedFloat(self.depthBottom, elementNode, 'depthBottom') + self.depthTop = self.radius * self.depthTopOverRadius + self.depthTop = evaluate.getEvaluatedFloat(self.depthTop, elementNode, 'depth') + self.depthTop = evaluate.getEvaluatedFloat(self.depthTop, elementNode, 'depthTop') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/solid.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/solid.py new file mode 100644 index 0000000..4defa04 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/solid.py @@ -0,0 +1,178 @@ +""" +Solid has functions for 3D shapes. + +Solid has some of the same functions as lineation, however you can not define geometry by dictionary string in the target because there is no getGeometryOutputByArguments function. You would have to define a shape by making the shape element. Also, you can not define geometry by 'get, because the target only gets element. Instead you would have the shape element, and set the target in solid to that element. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import boolean_geometry +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutputByFunction(elementNode, geometryFunction): + 'Get geometry output by manipulationFunction.' + if elementNode.xmlObject == None: + print('Warning, there is no object in getGeometryOutputByFunction in solid for:') + print(elementNode) + return None + geometryOutput = elementNode.xmlObject.getGeometryOutput() + if geometryOutput == None: + print('Warning, there is no geometryOutput in getGeometryOutputByFunction in solid for:') + print(elementNode) + return None + return geometryFunction(elementNode, geometryOutput, '') + +def getGeometryOutputByManipulation(elementNode, geometryOutput): + 'Get geometryOutput manipulated by the plugins in the manipulation shapes & solids folders.' + xmlProcessor = elementNode.getXMLProcessor() + matchingPlugins = getSolidMatchingPlugins(elementNode) + matchingPlugins.sort(evaluate.compareExecutionOrderAscending) + for matchingPlugin in matchingPlugins: + prefix = matchingPlugin.__name__.replace('_', '') + '.' + geometryOutput = matchingPlugin.getManipulatedGeometryOutput(elementNode, geometryOutput, prefix) + return geometryOutput + +def getLoopLayersSetCopy(elementNode, geometryOutput, importRadius, radius): + 'Get the loop layers and set the copyShallow.' + halfLayerHeight = 0.5 * radius + copyShallow = elementNode.getCopyShallow() + processElementNodeByGeometry(copyShallow, geometryOutput) + targetMatrix = matrix.getBranchMatrixSetElementNode(elementNode) + matrix.setElementNodeDictionaryMatrix(copyShallow, targetMatrix) + transformedVertexes = copyShallow.xmlObject.getTransformedVertexes() + minimumZ = boolean_geometry.getMinimumZ(copyShallow.xmlObject) + if minimumZ == None: + copyShallow.parentNode.xmlObject.archivableObjects.remove(copyShallow.xmlObject) + return [] + maximumZ = euclidean.getTopPath(transformedVertexes) + copyShallow.attributes['visible'] = True + copyShallowObjects = [copyShallow.xmlObject] + bottomLoopLayer = euclidean.LoopLayer(minimumZ) + z = minimumZ + 0.1 * radius + zoneArrangement = triangle_mesh.ZoneArrangement(radius, transformedVertexes) + bottomLoopLayer.loops = boolean_geometry.getEmptyZLoops(copyShallowObjects, importRadius, False, z, zoneArrangement) + loopLayers = [bottomLoopLayer] + z = minimumZ + halfLayerHeight + loopLayers += boolean_geometry.getLoopLayers(copyShallowObjects, importRadius, halfLayerHeight, maximumZ, False, z, zoneArrangement) + copyShallow.parentNode.xmlObject.archivableObjects.remove(copyShallow.xmlObject) + return loopLayers + +def getLoopOrEmpty(loopIndex, loopLayers): + 'Get the loop, or if the loopIndex is out of range, get an empty list.' + if loopIndex < 0 or loopIndex >= len(loopLayers): + return [] + return loopLayers[loopIndex].loops[0] + +def getNewDerivation(elementNode): + 'Get new derivation.' + return SolidDerivation(elementNode) + +def getSolidMatchingPlugins(elementNode): + 'Get solid plugins in the manipulation matrix, shapes & solids folders.' + xmlProcessor = elementNode.getXMLProcessor() + matchingPlugins = evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationMatrixDictionary) + return matchingPlugins + evaluate.getMatchingPlugins(elementNode, xmlProcessor.manipulationShapeDictionary) + +def processArchiveRemoveSolid(elementNode, geometryOutput): + 'Process the target by the manipulationFunction.' + solidMatchingPlugins = getSolidMatchingPlugins(elementNode) + if len(solidMatchingPlugins) == 0: + elementNode.parentNode.xmlObject.archivableObjects.append(elementNode.xmlObject) + matrix.getBranchMatrixSetElementNode(elementNode) + return + processElementNodeByGeometry(elementNode, getGeometryOutputByManipulation(elementNode, geometryOutput)) + +def processElementNode(elementNode): + 'Process the xml element.' + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = SolidDerivation(elementNode) + elementAttributesCopy = elementNode.attributes.copy() + for target in derivation.targets: + targetAttributesCopy = target.attributes.copy() + target.attributes = elementAttributesCopy + processTarget(target) + target.attributes = targetAttributesCopy + +def processElementNodeByFunction(elementNode, manipulationFunction): + 'Process the xml element.' + if 'target' not in elementNode.attributes: + print('Warning, there was no target in processElementNodeByFunction in solid for:') + print(elementNode) + return + target = evaluate.getEvaluatedLinkValue(elementNode, str(elementNode.attributes['target']).strip()) + if target.__class__.__name__ == 'ElementNode': + manipulationFunction(elementNode, target) + return + path.convertElementNode(elementNode, target) + manipulationFunction(elementNode, elementNode) + +def processElementNodeByFunctionPair(elementNode, geometryFunction, pathFunction): + 'Process the xml element by the appropriate manipulationFunction.' + elementAttributesCopy = elementNode.attributes.copy() + targets = evaluate.getElementNodesByKey(elementNode, 'target') + for target in targets: + targetAttributesCopy = target.attributes.copy() + target.attributes = elementAttributesCopy + processTargetByFunctionPair(geometryFunction, pathFunction, target) + target.attributes = targetAttributesCopy + +def processElementNodeByGeometry(elementNode, geometryOutput): + 'Process the xml element by geometryOutput.' + if geometryOutput != None: + elementNode.getXMLProcessor().convertElementNode(elementNode, geometryOutput) + +def processTarget(target): + 'Process the target.' + if target.xmlObject == None: + print('Warning, there is no object in processElementNode in solid for:') + print(target) + return + geometryOutput = target.xmlObject.getGeometryOutput() + if geometryOutput == None: + print('Warning, there is no geometryOutput in processElementNode in solid for:') + print(target.xmlObject) + return + geometryOutput = getGeometryOutputByManipulation(target, geometryOutput) + lineation.removeChildNodesFromElementObject(target) + target.getXMLProcessor().convertElementNode(target, geometryOutput) + +def processTargetByFunctionPair(geometryFunction, pathFunction, target): + 'Process the target by the manipulationFunction.' + if target.xmlObject == None: + print('Warning, there is no object in processTargetByFunctions in solid for:') + print(target) + return + if len(target.xmlObject.getPaths()) > 0: + lineation.processTargetByFunction(pathFunction, target) + return + geometryOutput = getGeometryOutputByFunction(target, geometryFunction) + lineation.removeChildNodesFromElementObject(target) + target.getXMLProcessor().convertElementNode(target, geometryOutput) + + +class SolidDerivation: + 'Class to hold solid variables.' + def __init__(self, elementNode): + 'Set defaults.' + self.targets = evaluate.getElementNodesByKey(elementNode, 'target') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/sponge_slice.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/sponge_slice.py new file mode 100644 index 0000000..21f6337 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/sponge_slice.py @@ -0,0 +1,157 @@ +""" +Sponge slice. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math +import random +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = SpongeSliceDerivation(elementNode) + awayPoints = [] + vector3Path = euclidean.getVector3Path(euclidean.getSquareLoopWiddershins(-derivation.inradius, derivation.inradius)) + geometryOutput = lineation.SideLoop(vector3Path).getManipulationPluginLoops(elementNode) + minimumDistanceFromOther = derivation.wallThickness + derivation.minimumRadius + derivation.minimumRadius + if derivation.inradiusMinusRadiusThickness.real <= 0.0 or derivation.inradiusMinusRadiusThickness.imag <= 0.0: + return geometryOutput + for point in derivation.path: + if abs(point.x) <= derivation.inradiusMinusRadiusThickness.real and abs(point.y) <= derivation.inradiusMinusRadiusThickness.imag: + awayPoints.append(point) + awayCircles = [] + for point in awayPoints: + if getIsPointAway(minimumDistanceFromOther, point, awayCircles): + awayCircles.append(SpongeCircle(point, derivation.minimumRadius)) + averagePotentialBubbleArea = derivation.potentialBubbleArea / float(len(awayCircles)) + averageBubbleRadius = math.sqrt(averagePotentialBubbleArea / math.pi) - 0.5 * derivation.wallThickness + sides = -4 * (max(evaluate.getSidesBasedOnPrecision(elementNode, averageBubbleRadius), 4) / 4) + sideAngle = math.pi / sides + cosSide = math.cos(sideAngle) + overlapArealRatio = (1 - cosSide) / cosSide + for circleIndex, circle in enumerate(awayCircles): + otherCircles = awayCircles[: circleIndex] + awayCircles[circleIndex + 1 :] + circle.radius = circle.getRadius(circle.center, derivation, otherCircles, overlapArealRatio) + if derivation.searchAttempts > 0: + for circleIndex, circle in enumerate(awayCircles): + otherCircles = awayCircles[: circleIndex] + awayCircles[circleIndex + 1 :] + circle.moveCircle(derivation, otherCircles, overlapArealRatio) + for circle in awayCircles: + vector3Path = euclidean.getVector3Path(euclidean.getComplexPolygon(circle.center.dropAxis(), circle.radius, sides, sideAngle)) + geometryOutput += lineation.SideLoop(vector3Path).getManipulationPluginLoops(elementNode) + return geometryOutput + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + return getGeometryOutput(None, elementNode) + +def getIsPointAway(minimumDistance, point, spongeCircles): + 'Determine if the point is at least the minimumDistance away from other points.' + for otherSpongeCircle in spongeCircles: + if abs(otherSpongeCircle.center - point) < minimumDistance: + return False + return True + +def getNewDerivation(elementNode): + 'Get new derivation.' + return SpongeSliceDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class SpongeCircle: + "Class to hold sponge circle." + def __init__(self, center, radius=0.0): + 'Initialize.' + self.center = center + self.radius = radius + + def getRadius(self, center, derivation, otherCircles, overlapArealRatio): + 'Get sponge bubble radius.' + radius = 987654321.0 + for otherSpongeCircle in otherCircles: + distance = abs(otherSpongeCircle.center.dropAxis() - center.dropAxis()) + radius = min(distance - derivation.wallThickness - otherSpongeCircle.radius, radius) + overlapAreal = overlapArealRatio * radius + radius = min(derivation.inradiusMinusThickness.real + overlapAreal - abs(center.x), radius) + return min(derivation.inradiusMinusThickness.imag + overlapAreal - abs(center.y), radius) + + def moveCircle(self, derivation, otherCircles, overlapArealRatio): + 'Move circle into an open spot.' + angle = (abs(self.center) + self.radius) % euclidean.globalTau + movedCenter = self.center + searchRadius = derivation.searchRadiusOverRadius * self.radius + distanceIncrement = searchRadius / float(derivation.searchAttempts) + distance = 0.0 + greatestRadius = self.radius + searchCircles = [] + searchCircleDistance = searchRadius + searchRadius + self.radius + derivation.wallThickness + for otherCircle in otherCircles: + if abs(self.center - otherCircle.center) <= searchCircleDistance + otherCircle.radius: + searchCircles.append(otherCircle) + for attemptIndex in xrange(derivation.searchAttempts): + angle += euclidean.globalGoldenAngle + distance += distanceIncrement + offset = distance * euclidean.getWiddershinsUnitPolar(angle) + attemptCenter = self.center + Vector3(offset.real, offset.imag) + radius = self.getRadius(attemptCenter, derivation, searchCircles, overlapArealRatio) + if radius > greatestRadius: + greatestRadius = radius + movedCenter = attemptCenter + self.center = movedCenter + self.radius = greatestRadius + + +class SpongeSliceDerivation: + "Class to hold sponge slice variables." + def __init__(self, elementNode): + 'Initialize.' + elementNode.attributes['closed'] = 'true' + self.density = evaluate.getEvaluatedFloat(1.0, elementNode, 'density') + self.minimumRadiusOverThickness = evaluate.getEvaluatedFloat(1.0, elementNode, 'minimumRadiusOverThickness') + self.mobile = evaluate.getEvaluatedBoolean(False, elementNode, 'mobile') + self.inradius = lineation.getInradius(complex(10.0, 10.0), elementNode) + self.path = None + if 'path' in elementNode.attributes: + self.path = evaluate.getPathByKey([], elementNode, 'path') + self.searchAttempts = evaluate.getEvaluatedInt(0, elementNode, 'searchAttempts') + self.searchRadiusOverRadius = evaluate.getEvaluatedFloat(1.0, elementNode, 'searchRadiusOverRadius') + self.seed = evaluate.getEvaluatedInt(None, elementNode, 'seed') + self.wallThickness = evaluate.getEvaluatedFloat(2.0 * setting.getEdgeWidth(elementNode), elementNode, 'wallThickness') + # Set derived variables. + self.halfWallThickness = 0.5 * self.wallThickness + self.inradiusMinusThickness = self.inradius - complex(self.wallThickness, self.wallThickness) + self.minimumRadius = evaluate.getEvaluatedFloat(self.minimumRadiusOverThickness * self.wallThickness, elementNode, 'minimumRadius') + self.inradiusMinusRadiusThickness = self.inradiusMinusThickness - complex(self.minimumRadius, self.minimumRadius) + self.potentialBubbleArea = 4.0 * self.inradiusMinusThickness.real * self.inradiusMinusThickness.imag + if self.path == None: + radiusPlusHalfThickness = self.minimumRadius + self.halfWallThickness + numberOfPoints = int(math.ceil(self.density * self.potentialBubbleArea / math.pi / radiusPlusHalfThickness / radiusPlusHalfThickness)) + self.path = [] + if self.seed == None: + self.seed = time.time() + print('Sponge slice seed used was: %s' % self.seed) + random.seed(self.seed) + for pointIndex in xrange(numberOfPoints): + point = euclidean.getRandomComplex(-self.inradiusMinusRadiusThickness, self.inradiusMinusRadiusThickness) + self.path.append(Vector3(point.real, point.imag)) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/square.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/square.py new file mode 100644 index 0000000..68b31bc --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/square.py @@ -0,0 +1,80 @@ +""" +Square path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = SquareDerivation(elementNode) + topRight = complex(derivation.topDemiwidth, derivation.demiheight) + topLeft = complex(-derivation.topDemiwidth, derivation.demiheight) + bottomLeft = complex(-derivation.bottomDemiwidth, -derivation.demiheight) + bottomRight = complex(derivation.bottomDemiwidth, -derivation.demiheight) + if derivation.interiorAngle != 90.0: + interiorPlaneAngle = euclidean.getWiddershinsUnitPolar(math.radians(derivation.interiorAngle - 90.0)) + topRight = (topRight - bottomRight) * interiorPlaneAngle + bottomRight + topLeft = (topLeft - bottomLeft) * interiorPlaneAngle + bottomLeft + lineation.setClosedAttribute(elementNode, derivation.revolutions) + complexLoop = [topRight, topLeft, bottomLeft, bottomRight] + originalLoop = complexLoop[:] + for revolution in xrange(1, derivation.revolutions): + complexLoop += originalLoop + spiral = lineation.Spiral(derivation.spiral, 0.25) + loop = [] + loopCentroid = euclidean.getLoopCentroid(originalLoop) + for point in complexLoop: + unitPolar = euclidean.getNormalized(point - loopCentroid) + loop.append(spiral.getSpiralPoint(unitPolar, Vector3(point.real, point.imag))) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop, 0.5 * math.pi)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + if len(arguments) < 1: + return getGeometryOutput(None, elementNode) + inradius = 0.5 * euclidean.getFloatFromValue(arguments[0]) + elementNode.attributes['inradius.x'] = str(inradius) + if len(arguments) > 1: + inradius = 0.5 * euclidean.getFloatFromValue(arguments[1]) + elementNode.attributes['inradius.y'] = str(inradius) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return SquareDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class SquareDerivation: + "Class to hold square variables." + def __init__(self, elementNode): + 'Set defaults.' + self.inradius = lineation.getInradius(complex(1.0, 1.0), elementNode) + self.demiwidth = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiwidth', 'width', self.inradius.real) + self.demiheight = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiheight', 'height', self.inradius.imag) + self.bottomDemiwidth = lineation.getFloatByPrefixBeginEnd(elementNode, 'bottomdemiwidth', 'bottomwidth', self.demiwidth) + self.topDemiwidth = lineation.getFloatByPrefixBeginEnd(elementNode, 'topdemiwidth', 'topwidth', self.demiwidth) + self.interiorAngle = evaluate.getEvaluatedFloat(90.0, elementNode, 'interiorangle') + self.revolutions = evaluate.getEvaluatedInt(1, elementNode, 'revolutions') + self.spiral = evaluate.getVector3ByPrefix(None, elementNode, 'spiral') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/teardrop.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/teardrop.py new file mode 100644 index 0000000..8761619 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/teardrop.py @@ -0,0 +1,116 @@ +""" +Teardrop path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import extrude +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addNegativesByRadius(elementNode, end, negatives, radius, start): + "Add teardrop drill hole to negatives." + if radius <= 0.0: + return + copyShallow = elementNode.getCopyShallow() + extrude.setElementNodeToEndStart(copyShallow, end, start) + extrudeDerivation = extrude.ExtrudeDerivation(copyShallow) + extrude.addNegatives(extrudeDerivation, negatives, [getTeardropPathByEndStart(elementNode, end, radius, start)]) + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attribute dictionary." + if derivation == None: + derivation = TeardropDerivation(elementNode) + teardropPath = getTeardropPath( + derivation.inclination, derivation.overhangRadians, derivation.overhangSpan, derivation.radiusArealized, derivation.sides) + return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(teardropPath)) + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['radius', 'inclination'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getInclination(end, start): + "Get inclination." + if end == None or start == None: + return 0.0 + endMinusStart = end - start + return math.atan2(endMinusStart.z, abs(endMinusStart.dropAxis())) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return TeardropDerivation(elementNode) + +def getTeardropPath(inclination, overhangRadians, overhangSpan, radiusArealized, sides): + "Get vector3 teardrop path." + sideAngle = 2.0 * math.pi / float(sides) + overhangPlaneAngle = euclidean.getWiddershinsUnitPolar(overhangRadians) + overhangRadians = math.atan2(overhangPlaneAngle.imag, overhangPlaneAngle.real * math.cos(inclination)) + tanOverhangAngle = math.tan(overhangRadians) + beginAngle = overhangRadians + beginMinusEndAngle = math.pi + overhangRadians + overhangRadians + withinSides = int(math.ceil(beginMinusEndAngle / sideAngle)) + withinSideAngle = -beginMinusEndAngle / float(withinSides) + teardropPath = [] + for side in xrange(withinSides + 1): + unitPolar = euclidean.getWiddershinsUnitPolar(beginAngle) + teardropPath.append(unitPolar * radiusArealized) + beginAngle += withinSideAngle + firstPoint = teardropPath[0] + if overhangSpan <= 0.0: + teardropPath.append(complex(0.0, firstPoint.imag + firstPoint.real / tanOverhangAngle)) + else: + deltaX = (radiusArealized - firstPoint.imag) * tanOverhangAngle + overhangPoint = complex(firstPoint.real - deltaX, radiusArealized) + remainingDeltaX = max(0.0, overhangPoint.real - 0.5 * overhangSpan ) + overhangPoint += complex(-remainingDeltaX, remainingDeltaX / tanOverhangAngle) + teardropPath.append(complex(-overhangPoint.real, overhangPoint.imag)) + teardropPath.append(overhangPoint) + return euclidean.getVector3Path(teardropPath) + +def getTeardropPathByEndStart(elementNode, end, radius, start): + "Get vector3 teardrop path by end and start." + inclination = getInclination(end, start) + sides = evaluate.getSidesMinimumThreeBasedOnPrecisionSides(elementNode, radius) + radiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNoderadius, sides) + return getTeardropPath(inclination, setting.getOverhangRadians(elementNode), setting.getOverhangSpan(elementNode), radiusArealized, sides) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class TeardropDerivation: + "Class to hold teardrop variables." + def __init__(self, elementNode): + 'Set defaults.' + end = evaluate.getVector3ByPrefix(None, elementNode, 'end') + start = evaluate.getVector3ByPrefix(Vector3(), elementNode, 'start') + inclinationDegree = math.degrees(getInclination(end, start)) + self.elementNode = elementNode + self.inclination = math.radians(evaluate.getEvaluatedFloat(inclinationDegree, elementNode, 'inclination')) + self.overhangRadians = setting.getOverhangRadians(elementNode) + self.overhangSpan = setting.getOverhangSpan(elementNode) + self.radius = lineation.getFloatByPrefixBeginEnd(elementNode, 'radius', 'diameter', 1.0) + size = evaluate.getEvaluatedFloat(None, elementNode, 'size') + if size != None: + self.radius = 0.5 * size + self.sides = evaluate.getEvaluatedFloat(None, elementNode, 'sides') + if self.sides == None: + self.sides = evaluate.getSidesMinimumThreeBasedOnPrecisionSides(elementNode, self.radius) + self.radiusArealized = evaluate.getRadiusArealizedBasedOnAreaRadius(elementNode, self.radius, self.sides) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/text.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/text.py new file mode 100644 index 0000000..2545d7d --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/creation/text.py @@ -0,0 +1,63 @@ +""" +Text vertexes. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import svg_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getGeometryOutput(derivation, elementNode): + "Get vector3 vertexes from attributes." + if derivation == None: + derivation = TextDerivation(elementNode) + if derivation.textString == '': + print('Warning, textString is empty in getGeometryOutput in text for:') + print(elementNode) + return [] + geometryOutput = [] + for textComplexLoop in svg_reader.getTextComplexLoops(derivation.fontFamily, derivation.fontSize, derivation.textString): + textComplexLoop.reverse() + vector3Path = euclidean.getVector3Path(textComplexLoop) + sideLoop = lineation.SideLoop(vector3Path) + sideLoop.rotate(elementNode) + geometryOutput += lineation.getGeometryOutputByManipulation(elementNode, sideLoop) + return geometryOutput + +def getGeometryOutputByArguments(arguments, elementNode): + "Get vector3 vertexes from attribute dictionary by arguments." + evaluate.setAttributesByArguments(['text', 'fontSize', 'fontFamily'], arguments, elementNode) + return getGeometryOutput(None, elementNode) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return TextDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + path.convertElementNode(elementNode, getGeometryOutput(None, elementNode)) + + +class TextDerivation: + "Class to hold text variables." + def __init__(self, elementNode): + 'Set defaults.' + self.fontFamily = evaluate.getEvaluatedString('Gentium Basic Regular', elementNode, 'font-family') + self.fontFamily = evaluate.getEvaluatedString(self.fontFamily, elementNode, 'fontFamily') + self.fontSize = evaluate.getEvaluatedFloat(12.0, elementNode, 'font-size') + self.fontSize = evaluate.getEvaluatedFloat(self.fontSize, elementNode, 'fontSize') + self.textString = elementNode.getTextContent() + self.textString = evaluate.getEvaluatedString(self.textString, elementNode, 'text') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/dictionary.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/dictionary.py new file mode 100644 index 0000000..f3714d8 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/dictionary.py @@ -0,0 +1,178 @@ +""" +Boolean geometry dictionary object. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import xml_simple_writer +import cStringIO + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getAllPaths(paths, xmlObject): + 'Get all paths.' + for archivableObject in xmlObject.archivableObjects: + paths += archivableObject.getPaths() + return paths + +def getAllTransformedPaths(transformedPaths, xmlObject): + 'Get all transformed paths.' + for archivableObject in xmlObject.archivableObjects: + transformedPaths += archivableObject.getTransformedPaths() + return transformedPaths + +def getAllTransformedVertexes(transformedVertexes, xmlObject): + 'Get all transformed vertexes.' + for archivableObject in xmlObject.archivableObjects: + transformedVertexes += archivableObject.getTransformedVertexes() + return transformedVertexes + +def getAllVertexes(vertexes, xmlObject): + 'Get all vertexes.' + for archivableObject in xmlObject.archivableObjects: + vertexes += archivableObject.getVertexes() + return vertexes + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable( Dictionary, elementNode) + + +class Dictionary: + 'A dictionary object.' + def __init__(self): + 'Add empty lists.' + self.archivableObjects = [] + self.elementNode = None + + def __repr__(self): + 'Get the string representation of this object info.' + output = xml_simple_writer.getBeginGeometryXMLOutput(self.elementNode) + self.addXML( 1, output ) + return xml_simple_writer.getEndGeometryXMLString(output) + + def addXML(self, depth, output): + 'Add xml for this object.' + attributeCopy = {} + if self.elementNode != None: + attributeCopy = evaluate.getEvaluatedDictionaryByCopyKeys(['paths', 'target', 'vertexes'], self.elementNode) + euclidean.removeElementsFromDictionary(attributeCopy, matrix.getKeysM()) + euclidean.removeTrueFromDictionary(attributeCopy, 'visible') + innerOutput = cStringIO.StringIO() + self.addXMLInnerSection(depth + 1, innerOutput) + self.addXMLArchivableObjects(depth + 1, innerOutput) + xml_simple_writer.addBeginEndInnerXMLTag(attributeCopy, depth, innerOutput.getvalue(), self.getXMLLocalName(), output) + + def addXMLArchivableObjects(self, depth, output): + 'Add xml for this object.' + xml_simple_writer.addXMLFromObjects( depth, self.archivableObjects, output ) + + def addXMLInnerSection(self, depth, output): + 'Add xml section for this object.' + pass + + def createShape(self): + 'Create the shape.' + pass + + def getAttributes(self): + 'Get attribute table.' + if self.elementNode == None: + return {} + return self.elementNode.attributes + + def getComplexTransformedPathLists(self): + 'Get complex transformed path lists.' + complexTransformedPathLists = [] + for archivableObject in self.archivableObjects: + complexTransformedPathLists.append(euclidean.getComplexPaths(archivableObject.getTransformedPaths())) + return complexTransformedPathLists + + def getFabricationExtension(self): + 'Get fabrication extension.' + return 'xml' + + def getFabricationText(self, addLayerTemplate): + 'Get fabrication text.' + return self.__repr__() + + def getGeometryOutput(self): + 'Get geometry output dictionary.' + shapeOutput = [] + for visibleObject in evaluate.getVisibleObjects(self.archivableObjects): + geometryOutput = visibleObject.getGeometryOutput() + if geometryOutput != None: + visibleObject.transformGeometryOutput(geometryOutput) + shapeOutput.append(geometryOutput) + if len(shapeOutput) < 1: + return None + return {self.getXMLLocalName() : {'shapes' : shapeOutput}} + + def getMatrix4X4(self): + "Get the matrix4X4." + return None + + def getMatrixChainTetragrid(self): + 'Get the matrix chain tetragrid.' + return self.elementNode.parentNode.xmlObject.getMatrixChainTetragrid() + + def getMinimumZ(self): + 'Get the minimum z.' + return None + + def getPaths(self): + 'Get all paths.' + return getAllPaths([], self) + + def getTransformedPaths(self): + 'Get all transformed paths.' + return getAllTransformedPaths([], self) + + def getTransformedVertexes(self): + 'Get all transformed vertexes.' + return getAllTransformedVertexes([], self) + + def getTriangleMeshes(self): + 'Get all triangleMeshes.' + triangleMeshes = [] + for archivableObject in self.archivableObjects: + triangleMeshes += archivableObject.getTriangleMeshes() + return triangleMeshes + + def getType(self): + 'Get type.' + return self.__class__.__name__ + + def getVertexes(self): + 'Get all vertexes.' + return getAllVertexes([], self) + + def getVisible(self): + 'Get visible.' + return False + + def getXMLLocalName(self): + 'Get xml local name.' + return self.__class__.__name__.lower() + + def setToElementNode(self, elementNode): + 'Set the shape of this carvable object info.' + self.elementNode = elementNode + elementNode.parentNode.xmlObject.archivableObjects.append(self) + + def transformGeometryOutput(self, geometryOutput): + 'Transform the geometry output by the local matrix4x4.' + if self.getMatrix4X4() != None: + matrix.transformVector3sByMatrix(self.getMatrix4X4().tetragrid, matrix.getVertexes(geometryOutput)) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/face.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/face.py new file mode 100644 index 0000000..e0e6e36 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/face.py @@ -0,0 +1,171 @@ +""" +Face of a triangle mesh. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import xml_simple_reader +from fabmetheus_utilities import xml_simple_writer +import cStringIO +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addFaces(geometryOutput, faces): + 'Add the faces.' + if geometryOutput.__class__ == list: + for element in geometryOutput: + addFaces(element, faces) + return + if geometryOutput.__class__ != dict: + return + for geometryOutputKey in geometryOutput.keys(): + geometryOutputValue = geometryOutput[geometryOutputKey] + if geometryOutputKey == 'face': + for face in geometryOutputValue: + faces.append(face) + else: + addFaces(geometryOutputValue, faces) + +def addGeometryList(elementNode, faces): + "Add vertex elements to an xml element." + for face in faces: + faceElement = xml_simple_reader.ElementNode() + face.addToAttributes( faceElement.attributes ) + faceElement.localName = 'face' + faceElement.parentNode = elementNode + elementNode.childNodes.append( faceElement ) + +def getCommonVertexIndex( edgeFirst, edgeSecond ): + "Get the vertex index that both edges have in common." + for edgeFirstVertexIndex in edgeFirst.vertexIndexes: + if edgeFirstVertexIndex == edgeSecond.vertexIndexes[0] or edgeFirstVertexIndex == edgeSecond.vertexIndexes[1]: + return edgeFirstVertexIndex + print("Inconsistent GNU Triangulated Surface") + print(edgeFirst) + print(edgeSecond) + return 0 + +def getFaces(geometryOutput): + 'Get the faces.' + faces = [] + addFaces(geometryOutput, faces) + return faces + +def processElementNode(elementNode): + "Process the xml element." + face = Face() + face.index = len(elementNode.parentNode.xmlObject.faces) + for vertexIndexIndex in xrange(3): + face.vertexIndexes.append(evaluate.getEvaluatedInt(None, elementNode, 'vertex' + str(vertexIndexIndex))) + elementNode.parentNode.xmlObject.faces.append(face) + + +class Edge: + "An edge of a triangle mesh." + def __init__(self): + "Set the face indexes to None." + self.faceIndexes = [] + self.vertexIndexes = [] + self.zMaximum = None + self.zMinimum = None + + def __repr__(self): + "Get the string representation of this Edge." + return str( self.index ) + ' ' + str( self.faceIndexes ) + ' ' + str(self.vertexIndexes) + + def addFaceIndex( self, faceIndex ): + "Add first None face index to input face index." + self.faceIndexes.append( faceIndex ) + + def getFromVertexIndexes( self, edgeIndex, vertexIndexes ): + "Initialize from two vertex indices." + self.index = edgeIndex + self.vertexIndexes = vertexIndexes[:] + self.vertexIndexes.sort() + return self + + +class Face: + "A face of a triangle mesh." + def __init__(self): + "Initialize." + self.edgeIndexes = [] + self.index = None + self.vertexIndexes = [] + + def __repr__(self): + "Get the string representation of this object info." + output = cStringIO.StringIO() + self.addXML( 2, output ) + return output.getvalue() + + def addToAttributes(self, attributes): + "Add to the attribute dictionary." + for vertexIndexIndex in xrange(len(self.vertexIndexes)): + vertexIndex = self.vertexIndexes[vertexIndexIndex] + attributes['vertex' + str(vertexIndexIndex)] = str(vertexIndex) + + def addXML(self, depth, output): + "Add the xml for this object." + attributes = {} + self.addToAttributes(attributes) + xml_simple_writer.addClosedXMLTag( attributes, depth, 'face', output ) + + def copy(self): + 'Get the copy of this face.' + faceCopy = Face() + faceCopy.edgeIndexes = self.edgeIndexes[:] + faceCopy.index = self.index + faceCopy.vertexIndexes = self.vertexIndexes[:] + return faceCopy + + def getFromEdgeIndexes( self, edgeIndexes, edges, faceIndex ): + "Initialize from edge indices." + if len(self.vertexIndexes) > 0: + return + self.index = faceIndex + self.edgeIndexes = edgeIndexes + for edgeIndex in edgeIndexes: + edges[ edgeIndex ].addFaceIndex( faceIndex ) + for triangleIndex in xrange(3): + indexFirst = ( 3 - triangleIndex ) % 3 + indexSecond = ( 4 - triangleIndex ) % 3 + self.vertexIndexes.append( getCommonVertexIndex( edges[ edgeIndexes[ indexFirst ] ], edges[ edgeIndexes[ indexSecond ] ] ) ) + return self + + def setEdgeIndexesToVertexIndexes( self, edges, edgeTable ): + "Set the edge indexes to the vertex indexes." + if len(self.edgeIndexes) > 0: + return + for triangleIndex in xrange(3): + indexFirst = ( 3 - triangleIndex ) % 3 + indexSecond = ( 4 - triangleIndex ) % 3 + vertexIndexFirst = self.vertexIndexes[ indexFirst ] + vertexIndexSecond = self.vertexIndexes[ indexSecond ] + vertexIndexPair = [ vertexIndexFirst, vertexIndexSecond ] + vertexIndexPair.sort() + edgeIndex = len( edges ) + if str( vertexIndexPair ) in edgeTable: + edgeIndex = edgeTable[ str( vertexIndexPair ) ] + else: + edgeTable[ str( vertexIndexPair ) ] = edgeIndex + edge = Edge().getFromVertexIndexes( edgeIndex, vertexIndexPair ) + edges.append( edge ) + edges[ edgeIndex ].addFaceIndex( self.index ) + self.edgeIndexes.append( edgeIndex ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path.py new file mode 100644 index 0000000..8ec3db1 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path.py @@ -0,0 +1,204 @@ +""" +Path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import dictionary +from fabmetheus_utilities.geometry.geometry_tools import vertex +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import svg_writer +from fabmetheus_utilities import xml_simple_reader +from fabmetheus_utilities import xml_simple_writer + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def convertElementNode(elementNode, geometryOutput): + 'Convert the xml element by geometryOutput.' + if geometryOutput == None: + return + if len(geometryOutput) < 1: + return + if len(geometryOutput) == 1: + firstLoop = geometryOutput[0] + if firstLoop.__class__ == list: + geometryOutput = firstLoop + firstElement = geometryOutput[0] + if firstElement.__class__ == list: + if len(firstElement) > 1: + convertElementNodeRenameByPaths(elementNode, geometryOutput) + else: + convertElementNodeByPath(elementNode, firstElement) + else: + convertElementNodeByPath(elementNode, geometryOutput) + +def convertElementNodeByPath(elementNode, geometryOutput): + 'Convert the xml element to a path xml element.' + createLinkPath(elementNode) + elementNode.xmlObject.vertexes = geometryOutput + vertex.addGeometryList(elementNode, geometryOutput) + +def convertElementNodeRenameByPaths(elementNode, geometryOutput): + 'Convert the xml element to a path xml element and add paths.' + createLinkPath(elementNode) + for geometryOutputChild in geometryOutput: + pathElement = xml_simple_reader.ElementNode() + pathElement.setParentAddToChildNodes(elementNode) + convertElementNodeByPath(pathElement, geometryOutputChild) + +def createLinkPath(elementNode): + 'Create and link a path object.' + elementNode.localName = 'path' + elementNode.linkObject(Path()) + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(Path, elementNode) + + +class Path(dictionary.Dictionary): + 'A path.' + def __init__(self): + 'Add empty lists.' + dictionary.Dictionary.__init__(self) + self.matrix4X4 = matrix.Matrix() + self.oldChainTetragrid = None + self.transformedPath = None + self.vertexes = [] + + def addXMLInnerSection(self, depth, output): + 'Add the xml section for this object.' + if self.matrix4X4 != None: + self.matrix4X4.addXML(depth, output) + xml_simple_writer.addXMLFromVertexes(depth, output, self.vertexes) + + def getFabricationExtension(self): + 'Get fabrication extension.' + return 'svg' + + def getFabricationText(self, addLayerTemplate): + 'Get fabrication text.' + carving = SVGFabricationCarving(addLayerTemplate, self.elementNode) + carving.setCarveLayerHeight(setting.getSheetThickness(self.elementNode)) + carving.processSVGElement(self.elementNode.getOwnerDocument().fileName) + return str(carving) + + def getMatrix4X4(self): + "Get the matrix4X4." + return self.matrix4X4 + + def getMatrixChainTetragrid(self): + 'Get the matrix chain tetragrid.' + return matrix.getTetragridTimesOther(self.elementNode.parentNode.xmlObject.getMatrixChainTetragrid(), self.matrix4X4.tetragrid) + + def getPaths(self): + 'Get all paths.' + self.transformedPath = None + if len(self.vertexes) > 0: + return dictionary.getAllPaths([self.vertexes], self) + return dictionary.getAllPaths([], self) + + def getTransformedPaths(self): + 'Get all transformed paths.' + if self.elementNode == None: + return dictionary.getAllPaths([self.vertexes], self) + chainTetragrid = self.getMatrixChainTetragrid() + if self.oldChainTetragrid != chainTetragrid: + self.oldChainTetragrid = chainTetragrid + self.transformedPath = None + if self.transformedPath == None: + self.transformedPath = matrix.getTransformedVector3s(chainTetragrid, self.vertexes) + if len(self.transformedPath) > 0: + return dictionary.getAllTransformedPaths([self.transformedPath], self) + return dictionary.getAllTransformedPaths([], self) + + +class SVGFabricationCarving: + 'An svg carving.' + def __init__(self, addLayerTemplate, elementNode): + 'Add empty lists.' + self.addLayerTemplate = addLayerTemplate + self.elementNode = elementNode + self.layerHeight = 1.0 + self.loopLayers = [] + + def __repr__(self): + 'Get the string representation of this carving.' + return self.getCarvedSVG() + + def addXML(self, depth, output): + 'Add xml for this object.' + xml_simple_writer.addXMLFromObjects(depth, self.loopLayers, output) + + def getCarveBoundaryLayers(self): + 'Get the boundary layers.' + return self.loopLayers + + def getCarveCornerMaximum(self): + 'Get the corner maximum of the vertexes.' + return self.cornerMaximum + + def getCarveCornerMinimum(self): + 'Get the corner minimum of the vertexes.' + return self.cornerMinimum + + def getCarvedSVG(self): + 'Get the carved svg text.' + return svg_writer.getSVGByLoopLayers(self.addLayerTemplate, self, self.loopLayers) + + def getCarveLayerHeight(self): + 'Get the layer height.' + return self.layerHeight + + def getFabmetheusXML(self): + 'Return the fabmetheus XML.' + return self.elementNode.getOwnerDocument().getOriginalRoot() + + def getInterpretationSuffix(self): + 'Return the suffix for a carving.' + return 'svg' + + def processSVGElement(self, fileName): + 'Parse SVG element and store the layers.' + self.fileName = fileName + paths = self.elementNode.xmlObject.getPaths() + oldZ = None + self.loopLayers = [] + loopLayer = None + for path in paths: + if len(path) > 0: + z = path[0].z + if z != oldZ: + loopLayer = euclidean.LoopLayer(z) + self.loopLayers.append(loopLayer) + oldZ = z + loopLayer.loops.append(euclidean.getComplexPath(path)) + if len(self.loopLayers) < 1: + return + self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0) + self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0) + svg_writer.setSVGCarvingCorners(self.cornerMaximum, self.cornerMinimum, self.layerHeight, self.loopLayers) + + def setCarveImportRadius( self, importRadius ): + 'Set the import radius.' + pass + + def setCarveIsCorrectMesh( self, isCorrectMesh ): + 'Set the is correct mesh flag.' + pass + + def setCarveLayerHeight( self, layerHeight ): + 'Set the layer height.' + self.layerHeight = layerHeight diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/__init__.py new file mode 100644 index 0000000..58ec332 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 4 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/arc.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/arc.py new file mode 100644 index 0000000..0adb453 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/arc.py @@ -0,0 +1,51 @@ +""" +Arc vertexes. + +From: +http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import svg_reader +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getArcPath(elementNode): + "Get the arc path.rx ry x-axis-rotation large-arc-flag sweep-flag" + begin = elementNode.getPreviousVertex(Vector3()) + end = evaluate.getVector3FromElementNode(elementNode) + largeArcFlag = evaluate.getEvaluatedBoolean(True, elementNode, 'largeArcFlag') + radius = lineation.getComplexByPrefix(elementNode, 'radius', complex(1.0, 1.0)) + sweepFlag = evaluate.getEvaluatedBoolean(True, elementNode, 'sweepFlag') + xAxisRotation = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, 'xAxisRotation')) + arcComplexes = svg_reader.getArcComplexes(begin.dropAxis(), end.dropAxis(), largeArcFlag, radius, sweepFlag, xAxisRotation) + path = [] + if len(arcComplexes) < 1: + return [] + incrementZ = (end.z - begin.z) / float(len(arcComplexes)) + z = begin.z + for pointIndex in xrange(len(arcComplexes)): + pointComplex = arcComplexes[pointIndex] + z += incrementZ + path.append(Vector3(pointComplex.real, pointComplex.imag, z)) + if len(path) > 0: + path[-1] = end + return path + +def processElementNode(elementNode): + "Process the xml element." + elementNode.parentNode.xmlObject.vertexes += getArcPath(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/cubic.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/cubic.py new file mode 100644 index 0000000..853729f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/cubic.py @@ -0,0 +1,58 @@ +""" +Cubic vertexes. + +From: +http://www.w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import svg_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCubicPath(elementNode): + "Get the cubic path." + end = evaluate.getVector3FromElementNode(elementNode) + previousElementNode = elementNode.getPreviousElementNode() + if previousElementNode == None: + print('Warning, can not get previousElementNode in getCubicPath in cubic for:') + print(elementNode) + return [end] + begin = elementNode.getPreviousVertex(Vector3()) + evaluatedControlPoints = evaluate.getTransformedPathByKey([], elementNode, 'controlPoints') + if len(evaluatedControlPoints) > 1: + return getCubicPathByBeginEnd(begin, evaluatedControlPoints, elementNode, end) + controlPoint0 = evaluate.getVector3ByPrefix(None, elementNode, 'controlPoint0') + controlPoint1 = evaluate.getVector3ByPrefix(None, elementNode, 'controlPoint1') + if len(evaluatedControlPoints) == 1: + controlPoint1 = evaluatedControlPoints[0] + if controlPoint0 == None: + oldControlPoint = evaluate.getVector3ByPrefixes(previousElementNode, ['controlPoint','controlPoint1'], None) + if oldControlPoint == None: + oldControlPoints = evaluate.getTransformedPathByKey([], previousElementNode, 'controlPoints') + if len(oldControlPoints) > 0: + oldControlPoint = oldControlPoints[-1] + if oldControlPoint == None: + oldControlPoint = end + controlPoint0 = begin + begin - oldControlPoint + return getCubicPathByBeginEnd(begin, [controlPoint0, controlPoint1], elementNode, end) + +def getCubicPathByBeginEnd(begin, controlPoints, elementNode, end): + "Get the cubic path by begin and end." + return svg_reader.getCubicPoints(begin, controlPoints, end, lineation.getNumberOfBezierPoints(begin, elementNode, end)) + +def processElementNode(elementNode): + "Process the xml element." + elementNode.parentNode.xmlObject.vertexes += getCubicPath(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/quadratic.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/quadratic.py new file mode 100644 index 0000000..d233d4c --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/path_elements/quadratic.py @@ -0,0 +1,45 @@ +""" +Quadratic vertexes. + +From: +http://www.w3.org/TR/SVG/paths.html#PathDataQuadraticBezierCommands + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import svg_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getQuadraticPath(elementNode): + "Get the quadratic path." + end = evaluate.getVector3FromElementNode(elementNode) + previousElementNode = elementNode.getPreviousElementNode() + if previousElementNode == None: + print('Warning, can not get previousElementNode in getQuadraticPath in quadratic for:') + print(elementNode) + return [end] + begin = elementNode.getPreviousVertex(Vector3()) + controlPoint = evaluate.getVector3ByPrefix(None, elementNode, 'controlPoint') + if controlPoint == None: + oldControlPoint = evaluate.getVector3ByPrefixes(previousElementNode, ['controlPoint','controlPoint1'], None) + if oldControlPoint == None: + oldControlPoint = end + controlPoint = begin + begin - oldControlPoint + evaluate.addVector3ToElementNode(elementNode, 'controlPoint', controlPoint) + return svg_reader.getQuadraticPoints(begin, controlPoint, end, lineation.getNumberOfBezierPoints(begin, elementNode, end)) + +def processElementNode(elementNode): + "Process the xml element." + elementNode.parentNode.xmlObject.vertexes += getQuadraticPath(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/vertex.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/vertex.py new file mode 100644 index 0000000..b002ed6 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_tools/vertex.py @@ -0,0 +1,45 @@ +""" +Vertex of a triangle mesh. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities import xml_simple_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addGeometryList(elementNode, vertexes): + "Add vertex elements to an xml element." + for vertex in vertexes: + vertexElement = getUnboundVertexElement(vertex) + vertexElement.parentNode = elementNode + elementNode.childNodes.append( vertexElement ) + +def addVertexToAttributes(attributes, vertex): + "Add to the attribute dictionary." + if vertex.x != 0.0: + attributes['x'] = str(vertex.x) + if vertex.y != 0.0: + attributes['y'] = str(vertex.y) + if vertex.z != 0.0: + attributes['z'] = str(vertex.z) + +def getUnboundVertexElement(vertex): + "Add vertex element to an xml element." + vertexElement = xml_simple_reader.ElementNode() + addVertexToAttributes(vertexElement.attributes, vertex) + vertexElement.localName = 'vertex' + return vertexElement + +def processElementNode(elementNode): + "Process the xml element." + elementNode.parentNode.xmlObject.vertexes.append(evaluate.getVector3FromElementNode(elementNode)) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/__init__.py new file mode 100644 index 0000000..cefa3e7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/__init__.py @@ -0,0 +1,12 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_geometry.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_geometry.py new file mode 100644 index 0000000..ede07a4 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_geometry.py @@ -0,0 +1,196 @@ +""" +This page is in the table of contents. +The xml.py script is an import translator plugin to get a carving from an Art of Illusion xml file. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an xml file and returns the carving. + +An xml file can be exported from Art of Illusion by going to the "File" menu, then going into the "Export" menu item, then picking the XML choice. This will bring up the XML file chooser window, choose a place to save the file then click "OK". Leave the "compressFile" checkbox unchecked. All the objects from the scene will be exported, this plugin will ignore the light and camera. If you want to fabricate more than one object at a time, you can have multiple objects in the Art of Illusion scene and they will all be carved, then fabricated together. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import settings +from fabmetheus_utilities import xml_simple_writer +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getEmptyZLoops(archivableObjects, importRadius, shouldPrintWarning, z, zoneArrangement): + 'Get loops at empty z level.' + emptyZ = zoneArrangement.getEmptyZ(z) + visibleObjects = evaluate.getVisibleObjects(archivableObjects) + visibleObjectLoopsList = boolean_solid.getVisibleObjectLoopsList(importRadius, visibleObjects, emptyZ) + loops = euclidean.getConcatenatedList(visibleObjectLoopsList) + if euclidean.isLoopListIntersecting(loops): + loops = boolean_solid.getLoopsUnion(importRadius, visibleObjectLoopsList) + if shouldPrintWarning: + print('Warning, the triangle mesh slice intersects itself in getExtruderPaths in boolean_geometry.') + print('Something will still be printed, but there is no guarantee that it will be the correct shape.') + print('Once the gcode is saved, you should check over the layer with a z of:') + print(z) + return loops + +def getLoopLayers(archivableObjects, importRadius, layerHeight, maximumZ, shouldPrintWarning, z, zoneArrangement): + 'Get loop layers.' + loopLayers = [] + while z <= maximumZ: + triangle_mesh.getLoopLayerAppend(loopLayers, z).loops = getEmptyZLoops(archivableObjects, importRadius, True, z, zoneArrangement) + z += layerHeight + return loopLayers + +def getMinimumZ(geometryObject): + 'Get the minimum of the minimum z of the archivableObjects and the object.' + booleanGeometry = BooleanGeometry() + booleanGeometry.archivableObjects = geometryObject.archivableObjects + booleanGeometry.importRadius = setting.getImportRadius(geometryObject.elementNode) + booleanGeometry.layerHeight = setting.getLayerHeight(geometryObject.elementNode) + archivableMinimumZ = booleanGeometry.getMinimumZ() + geometryMinimumZ = geometryObject.getMinimumZ() + if archivableMinimumZ == None: + return geometryMinimumZ + if geometryMinimumZ == None: + return archivableMinimumZ + return min(archivableMinimumZ, geometryMinimumZ) + + +class BooleanGeometry: + 'A boolean geometry scene.' + def __init__(self): + 'Add empty lists.' + self.archivableObjects = [] + self.belowLoops = [] + self.importRadius = 0.6 + self.layerHeight = 0.4 + self.loopLayers = [] + + def __repr__(self): + 'Get the string representation of this carving.' + elementNode = None + if len(self.archivableObjects) > 0: + elementNode = self.archivableObjects[0].elementNode + output = xml_simple_writer.getBeginGeometryXMLOutput(elementNode) + self.addXML( 1, output ) + return xml_simple_writer.getEndGeometryXMLString(output) + + def addXML(self, depth, output): + 'Add xml for this object.' + xml_simple_writer.addXMLFromObjects( depth, self.archivableObjects, output ) + + def getCarveBoundaryLayers(self): + 'Get the boundary layers.' + if self.getMinimumZ() == None: + return [] + z = self.minimumZ + 0.5 * self.layerHeight + self.loopLayers = getLoopLayers(self.archivableObjects, self.importRadius, self.layerHeight, self.maximumZ, True, z, self.zoneArrangement) + self.cornerMaximum = Vector3(-912345678.0, -912345678.0, -912345678.0) + self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0) + for loopLayer in self.loopLayers: + for loop in loopLayer.loops: + for point in loop: + pointVector3 = Vector3(point.real, point.imag, loopLayer.z) + self.cornerMaximum.maximize(pointVector3) + self.cornerMinimum.minimize(pointVector3) + self.cornerMaximum.z += self.halfHeight + self.cornerMinimum.z -= self.halfHeight + for loopLayerIndex in xrange(len(self.loopLayers) -1, -1, -1): + loopLayer = self.loopLayers[loopLayerIndex] + if len(loopLayer.loops) > 0: + return self.loopLayers[: loopLayerIndex + 1] + return [] + + def getCarveCornerMaximum(self): + 'Get the corner maximum of the vertexes.' + return self.cornerMaximum + + def getCarveCornerMinimum(self): + 'Get the corner minimum of the vertexes.' + return self.cornerMinimum + + def getCarveLayerHeight(self): + 'Get the layer height.' + return self.layerHeight + + def getFabmetheusXML(self): + 'Return the fabmetheus XML.' + if len(self.archivableObjects) > 0: + return self.archivableObjects[0].elementNode.getOwnerDocument().getOriginalRoot() + return None + + def getInterpretationSuffix(self): + 'Return the suffix for a boolean carving.' + return 'xml' + + def getMatrix4X4(self): + 'Get the matrix4X4.' + return None + + def getMatrixChainTetragrid(self): + 'Get the matrix chain tetragrid.' + return None + + def getMinimumZ(self): + 'Get the minimum z.' + vertexes = [] + for visibleObject in evaluate.getVisibleObjects(self.archivableObjects): + vertexes += visibleObject.getTransformedVertexes() + if len(vertexes) < 1: + return None + self.maximumZ = -912345678.0 + self.minimumZ = 912345678.0 + for vertex in vertexes: + self.maximumZ = max(self.maximumZ, vertex.z) + self.minimumZ = min(self.minimumZ, vertex.z) + self.zoneArrangement = triangle_mesh.ZoneArrangement(self.layerHeight, vertexes) + self.halfHeight = 0.5 * self.layerHeight + self.setActualMinimumZ() + return self.minimumZ + + def getNumberOfEmptyZLoops(self, z): + 'Get number of empty z loops.' + return len(getEmptyZLoops(self.archivableObjects, self.importRadius, False, z, self.zoneArrangement)) + + def setActualMinimumZ(self): + 'Get the actual minimum z at the lowest rotated boundary layer.' + halfHeightOverMyriad = 0.0001 * self.halfHeight + while self.minimumZ < self.maximumZ: + if self.getNumberOfEmptyZLoops(self.minimumZ + halfHeightOverMyriad) > 0: + if self.getNumberOfEmptyZLoops(self.minimumZ - halfHeightOverMyriad) < 1: + return + increment = -self.halfHeight + while abs(increment) > halfHeightOverMyriad: + self.minimumZ += increment + increment = 0.5 * abs(increment) + if self.getNumberOfEmptyZLoops(self.minimumZ) > 0: + increment = -increment + self.minimumZ = round(self.minimumZ, -int(round(math.log10(halfHeightOverMyriad) + 1.5))) + return + self.minimumZ += self.layerHeight + + def setCarveImportRadius( self, importRadius ): + 'Set the import radius.' + self.importRadius = importRadius + + def setCarveIsCorrectMesh( self, isCorrectMesh ): + 'Set the is correct mesh flag.' + self.isCorrectMesh = isCorrectMesh + + def setCarveLayerHeight( self, layerHeight ): + 'Set the layer height.' + self.layerHeight = layerHeight diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_solid.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_solid.py new file mode 100644 index 0000000..1b2c303 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/boolean_solid.py @@ -0,0 +1,235 @@ +""" +This page is in the table of contents. +The xml.py script is an import translator plugin to get a carving from an Art of Illusion xml file. + +An import plugin is a script in the interpret_plugins folder which has the function getCarving. It is meant to be run from the interpret tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getCarving function takes the file name of an xml file and returns the carving. + +An xml file can be exported from Art of Illusion by going to the "File" menu, then going into the "Export" menu item, then picking the XML choice. This will bring up the XML file chooser window, choose a place to save the file then click "OK". Leave the "compressFile" checkbox unchecked. All the objects from the scene will be exported, this plugin will ignore the light and camera. If you want to fabricate more than one object at a time, you can have multiple objects in the Art of Illusion scene and they will all be carved, then fabricated together. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import group +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addLineLoopsIntersections( loopLoopsIntersections, loops, pointBegin, pointEnd ): + 'Add intersections of the line with the loops.' + normalizedSegment = pointEnd - pointBegin + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength <= 0.0: + return + lineLoopsIntersections = [] + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = segmentYMirror * pointBegin + pointEndRotated = segmentYMirror * pointEnd + addLoopsXSegmentIntersections( lineLoopsIntersections, loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ) + for lineLoopsIntersection in lineLoopsIntersections: + point = complex( lineLoopsIntersection, pointBeginRotated.imag ) * normalizedSegment + loopLoopsIntersections.append(point) + +def addLineXSegmentIntersection( lineLoopsIntersections, segmentFirstX, segmentSecondX, vector3First, vector3Second, y ): + 'Add intersections of the line with the x segment.' + xIntersection = euclidean.getXIntersectionIfExists( vector3First, vector3Second, y ) + if xIntersection == None: + return + if xIntersection < min( segmentFirstX, segmentSecondX ): + return + if xIntersection <= max( segmentFirstX, segmentSecondX ): + lineLoopsIntersections.append( xIntersection ) + +def addLoopLoopsIntersections( loop, loopsLoopsIntersections, otherLoops ): + 'Add intersections of the loop with the other loops.' + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + addLineLoopsIntersections( loopsLoopsIntersections, otherLoops, pointBegin, pointEnd ) + +def addLoopsXSegmentIntersections( lineLoopsIntersections, loops, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Add intersections of the loops with the x segment.' + for loop in loops: + addLoopXSegmentIntersections( lineLoopsIntersections, loop, segmentFirstX, segmentSecondX, segmentYMirror, y ) + +def addLoopXSegmentIntersections( lineLoopsIntersections, loop, segmentFirstX, segmentSecondX, segmentYMirror, y ): + 'Add intersections of the loop with the x segment.' + rotatedLoop = euclidean.getRotatedComplexes( segmentYMirror, loop ) + for pointIndex in xrange( len( rotatedLoop ) ): + pointFirst = rotatedLoop[pointIndex] + pointSecond = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ] + addLineXSegmentIntersection( lineLoopsIntersections, segmentFirstX, segmentSecondX, pointFirst, pointSecond, y ) + +def getInBetweenLoopsFromLoops(loops, radius): + 'Get the in between loops from loops.' + inBetweenLoops = [] + for loop in loops: + inBetweenLoop = [] + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + intercircle.addPointsFromSegment(pointBegin, pointEnd, inBetweenLoop, radius) + inBetweenLoops.append(inBetweenLoop) + return inBetweenLoops + +def getInsetPointsByInsetLoop( insetLoop, inside, loops, radius ): + 'Get the inset points of the inset loop inside the loops.' + insetPointsByInsetLoop = [] + for pointIndex in xrange( len( insetLoop ) ): + pointBegin = insetLoop[ ( pointIndex + len( insetLoop ) - 1 ) % len( insetLoop ) ] + pointCenter = insetLoop[pointIndex] + pointEnd = insetLoop[ (pointIndex + 1) % len( insetLoop ) ] + if getIsInsetPointInsideLoops( inside, loops, pointBegin, pointCenter, pointEnd, radius ): + insetPointsByInsetLoop.append( pointCenter ) + return insetPointsByInsetLoop + +def getInsetPointsByInsetLoops( insetLoops, inside, loops, radius ): + 'Get the inset points of the inset loops inside the loops.' + insetPointsByInsetLoops = [] + for insetLoop in insetLoops: + insetPointsByInsetLoops += getInsetPointsByInsetLoop( insetLoop, inside, loops, radius ) + return insetPointsByInsetLoops + +def getIsInsetPointInsideLoops( inside, loops, pointBegin, pointCenter, pointEnd, radius ): + 'Determine if the inset point is inside the loops.' + centerMinusBegin = euclidean.getNormalized( pointCenter - pointBegin ) + centerMinusBeginWiddershins = complex( - centerMinusBegin.imag, centerMinusBegin.real ) + endMinusCenter = euclidean.getNormalized( pointEnd - pointCenter ) + endMinusCenterWiddershins = complex( - endMinusCenter.imag, endMinusCenter.real ) + widdershinsNormalized = euclidean.getNormalized( centerMinusBeginWiddershins + endMinusCenterWiddershins ) * radius + return euclidean.getIsInFilledRegion( loops, pointCenter + widdershinsNormalized ) == inside + +def getLoopsDifference(importRadius, loopLists): + 'Get difference loops.' + halfImportRadius = 0.5 * importRadius # so that there are no misses on shallow angles + radiusSide = 0.01 * importRadius + negativeLoops = getLoopsUnion(importRadius, loopLists[1 :]) + intercircle.directLoops(False, negativeLoops) + positiveLoops = loopLists[0] + intercircle.directLoops(True, positiveLoops) + corners = getInsetPointsByInsetLoops(negativeLoops, True, positiveLoops, radiusSide) + corners += getInsetPointsByInsetLoops(positiveLoops, False, negativeLoops, radiusSide) + allPoints = corners[:] + allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(negativeLoops, halfImportRadius), True, positiveLoops, radiusSide) + allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(positiveLoops, halfImportRadius), False, negativeLoops, radiusSide) + return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius) + +def getLoopsIntersection(importRadius, loopLists): + 'Get intersection loops.' + intercircle.directLoopLists(True, loopLists) + if len(loopLists) < 1: + return [] + if len(loopLists) < 2: + return loopLists[0] + intercircle.directLoopLists(True, loopLists) + loopsIntersection = loopLists[0] + for loopList in loopLists[1 :]: + loopsIntersection = getLoopsIntersectionByPair(importRadius, loopsIntersection, loopList) + return loopsIntersection + +def getLoopsIntersectionByPair(importRadius, loopsFirst, loopsLast): + 'Get intersection loops for a pair of loop lists.' + halfImportRadius = 0.5 * importRadius # so that there are no misses on shallow angles + radiusSide = 0.01 * importRadius + corners = [] + corners += getInsetPointsByInsetLoops(loopsFirst, True, loopsLast, radiusSide) + corners += getInsetPointsByInsetLoops(loopsLast, True, loopsFirst, radiusSide) + allPoints = corners[:] + allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(loopsFirst, halfImportRadius), True, loopsLast, radiusSide) + allPoints += getInsetPointsByInsetLoops(getInBetweenLoopsFromLoops(loopsLast, halfImportRadius), True, loopsFirst, radiusSide) + return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius) + +def getLoopsListsIntersections( loopsList ): + 'Get intersections betweens the loops lists.' + loopsListsIntersections = [] + for loopsIndex in xrange( len( loopsList ) ): + loops = loopsList[ loopsIndex ] + for otherLoops in loopsList[ : loopsIndex ]: + loopsListsIntersections += getLoopsLoopsIntersections( loops, otherLoops ) + return loopsListsIntersections + +def getLoopsLoopsIntersections( loops, otherLoops ): + 'Get all the intersections of the loops with the other loops.' + loopsLoopsIntersections = [] + for loop in loops: + addLoopLoopsIntersections( loop, loopsLoopsIntersections, otherLoops ) + return loopsLoopsIntersections + +def getLoopsUnion(importRadius, loopLists): + 'Get joined loops sliced through shape.' + allPoints = [] + corners = getLoopsListsIntersections(loopLists) + radiusSideNegative = -0.01 * importRadius + intercircle.directLoopLists(True, loopLists) + for loopListIndex in xrange(len(loopLists)): + insetLoops = loopLists[ loopListIndex ] + inBetweenInsetLoops = getInBetweenLoopsFromLoops(insetLoops, importRadius) + otherLoops = euclidean.getConcatenatedList(loopLists[: loopListIndex] + loopLists[loopListIndex + 1 :]) + corners += getInsetPointsByInsetLoops(insetLoops, False, otherLoops, radiusSideNegative) + allPoints += getInsetPointsByInsetLoops(inBetweenInsetLoops, False, otherLoops, radiusSideNegative) + allPoints += corners[:] + return triangle_mesh.getDescendingAreaOrientedLoops(allPoints, corners, importRadius) + +def getVisibleObjectLoopsList( importRadius, visibleObjects, z ): + 'Get visible object loops list.' + visibleObjectLoopsList = [] + for visibleObject in visibleObjects: + visibleObjectLoops = visibleObject.getLoops(importRadius, z) + visibleObjectLoopsList.append( visibleObjectLoops ) + return visibleObjectLoopsList + + +class BooleanSolid( group.Group ): + 'A boolean solid object.' + def getDifference(self, importRadius, visibleObjectLoopsList): + 'Get subtracted loops sliced through shape.' + return getLoopsDifference(importRadius, visibleObjectLoopsList) + + def getIntersection(self, importRadius, visibleObjectLoopsList): + 'Get intersected loops sliced through shape.' + return getLoopsIntersection(importRadius, visibleObjectLoopsList) + + def getLoops(self, importRadius, z): + 'Get loops sliced through shape.' + visibleObjects = evaluate.getVisibleObjects(self.archivableObjects) + if len( visibleObjects ) < 1: + return [] + visibleObjectLoopsList = getVisibleObjectLoopsList( importRadius, visibleObjects, z ) + loops = self.getLoopsFromObjectLoopsList(importRadius, visibleObjectLoopsList) + return euclidean.getSimplifiedLoops( loops, importRadius ) + + def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList): + 'Get loops from visible object loops list.' + return self.operationFunction(importRadius, visibleObjectLoopsList) + + def getTransformedPaths(self): + 'Get all transformed paths.' + importRadius = setting.getImportRadius(self.elementNode) + loopsFromObjectLoopsList = self.getLoopsFromObjectLoopsList(importRadius, self.getComplexTransformedPathLists()) + return euclidean.getVector3Paths(loopsFromObjectLoopsList) + + def getUnion(self, importRadius, visibleObjectLoopsList): + 'Get joined loops sliced through shape.' + return getLoopsUnion(importRadius, visibleObjectLoopsList) + + def getXMLLocalName(self): + 'Get xml class name.' + return self.operationFunction.__name__.lower()[ len('get') : ] diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate.py new file mode 100644 index 0000000..0c968ce --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate.py @@ -0,0 +1,1940 @@ +""" +Evaluate expressions. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +import math +import os +import sys +import traceback + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalModuleFunctionsDictionary = {} + + +def addPrefixDictionary(dictionary, keys, value): + 'Add prefixed key values to dictionary.' + for key in keys: + dictionary[key.lstrip('_')] = value + +def addQuoteWord(evaluatorWords, word): + 'Add quote word and remainder if the word starts with a quote character or dollar sign, otherwise add the word.' + if len(word) < 2: + evaluatorWords.append(word) + return + firstCharacter = word[0] + if firstCharacter == '$': + dotIndex = word.find('.', 1) + if dotIndex > -1: + evaluatorWords.append(word[: dotIndex]) + evaluatorWords.append(word[dotIndex :]) + return + if firstCharacter != '"' and firstCharacter != "'": + evaluatorWords.append(word) + return + nextQuoteIndex = word.find(firstCharacter, 1) + if nextQuoteIndex < 0 or nextQuoteIndex == len(word) - 1: + evaluatorWords.append(word) + return + nextQuoteIndex += 1 + evaluatorWords.append(word[: nextQuoteIndex]) + evaluatorWords.append(word[nextQuoteIndex :]) + +def addToPathsRecursively(paths, vector3Lists): + 'Add to vector3 paths recursively.' + if vector3Lists.__class__ == Vector3 or vector3Lists.__class__ .__name__ == 'Vector3Index': + paths.append([ vector3Lists ]) + return + path = [] + for vector3List in vector3Lists: + if vector3List.__class__ == list: + addToPathsRecursively(paths, vector3List) + elif vector3List.__class__ == Vector3: + path.append(vector3List) + if len(path) > 0: + paths.append(path) + +def addValueToEvaluatedDictionary(elementNode, evaluatedDictionary, key): + 'Get the evaluated dictionary.' + value = getEvaluatedValueObliviously(elementNode, key) + if value == None: + valueString = str(elementNode.attributes[key]) + print('Warning, addValueToEvaluatedDictionary in evaluate can not get a value for:') + print(valueString) + evaluatedDictionary[key + '__Warning__'] = 'Can not evaluate: ' + valueString.replace('"', ' ').replace( "'", ' ') + else: + evaluatedDictionary[key] = value + +def addVector3ToElementNode(elementNode, key, vector3): + 'Add vector3 to xml element.' + elementNode.attributes[key] = '[%s,%s,%s]' % (vector3.x, vector3.y, vector3.z) + +def compareExecutionOrderAscending(module, otherModule): + 'Get comparison in order to sort modules in ascending execution order.' + if module.globalExecutionOrder < otherModule.globalExecutionOrder: + return -1 + if module.globalExecutionOrder > otherModule.globalExecutionOrder: + return 1 + if module.__name__ < otherModule.__name__: + return -1 + return int(module.__name__ > otherModule.__name__) + +def convertToPaths(dictionary): + 'Recursively convert any ElementNodes to paths.' + if dictionary.__class__ == Vector3 or dictionary.__class__.__name__ == 'Vector3Index': + return + keys = getKeys(dictionary) + if keys == None: + return + for key in keys: + value = dictionary[key] + if value.__class__.__name__ == 'ElementNode': + if value.xmlObject != None: + dictionary[key] = getFloatListListsByPaths(value.xmlObject.getPaths()) + else: + convertToPaths(dictionary[key]) + +def convertToTransformedPaths(dictionary): + 'Recursively convert any ElementNodes to paths.' + if dictionary.__class__ == Vector3 or dictionary.__class__.__name__ == 'Vector3Index': + return + keys = getKeys(dictionary) + if keys == None: + return + for key in keys: + value = dictionary[key] + if value.__class__.__name__ == 'ElementNode': + if value.xmlObject != None: + dictionary[key] = value.xmlObject.getTransformedPaths() + else: + convertToTransformedPaths(dictionary[key]) + +def executeLeftOperations( evaluators, operationLevel ): + 'Evaluate the expression value from the numeric and operation evaluators.' + for negativeIndex in xrange( - len(evaluators), - 1 ): + evaluatorIndex = negativeIndex + len(evaluators) + evaluators[evaluatorIndex].executeLeftOperation( evaluators, evaluatorIndex, operationLevel ) + +def executeNextEvaluatorArguments(evaluator, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the nextEvaluator arguments.' + if evaluator.value == None: + print('Warning, executeNextEvaluatorArguments in evaluate can not get a evaluator.value for:') + print(evaluatorIndex) + print(evaluators) + print(evaluator) + return + nextEvaluator.value = evaluator.value(*nextEvaluator.arguments) + del evaluators[evaluatorIndex] + +def executePairOperations(evaluators, operationLevel): + 'Evaluate the expression value from the numeric and operation evaluators.' + for negativeIndex in xrange(1 - len(evaluators), - 1): + evaluatorIndex = negativeIndex + len(evaluators) + evaluators[evaluatorIndex].executePairOperation(evaluators, evaluatorIndex, operationLevel) + +def getBracketEvaluators(bracketBeginIndex, bracketEndIndex, evaluators): + 'Get the bracket evaluators.' + return getEvaluatedExpressionValueEvaluators(evaluators[bracketBeginIndex + 1 : bracketEndIndex]) + +def getBracketsExist(evaluators): + 'Evaluate the expression value.' + bracketBeginIndex = None + for negativeIndex in xrange( - len(evaluators), 0 ): + bracketEndIndex = negativeIndex + len(evaluators) + evaluatorEnd = evaluators[ bracketEndIndex ] + evaluatorWord = evaluatorEnd.word + if evaluatorWord in ['(', '[', '{']: + bracketBeginIndex = bracketEndIndex + elif evaluatorWord in [')', ']', '}']: + if bracketBeginIndex == None: + print('Warning, bracketBeginIndex in evaluateBrackets in evaluate is None.') + print('This may be because the brackets are not balanced.') + print(evaluators) + del evaluators[ bracketEndIndex ] + return + evaluators[ bracketBeginIndex ].executeBracket(bracketBeginIndex, bracketEndIndex, evaluators) + evaluators[ bracketBeginIndex ].word = None + return True + return False + +def getBracketValuesDeleteEvaluator(bracketBeginIndex, bracketEndIndex, evaluators): + 'Get the bracket values and delete the evaluator.' + evaluatedExpressionValueEvaluators = getBracketEvaluators(bracketBeginIndex, bracketEndIndex, evaluators) + bracketValues = [] + for evaluatedExpressionValueEvaluator in evaluatedExpressionValueEvaluators: + bracketValues.append( evaluatedExpressionValueEvaluator.value ) + del evaluators[ bracketBeginIndex + 1: bracketEndIndex + 1 ] + return bracketValues + +def getCapitalizedSuffixKey(prefix, suffix): + 'Get key with capitalized suffix.' + if prefix == '' or prefix.endswith('.'): + return prefix + suffix + return prefix + suffix[:1].upper()+suffix[1:] + +def getDictionarySplitWords(dictionary, value): + 'Get split line for evaluators.' + if getIsQuoted(value): + return [value] + for dictionaryKey in dictionary.keys(): + value = value.replace(dictionaryKey, ' ' + dictionaryKey + ' ') + dictionarySplitWords = [] + for word in value.split(): + dictionarySplitWords.append(word) + return dictionarySplitWords + +def getElementNodeByKey(elementNode, key): + 'Get the xml element by key.' + if key not in elementNode.attributes: + return None + word = str(elementNode.attributes[key]).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__.__name__ == 'ElementNode': + return evaluatedLinkValue + print('Warning, could not get ElementNode in getElementNodeByKey in evaluate for:') + print(key) + print(evaluatedLinkValue) + print(elementNode) + return None + +def getElementNodeObject(evaluatedLinkValue): + 'Get ElementNodeObject.' + if evaluatedLinkValue.__class__.__name__ != 'ElementNode': + print('Warning, could not get ElementNode in getElementNodeObject in evaluate for:') + print(evaluatedLinkValue.__class__.__name__) + print(evaluatedLinkValue) + return None + if evaluatedLinkValue.xmlObject == None: + print('Warning, evaluatedLinkValue.xmlObject is None in getElementNodeObject in evaluate for:') + print(evaluatedLinkValue) + return None + return evaluatedLinkValue.xmlObject + +def getElementNodesByKey(elementNode, key): + 'Get the xml elements by key.' + if key not in elementNode.attributes: + return [] + word = str(elementNode.attributes[key]).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__.__name__ == 'ElementNode': + return [evaluatedLinkValue] + if evaluatedLinkValue.__class__ == list: + return evaluatedLinkValue + print('Warning, could not get ElementNodes in getElementNodesByKey in evaluate for:') + print(key) + print(evaluatedLinkValue) + print(elementNode) + return [] + +def getEndIndexConvertEquationValue( bracketEndIndex, evaluatorIndex, evaluators ): + 'Get the bracket end index and convert the equation value evaluators into a string.' + evaluator = evaluators[evaluatorIndex] + if evaluator.__class__ != EvaluatorValue: + return bracketEndIndex + if not evaluator.word.startswith('equation.'): + return bracketEndIndex + if evaluators[ evaluatorIndex + 1 ].word != ':': + return bracketEndIndex + valueBeginIndex = evaluatorIndex + 2 + equationValueString = '' + for valueEvaluatorIndex in xrange( valueBeginIndex, len(evaluators) ): + valueEvaluator = evaluators[ valueEvaluatorIndex ] + if valueEvaluator.word == ',' or valueEvaluator.word == '}': + if equationValueString == '': + return bracketEndIndex + else: + evaluators[ valueBeginIndex ] = EvaluatorValue( equationValueString ) + valueDeleteIndex = valueBeginIndex + 1 + del evaluators[ valueDeleteIndex : valueEvaluatorIndex ] + return bracketEndIndex - valueEvaluatorIndex + valueDeleteIndex + equationValueString += valueEvaluator.word + return bracketEndIndex + +def getEvaluatedBoolean(defaultValue, elementNode, key): + 'Get the evaluated boolean.' + if elementNode == None: + return defaultValue + if key in elementNode.attributes: + return euclidean.getBooleanFromValue(getEvaluatedValueObliviously(elementNode, key)) + return defaultValue + +def getEvaluatedDictionaryByCopyKeys(copyKeys, elementNode): + 'Get the evaluated dictionary by copyKeys.' + evaluatedDictionary = {} + for key in elementNode.attributes.keys(): + if key in copyKeys: + evaluatedDictionary[key] = elementNode.attributes[key] + else: + addValueToEvaluatedDictionary(elementNode, evaluatedDictionary, key) + return evaluatedDictionary + +def getEvaluatedDictionaryByEvaluationKeys(elementNode, evaluationKeys): + 'Get the evaluated dictionary.' + evaluatedDictionary = {} + for key in elementNode.attributes.keys(): + if key in evaluationKeys: + addValueToEvaluatedDictionary(elementNode, evaluatedDictionary, key) + return evaluatedDictionary + +def getEvaluatedExpressionValue(elementNode, value): + 'Evaluate the expression value.' + try: + return getEvaluatedExpressionValueBySplitLine(elementNode, getEvaluatorSplitWords(value)) + except: + print('Warning, in getEvaluatedExpressionValue in evaluate could not get a value for:') + print(value) + traceback.print_exc(file=sys.stdout) + return None + +def getEvaluatedExpressionValueBySplitLine(elementNode, words): + 'Evaluate the expression value.' + evaluators = [] + for wordIndex, word in enumerate(words): + nextWord = '' + nextWordIndex = wordIndex + 1 + if nextWordIndex < len(words): + nextWord = words[nextWordIndex] + evaluator = getEvaluator(elementNode, evaluators, nextWord, word) + if evaluator != None: + evaluators.append(evaluator) + while getBracketsExist(evaluators): + pass + evaluatedExpressionValueEvaluators = getEvaluatedExpressionValueEvaluators(evaluators) + if len( evaluatedExpressionValueEvaluators ) > 0: + return evaluatedExpressionValueEvaluators[0].value + return None + +def getEvaluatedExpressionValueEvaluators(evaluators): + 'Evaluate the expression value from the numeric and operation evaluators.' + for evaluatorIndex, evaluator in enumerate(evaluators): + evaluator.executeCenterOperation(evaluators, evaluatorIndex) + for negativeIndex in xrange(1 - len(evaluators), 0): + evaluatorIndex = negativeIndex + len(evaluators) + evaluators[evaluatorIndex].executeRightOperation(evaluators, evaluatorIndex) + executeLeftOperations(evaluators, 200) + for operationLevel in [80, 60, 40, 20, 15]: + executePairOperations(evaluators, operationLevel) + executeLeftOperations(evaluators, 13) + executePairOperations(evaluators, 12) + for negativeIndex in xrange(-len(evaluators), 0): + evaluatorIndex = negativeIndex + len(evaluators) + evaluators[evaluatorIndex].executePairOperation(evaluators, evaluatorIndex, 10) + for evaluatorIndex in xrange(len(evaluators) - 1, -1, -1): + evaluators[evaluatorIndex].executePairOperation(evaluators, evaluatorIndex, 0) + return evaluators + +def getEvaluatedFloat(defaultValue, elementNode, key): + 'Get the evaluated float.' + if elementNode == None: + return defaultValue + if key in elementNode.attributes: + return euclidean.getFloatFromValue(getEvaluatedValueObliviously(elementNode, key)) + return defaultValue + +def getEvaluatedInt(defaultValue, elementNode, key): + 'Get the evaluated int.' + if elementNode == None: + return None + if key in elementNode.attributes: + try: + return getIntFromFloatString(getEvaluatedValueObliviously(elementNode, key)) + except: + print('Warning, could not evaluate the int.') + print(key) + print(elementNode.attributes[key]) + return defaultValue + +def getEvaluatedIntByKeys(defaultValue, elementNode, keys): + 'Get the evaluated int by keys.' + for key in keys: + defaultValue = getEvaluatedInt(defaultValue, elementNode, key) + return defaultValue + +def getEvaluatedLinkValue(elementNode, word): + 'Get the evaluated link value.' + if word == '': + return '' + if getStartsWithCurlyEqualRoundSquare(word): + return getEvaluatedExpressionValue(elementNode, word) + return word + +def getEvaluatedString(defaultValue, elementNode, key): + 'Get the evaluated string.' + if elementNode == None: + return defaultValue + if key in elementNode.attributes: + return str(getEvaluatedValueObliviously(elementNode, key)) + return defaultValue + +def getEvaluatedValue(defaultValue, elementNode, key): + 'Get the evaluated value.' + if elementNode == None: + return defaultValue + if key in elementNode.attributes: + return getEvaluatedValueObliviously(elementNode, key) + return defaultValue + +def getEvaluatedValueObliviously(elementNode, key): + 'Get the evaluated value.' + value = str(elementNode.attributes[key]).strip() + if key == 'id' or key == 'name' or key == 'tags': + return value + return getEvaluatedLinkValue(elementNode, value) + +def getEvaluator(elementNode, evaluators, nextWord, word): + 'Get the evaluator.' + if word in globalSplitDictionary: + return globalSplitDictionary[word](elementNode, word) + firstCharacter = word[: 1] + if firstCharacter == "'" or firstCharacter == '"': + if len(word) > 1: + if firstCharacter == word[-1]: + return EvaluatorValue(word[1 : -1]) + if firstCharacter == '$': + return EvaluatorValue(word[1 :]) + dotIndex = word.find('.') + functions = elementNode.getXMLProcessor().functions + if dotIndex > -1 and len(word) > 1: + if dotIndex == 0 and word[1].isalpha(): + return EvaluatorAttribute(elementNode, word) + if dotIndex > 0: + untilDot = word[: dotIndex] + if untilDot in globalModuleEvaluatorDictionary: + return globalModuleEvaluatorDictionary[untilDot](elementNode, word) + if len(functions) > 0: + if untilDot in functions[-1].localDictionary: + return EvaluatorLocal(elementNode, word) + if firstCharacter.isalpha() or firstCharacter == '_': + if len(functions) > 0: + if word in functions[-1].localDictionary: + return EvaluatorLocal(elementNode, word) + wordElement = elementNode.getElementNodeByID(word) + if wordElement != None: + if wordElement.getNodeName() == 'class': + return EvaluatorClass(wordElement, word) + if wordElement.getNodeName() == 'function': + return EvaluatorFunction(wordElement, word) + return EvaluatorValue(word) + return EvaluatorNumeric(elementNode, word) + +def getEvaluatorSplitWords(value): + 'Get split words for evaluators.' + if value.startswith('='): + value = value[len('=') :] + if len(value) < 1: + return [] + global globalDictionaryOperatorBegin + uniqueQuoteIndex = 0 + word = '' + quoteString = None + quoteDictionary = {} + for characterIndex in xrange(len(value)): + character = value[characterIndex] + if character == '"' or character == "'": + if quoteString == None: + quoteString = '' + elif quoteString != None: + if character == quoteString[: 1]: + uniqueQuoteIndex = getUniqueQuoteIndex(uniqueQuoteIndex, value) + uniqueToken = getTokenByNumber(uniqueQuoteIndex) + quoteDictionary[uniqueToken] = quoteString + character + character = uniqueToken + quoteString = None + if quoteString == None: + word += character + else: + quoteString += character + beginSplitWords = getDictionarySplitWords(globalDictionaryOperatorBegin, word) + global globalSplitDictionaryOperator + evaluatorSplitWords = [] + for beginSplitWord in beginSplitWords: + if beginSplitWord in globalDictionaryOperatorBegin: + evaluatorSplitWords.append(beginSplitWord) + else: + evaluatorSplitWords += getDictionarySplitWords(globalSplitDictionaryOperator, beginSplitWord) + for evaluatorSplitWordIndex, evaluatorSplitWord in enumerate(evaluatorSplitWords): + for quoteDictionaryKey in quoteDictionary.keys(): + if quoteDictionaryKey in evaluatorSplitWord: + evaluatorSplitWords[evaluatorSplitWordIndex] = evaluatorSplitWord.replace(quoteDictionaryKey, quoteDictionary[quoteDictionaryKey]) + evaluatorTransitionWords = [] + for evaluatorSplitWord in evaluatorSplitWords: + addQuoteWord(evaluatorTransitionWords, evaluatorSplitWord) + return evaluatorTransitionWords + +def getFloatListFromBracketedString( bracketedString ): + 'Get list from a bracketed string.' + if not getIsBracketed( bracketedString ): + return None + bracketedString = bracketedString.strip().replace('[', '').replace(']', '').replace('(', '').replace(')', '') + if len( bracketedString ) < 1: + return [] + splitLine = bracketedString.split(',') + floatList = [] + for word in splitLine: + evaluatedFloat = euclidean.getFloatFromValue(word) + if evaluatedFloat != None: + floatList.append( evaluatedFloat ) + return floatList + +def getFloatListListsByPaths(paths): + 'Get float lists by paths.' + floatListLists = [] + for path in paths: + floatListList = [] + for point in path: + floatListList.append( point.getFloatList() ) + return floatListLists + +def getIntFromFloatString(value): + 'Get the int from the string.' + floatString = str(value).strip() + if floatString == '': + return None + dotIndex = floatString.find('.') + if dotIndex < 0: + return int(value) + return int( round( float(floatString) ) ) + +def getIsBracketed(word): + 'Determine if the word is bracketed.' + if len(word) < 2: + return False + firstCharacter = word[0] + lastCharacter = word[-1] + if firstCharacter == '(' and lastCharacter == ')': + return True + return firstCharacter == '[' and lastCharacter == ']' + +def getIsQuoted(word): + 'Determine if the word is quoted.' + if len(word) < 2: + return False + firstCharacter = word[0] + lastCharacter = word[-1] + if firstCharacter == '"' and lastCharacter == '"': + return True + return firstCharacter == "'" and lastCharacter == "'" + +def getKeys(repository): + 'Get keys for repository.' + repositoryClass = repository.__class__ + if repositoryClass == list or repositoryClass == tuple: + return range(len(repository)) + if repositoryClass == dict: + return repository.keys() + return None + +def getLocalAttributeValueString(key, valueString): + 'Get the local attribute value string with augmented assignment.' + augmentedStatements = '+= -= *= /= %= **='.split() + for augmentedStatement in augmentedStatements: + if valueString.startswith(augmentedStatement): + return key + augmentedStatement[: -1] + valueString[len(augmentedStatement) :] + return valueString + +def getMatchingPlugins(elementNode, namePathDictionary): + 'Get the plugins whose names are in the attribute dictionary.' + matchingPlugins = [] + namePathDictionaryCopy = namePathDictionary.copy() + for key in elementNode.attributes: + dotIndex = key.find('.') + if dotIndex > - 1: + keyUntilDot = key[: dotIndex] + if keyUntilDot in namePathDictionaryCopy: + pluginModule = archive.getModuleWithPath( namePathDictionaryCopy[ keyUntilDot ] ) + del namePathDictionaryCopy[ keyUntilDot ] + if pluginModule != None: + matchingPlugins.append( pluginModule ) + return matchingPlugins + +def getNextChildIndex(elementNode): + 'Get the next childNode index.' + for childNodeIndex, childNode in enumerate( elementNode.parentNode.childNodes ): + if childNode == elementNode: + return childNodeIndex + 1 + return len( elementNode.parentNode.childNodes ) + +def getPathByKey(defaultPath, elementNode, key): + 'Get path from prefix and xml element.' + if key not in elementNode.attributes: + return defaultPath + word = str(elementNode.attributes[key]).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__ == list: + return getPathByList(evaluatedLinkValue) + elementNodeObject = getElementNodeObject(evaluatedLinkValue) + if elementNodeObject == None: + return defaultPath + return elementNodeObject.getPaths()[0] + +def getPathByList(vertexList): + 'Get the paths by list.' + if len(vertexList) < 1: + return Vector3() + if vertexList[0].__class__ != list: + vertexList = [vertexList] + path = [] + for floatList in vertexList: + vector3 = getVector3ByFloatList(floatList, Vector3()) + path.append(vector3) + return path + +def getPathByPrefix(elementNode, path, prefix): + 'Get path from prefix and xml element.' + if len(path) < 2: + print('Warning, bug, path is too small in evaluate in setPathByPrefix.') + return + pathByKey = getPathByKey([], elementNode, getCapitalizedSuffixKey(prefix, 'path')) + if len( pathByKey ) < len(path): + for pointIndex in xrange( len( pathByKey ) ): + path[pointIndex] = pathByKey[pointIndex] + else: + path = pathByKey + path[0] = getVector3ByPrefix(path[0], elementNode, getCapitalizedSuffixKey(prefix, 'pathStart')) + path[-1] = getVector3ByPrefix(path[-1], elementNode, getCapitalizedSuffixKey(prefix, 'pathEnd')) + return path + +def getPathsByKey(defaultPaths, elementNode, key): + 'Get paths by key.' + if key not in elementNode.attributes: + return defaultPaths + word = str(elementNode.attributes[key]).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__ == dict or evaluatedLinkValue.__class__ == list: + convertToPaths(evaluatedLinkValue) + return getPathsByLists(evaluatedLinkValue) + elementNodeObject = getElementNodeObject(evaluatedLinkValue) + if elementNodeObject == None: + return defaultPaths + return elementNodeObject.getPaths() + +def getPathsByLists(vertexLists): + 'Get paths by lists.' + vector3Lists = getVector3ListsRecursively(vertexLists) + paths = [] + addToPathsRecursively(paths, vector3Lists) + return paths + +def getRadiusArealizedBasedOnAreaRadius(elementNode, radius, sides): + 'Get the areal radius from the radius, number of sides and cascade radiusAreal.' + if elementNode.getCascadeBoolean(False, 'radiusAreal'): + return radius + return radius * euclidean.getRadiusArealizedMultiplier(sides) + +def getSidesBasedOnPrecision(elementNode, radius): + 'Get the number of polygon sides.' + return int(math.ceil(math.sqrt(0.5 * radius / setting.getPrecision(elementNode)) * math.pi)) + +def getSidesMinimumThreeBasedOnPrecision(elementNode, radius): + 'Get the number of polygon sides, with a minimum of three.' + return max(getSidesBasedOnPrecision(elementNode, radius), 3) + +def getSidesMinimumThreeBasedOnPrecisionSides(elementNode, radius): + 'Get the number of polygon sides, with a minimum of three.' + sides = getSidesMinimumThreeBasedOnPrecision(elementNode, radius) + return getEvaluatedFloat(sides, elementNode, 'sides') + +def getSplitDictionary(): + 'Get split dictionary.' + global globalSplitDictionaryOperator + splitDictionary = globalSplitDictionaryOperator.copy() + global globalDictionaryOperatorBegin + splitDictionary.update( globalDictionaryOperatorBegin ) + splitDictionary['and'] = EvaluatorAnd + splitDictionary['false'] = EvaluatorFalse + splitDictionary['False'] = EvaluatorFalse + splitDictionary['or'] = EvaluatorOr + splitDictionary['not'] = EvaluatorNot + splitDictionary['true'] = EvaluatorTrue + splitDictionary['True'] = EvaluatorTrue + splitDictionary['none'] = EvaluatorNone + splitDictionary['None'] = EvaluatorNone + return splitDictionary + +def getStartsWithCurlyEqualRoundSquare(word): + 'Determine if the word starts with round or square brackets.' + return word.startswith('{') or word.startswith('=') or word.startswith('(') or word.startswith('[') + +def getTokenByNumber(number): + 'Get token by number.' + return '_%s_' % number + +def getTransformedPathByKey(defaultTransformedPath, elementNode, key): + 'Get transformed path from prefix and xml element.' + if key not in elementNode.attributes: + return defaultTransformedPath + value = elementNode.attributes[key] + if value.__class__ == list: + return value + word = str(value).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__ == list: + return getPathByList(evaluatedLinkValue) + elementNodeObject = getElementNodeObject(evaluatedLinkValueClass) + if elementNodeObject == None: + return defaultTransformedPath + return elementNodeObject.getTransformedPaths()[0] + +def getTransformedPathByPrefix(elementNode, path, prefix): + 'Get path from prefix and xml element.' + if len(path) < 2: + print('Warning, bug, path is too small in evaluate in setPathByPrefix.') + return + pathByKey = getTransformedPathByKey([], elementNode, getCapitalizedSuffixKey(prefix, 'path')) + if len( pathByKey ) < len(path): + for pointIndex in xrange( len( pathByKey ) ): + path[pointIndex] = pathByKey[pointIndex] + else: + path = pathByKey + path[0] = getVector3ByPrefix(path[0], elementNode, getCapitalizedSuffixKey(prefix, 'pathStart')) + path[-1] = getVector3ByPrefix(path[-1], elementNode, getCapitalizedSuffixKey(prefix, 'pathEnd')) + return path + +def getTransformedPathsByKey(defaultTransformedPaths, elementNode, key): + 'Get transformed paths by key.' + if key not in elementNode.attributes: + return defaultTransformedPaths + value = elementNode.attributes[key] + if value.__class__ == list: + return getPathsByLists(value) + word = str(value).strip() + evaluatedLinkValue = getEvaluatedLinkValue(elementNode, word) + if evaluatedLinkValue.__class__ == dict or evaluatedLinkValue.__class__ == list: + convertToTransformedPaths(evaluatedLinkValue) + return getPathsByLists(evaluatedLinkValue) + elementNodeObject = getElementNodeObject(evaluatedLinkValue) + if elementNodeObject == None: + return defaultTransformedPaths + return elementNodeObject.getTransformedPaths() + +def getUniqueQuoteIndex( uniqueQuoteIndex, word ): + 'Get uniqueQuoteIndex.' + uniqueQuoteIndex += 1 + while getTokenByNumber(uniqueQuoteIndex) in word: + uniqueQuoteIndex += 1 + return uniqueQuoteIndex + +def getUniqueToken(word): + 'Get unique token.' + uniqueString = '@#!' + for character in uniqueString: + if character not in word: + return character + uniqueNumber = 0 + while True: + for character in uniqueString: + uniqueToken = character + str(uniqueNumber) + if uniqueToken not in word: + return uniqueToken + uniqueNumber += 1 + +def getVector3ByDictionary( dictionary, vector3 ): + 'Get vector3 by dictionary.' + if 'x' in dictionary: + vector3 = getVector3IfNone(vector3) + vector3.x = euclidean.getFloatFromValue(dictionary['x']) + if 'y' in dictionary: + vector3 = getVector3IfNone(vector3) + vector3.y = euclidean.getFloatFromValue(dictionary['y']) + if 'z' in dictionary: + vector3 = getVector3IfNone(vector3) + vector3.z = euclidean.getFloatFromValue( dictionary['z'] ) + return vector3 + +def getVector3ByDictionaryListValue(value, vector3): + 'Get vector3 by dictionary, list or value.' + if value.__class__ == Vector3 or value.__class__.__name__ == 'Vector3Index': + return value + if value.__class__ == dict: + return getVector3ByDictionary(value, vector3) + if value.__class__ == list: + return getVector3ByFloatList(value, vector3) + floatFromValue = euclidean.getFloatFromValue(value) + if floatFromValue == None: + return vector3 + vector3.setToXYZ(floatFromValue, floatFromValue, floatFromValue) + return vector3 + +def getVector3ByFloatList(floatList, vector3): + 'Get vector3 by float list.' + if len(floatList) > 0: + vector3 = getVector3IfNone(vector3) + vector3.x = euclidean.getFloatFromValue(floatList[0]) + if len(floatList) > 1: + vector3 = getVector3IfNone(vector3) + vector3.y = euclidean.getFloatFromValue(floatList[1]) + if len(floatList) > 2: + vector3 = getVector3IfNone(vector3) + vector3.z = euclidean.getFloatFromValue(floatList[2]) + return vector3 + +def getVector3ByMultiplierPrefix( elementNode, multiplier, prefix, vector3 ): + 'Get vector3 from multiplier, prefix and xml element.' + if multiplier == 0.0: + return vector3 + oldMultipliedValueVector3 = vector3 * multiplier + vector3ByPrefix = getVector3ByPrefix(oldMultipliedValueVector3.copy(), elementNode, prefix) + if vector3ByPrefix == oldMultipliedValueVector3: + return vector3 + return vector3ByPrefix / multiplier + +def getVector3ByMultiplierPrefixes( elementNode, multiplier, prefixes, vector3 ): + 'Get vector3 from multiplier, prefixes and xml element.' + for prefix in prefixes: + vector3 = getVector3ByMultiplierPrefix( elementNode, multiplier, prefix, vector3 ) + return vector3 + +def getVector3ByPrefix(defaultVector3, elementNode, prefix): + 'Get vector3 from prefix and xml element.' + value = getEvaluatedValue(None, elementNode, prefix) + if value != None: + defaultVector3 = getVector3ByDictionaryListValue(value, defaultVector3) + prefix = archive.getUntilDot(prefix) + x = getEvaluatedFloat(None, elementNode, prefix + '.x') + if x != None: + defaultVector3 = getVector3IfNone(defaultVector3) + defaultVector3.x = x + y = getEvaluatedFloat(None, elementNode, prefix + '.y') + if y != None: + defaultVector3 = getVector3IfNone(defaultVector3) + defaultVector3.y = y + z = getEvaluatedFloat(None, elementNode, prefix + '.z') + if z != None: + defaultVector3 = getVector3IfNone(defaultVector3) + defaultVector3.z = z + return defaultVector3 + +def getVector3ByPrefixes( elementNode, prefixes, vector3 ): + 'Get vector3 from prefixes and xml element.' + for prefix in prefixes: + vector3 = getVector3ByPrefix(vector3, elementNode, prefix) + return vector3 + +def getVector3FromElementNode(elementNode): + 'Get vector3 from xml element.' + vector3 = Vector3( + getEvaluatedFloat(0.0, elementNode, 'x'), + getEvaluatedFloat(0.0, elementNode, 'y'), + getEvaluatedFloat(0.0, elementNode, 'z')) + return getVector3ByPrefix(vector3, elementNode, 'cartesian') + +def getVector3IfNone(vector3): + 'Get new vector3 if the original vector3 is none.' + if vector3 == None: + return Vector3() + return vector3 + +def getVector3ListsRecursively(floatLists): + 'Get vector3 lists recursively.' + if len(floatLists) < 1: + return Vector3() + firstElement = floatLists[0] + if firstElement.__class__ == Vector3: + return floatLists + if firstElement.__class__ != list: + return getVector3ByFloatList(floatLists, Vector3()) + vector3ListsRecursively = [] + for floatList in floatLists: + vector3ListsRecursively.append(getVector3ListsRecursively(floatList)) + return vector3ListsRecursively + +def getVisibleObjects(archivableObjects): + 'Get the visible objects.' + visibleObjects = [] + for archivableObject in archivableObjects: + if archivableObject.getVisible(): + visibleObjects.append(archivableObject) + return visibleObjects + +def processArchivable(archivableClass, elementNode): + 'Get any new elements and process the archivable.' + if elementNode == None: + return + elementNode.xmlObject = archivableClass() + elementNode.xmlObject.setToElementNode(elementNode) + elementNode.getXMLProcessor().processChildNodes(elementNode) + +def processCondition(elementNode): + 'Process the xml element condition.' + xmlProcessor = elementNode.getXMLProcessor() + if elementNode.xmlObject == None: + elementNode.xmlObject = ModuleElementNode(elementNode) + if elementNode.xmlObject.conditionSplitWords == None: + return + if len(xmlProcessor.functions ) < 1: + print('Warning, the (in) element is not in a function in processCondition in evaluate for:') + print(elementNode) + return + if int(getEvaluatedExpressionValueBySplitLine(elementNode, elementNode.xmlObject.conditionSplitWords)) > 0: + xmlProcessor.functions[-1].processChildNodes(elementNode) + else: + elementNode.xmlObject.processElse(elementNode) + +def removeIdentifiersFromDictionary(dictionary): + 'Remove the identifier elements from a dictionary.' + euclidean.removeElementsFromDictionary(dictionary, ['id', 'name', 'tags']) + return dictionary + +def setAttributesByArguments(argumentNames, arguments, elementNode): + 'Set the attribute dictionary to the arguments.' + for argumentIndex, argument in enumerate(arguments): + elementNode.attributes[argumentNames[argumentIndex]] = argument + +def setFunctionLocalDictionary(arguments, function): + 'Evaluate the function statement and delete the evaluators.' + function.localDictionary = {'_arguments' : arguments} + if len(arguments) > 0: + firstArgument = arguments[0] + if firstArgument.__class__ == dict: + function.localDictionary = firstArgument + return + if 'parameters' not in function.elementNode.attributes: + return + parameters = function.elementNode.attributes['parameters'].strip() + if parameters == '': + return + parameterWords = parameters.split(',') + for parameterWordIndex, parameterWord in enumerate(parameterWords): + strippedWord = parameterWord.strip() + keyValue = KeyValue().getByEqual(strippedWord) + if parameterWordIndex < len(arguments): + function.localDictionary[keyValue.key] = arguments[parameterWordIndex] + else: + strippedValue = keyValue.value + if strippedValue == None: + print('Warning there is no default parameter in getParameterValue for:') + print(strippedWord) + print(parameterWords) + print(arguments) + print(function.elementNode.attributes) + else: + strippedValue = strippedValue.strip() + function.localDictionary[keyValue.key.strip()] = strippedValue + if len(arguments) > len(parameterWords): + print('Warning there are too many initializeFunction parameters for:') + print(function.elementNode.attributes) + print(parameterWords) + print(arguments) + +def setLocalAttribute(elementNode): + 'Set the local attribute if any.' + if elementNode.xmlObject != None: + return + for key in elementNode.attributes: + if key[: 1].isalpha(): + value = getEvaluatorSplitWords(getLocalAttributeValueString(key, elementNode.attributes[key].strip())) + elementNode.xmlObject = KeyValue(key, value) + return + elementNode.xmlObject = KeyValue() + + +class BaseFunction: + 'Class to get equation results.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + self.localDictionary = {} + self.xmlProcessor = elementNode.getXMLProcessor() + + def __repr__(self): + 'Get the string representation of this Class.' + return str(self.__dict__) + + def getReturnValue(self): + 'Get return value.' + self.getReturnValueWithoutDeletion() + del self.xmlProcessor.functions[-1] + return self.returnValue + + def processChildNodes(self, elementNode): + 'Process childNodes if shouldReturn is false.' + for childNode in elementNode.childNodes: + if self.shouldReturn: + return + self.xmlProcessor.processElementNode(childNode) + + +class ClassFunction(BaseFunction): + 'Class to get class results.' + def getReturnValueByArguments(self, *arguments): + 'Get return value by arguments.' + setFunctionLocalDictionary(arguments, self) + return self.getReturnValue() + + def getReturnValueWithoutDeletion(self): + 'Get return value without deleting last function.' + self.returnValue = None + self.shouldReturn = False + self.xmlProcessor.functions.append(self) + self.processChildNodes(self.elementNode) + return self.returnValue + + +class ClassObject: + 'Class to hold class attributes and functions.' + def __init__(self, elementNode): + 'Initialize.' + self.functionDictionary = elementNode.xmlObject.functionDictionary + self.selfDictionary = {} + for variable in elementNode.xmlObject.variables: + self.selfDictionary[variable] = None + + def __repr__(self): + 'Get the string representation of this Class.' + return str(self.__dict__) + + def _getAccessibleAttribute(self, attributeName): + 'Get the accessible attribute.' + if attributeName in self.selfDictionary: + return self.selfDictionary[attributeName] + if attributeName in self.functionDictionary: + function = self.functionDictionary[attributeName] + function.classObject = self + return function.getReturnValueByArguments + return None + + def _setAccessibleAttribute(self, attributeName, value): + 'Set the accessible attribute.' + if attributeName in self.selfDictionary: + self.selfDictionary[attributeName] = value + + +class EmptyObject: + 'An empty object.' + def __init__(self): + 'Do nothing.' + pass + + +class Evaluator: + 'Base evaluator class.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.value = None + self.word = word + + def __repr__(self): + 'Get the string representation of this Class.' + return str(self.__dict__) + + def executeBracket( self, bracketBeginIndex, bracketEndIndex, evaluators ): + 'Execute the bracket.' + pass + + def executeCenterOperation(self, evaluators, evaluatorIndex): + 'Execute operator which acts on the center.' + pass + + def executeDictionary(self, dictionary, evaluators, keys, evaluatorIndex, nextEvaluator): + 'Execute the dictionary.' + del evaluators[evaluatorIndex] + enumeratorKeys = euclidean.getEnumeratorKeys(dictionary, keys) + if enumeratorKeys.__class__ == list: + nextEvaluator.value = [] + for enumeratorKey in enumeratorKeys: + if enumeratorKey in dictionary: + nextEvaluator.value.append(dictionary[enumeratorKey]) + else: + print('Warning, key in executeKey in Evaluator in evaluate is not in for:') + print(enumeratorKey) + print(dictionary) + return + if enumeratorKeys in dictionary: + nextEvaluator.value = dictionary[enumeratorKeys] + else: + print('Warning, key in executeKey in Evaluator in evaluate is not in for:') + print(enumeratorKeys) + print(dictionary) + + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + pass + + def executeKey(self, evaluators, keys, evaluatorIndex, nextEvaluator): + 'Execute the key index.' + if self.value.__class__ == str: + self.executeString(evaluators, keys, evaluatorIndex, nextEvaluator) + return + if self.value.__class__ == list: + self.executeList(evaluators, keys, evaluatorIndex, nextEvaluator) + return + if self.value.__class__ == dict: + self.executeDictionary(self.value, evaluators, keys, evaluatorIndex, nextEvaluator) + return + getAccessibleDictionaryFunction = getattr(self.value, '_getAccessibleDictionary', None) + if getAccessibleDictionaryFunction != None: + self.executeDictionary(getAccessibleDictionaryFunction(), evaluators, keys, evaluatorIndex, nextEvaluator) + return + if self.value.__class__.__name__ != 'ElementNode': + return + del evaluators[evaluatorIndex] + enumeratorKeys = euclidean.getEnumeratorKeys(self.value.attributes, keys) + if enumeratorKeys.__class__ == list: + nextEvaluator.value = [] + for enumeratorKey in enumeratorKeys: + if enumeratorKey in self.value.attributes: + nextEvaluator.value.append(getEvaluatedExpressionValue(self.value, self.value.attributes[enumeratorKey])) + else: + print('Warning, key in executeKey in Evaluator in evaluate is not in for:') + print(enumeratorKey) + print(self.value.attributes) + return + if enumeratorKeys in self.value.attributes: + nextEvaluator.value = getEvaluatedExpressionValue(self.value, self.value.attributes[enumeratorKeys]) + else: + print('Warning, key in executeKey in Evaluator in evaluate is not in for:') + print(enumeratorKeys) + print(self.value.attributes) + + def executeLeftOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Execute operator which acts from the left.' + pass + + def executeList(self, evaluators, keys, evaluatorIndex, nextEvaluator): + 'Execute the key index.' + del evaluators[evaluatorIndex] + enumeratorKeys = euclidean.getEnumeratorKeys(self.value, keys) + if enumeratorKeys.__class__ == list: + nextEvaluator.value = [] + for enumeratorKey in enumeratorKeys: + intKey = euclidean.getIntFromValue(enumeratorKey) + if self.getIsInRange(intKey): + nextEvaluator.value.append(self.value[intKey]) + else: + print('Warning, key in executeList in Evaluator in evaluate is not in for:') + print(enumeratorKey) + print(self.value) + return + intKey = euclidean.getIntFromValue(enumeratorKeys) + if self.getIsInRange(intKey): + nextEvaluator.value = self.value[intKey] + else: + print('Warning, key in executeList in Evaluator in evaluate is not in for:') + print(enumeratorKeys) + print(self.value) + + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + pass + + def executeRightOperation( self, evaluators, evaluatorIndex ): + 'Execute operator which acts from the right.' + pass + + def executeString(self, evaluators, keys, evaluatorIndex, nextEvaluator): + 'Execute the string.' + del evaluators[evaluatorIndex] + enumeratorKeys = euclidean.getEnumeratorKeys(self.value, keys) + if enumeratorKeys.__class__ == list: + nextEvaluator.value = '' + for enumeratorKey in enumeratorKeys: + intKey = euclidean.getIntFromValue(enumeratorKey) + if self.getIsInRange(intKey): + nextEvaluator.value += self.value[intKey] + else: + print('Warning, key in executeString in Evaluator in evaluate is not in for:') + print(enumeratorKey) + print(self.value) + return + intKey = euclidean.getIntFromValue(enumeratorKeys) + if self.getIsInRange(intKey): + nextEvaluator.value = self.value[intKey] + else: + print('Warning, key in executeString in Evaluator in evaluate is not in for:') + print(enumeratorKeys) + print(self.value) + + def getIsInRange(self, keyIndex): + 'Determine if the keyIndex is in range.' + if keyIndex == None: + return False + return keyIndex >= -len(self.value) and keyIndex < len(self.value) + + +class EvaluatorAddition(Evaluator): + 'Class to add two evaluators.' + def executePair( self, evaluators, evaluatorIndex ): + 'Add two evaluators.' + leftIndex = evaluatorIndex - 1 + rightIndex = evaluatorIndex + 1 + if leftIndex < 0: + print('Warning, no leftKey in executePair in EvaluatorAddition for:') + print(evaluators) + print(evaluatorIndex) + print(self) + del evaluators[evaluatorIndex] + return + if rightIndex >= len(evaluators): + print('Warning, no rightKey in executePair in EvaluatorAddition for:') + print(evaluators) + print(evaluatorIndex) + print(self) + del evaluators[evaluatorIndex] + return + rightValue = evaluators[rightIndex].value + evaluators[leftIndex].value = self.getOperationValue(evaluators[leftIndex].value, evaluators[rightIndex].value) + del evaluators[ evaluatorIndex : evaluatorIndex + 2 ] + + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel == 20: + self.executePair(evaluators, evaluatorIndex) + + def getEvaluatedValues(self, enumerable, keys, value): + 'Get evaluatedValues.' + if enumerable.__class__ == dict: + evaluatedValues = {} + for key in keys: + evaluatedValues[key] = self.getOperationValue(value, enumerable[key]) + return evaluatedValues + evaluatedValues = [] + for key in keys: + evaluatedValues.append(self.getOperationValue(value, enumerable[key])) + return evaluatedValues + + def getOperationValue(self, leftValue, rightValue): + 'Get operation value.' + leftKeys = getKeys(leftValue) + rightKeys = getKeys(rightValue) + if leftKeys == None and rightKeys == None: + return self.getValueFromValuePair(leftValue, rightValue) + if leftKeys == None: + return self.getEvaluatedValues(rightValue, rightKeys, leftValue) + if rightKeys == None: + return self.getEvaluatedValues(leftValue, leftKeys, rightValue) + leftKeys.sort(reverse=True) + rightKeys.sort(reverse=True) + if leftKeys != rightKeys: + print('Warning, the leftKeys are different from the rightKeys in getOperationValue in EvaluatorAddition for:') + print('leftValue') + print(leftValue) + print(leftKeys) + print('rightValue') + print(rightValue) + print(rightKeys) + print(self) + return None + if leftValue.__class__ == dict or rightValue.__class__ == dict: + evaluatedValues = {} + for leftKey in leftKeys: + evaluatedValues[leftKey] = self.getOperationValue(leftValue[leftKey], rightValue[leftKey]) + return evaluatedValues + evaluatedValues = [] + for leftKey in leftKeys: + evaluatedValues.append(self.getOperationValue(leftValue[leftKey], rightValue[leftKey])) + return evaluatedValues + + def getValueFromValuePair(self, leftValue, rightValue): + 'Add two values.' + return leftValue + rightValue + + +class EvaluatorEqual(EvaluatorAddition): + 'Class to compare two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel == 15: + self.executePair(evaluators, evaluatorIndex) + + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue == rightValue + + def getValueFromValuePair(self, leftValue, rightValue): + 'Get value from comparison.' + return self.getBooleanFromValuePair(leftValue, rightValue) + + +class EvaluatorSubtraction(EvaluatorAddition): + 'Class to subtract two evaluators.' + def executeLeft( self, evaluators, evaluatorIndex ): + 'Minus the value to the right.' + leftIndex = evaluatorIndex - 1 + rightIndex = evaluatorIndex + 1 + leftValue = None + if leftIndex >= 0: + leftValue = evaluators[leftIndex].value + if leftValue != None: + return + rightValue = evaluators[rightIndex].value + if rightValue == None: + print('Warning, can not minus.') + print(evaluators[rightIndex].word) + else: + evaluators[rightIndex].value = self.getNegativeValue(rightValue) + del evaluators[evaluatorIndex] + + def executeLeftOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Minus the value to the right.' + if operationLevel == 200: + self.executeLeft(evaluators, evaluatorIndex) + + def getNegativeValue( self, value ): + 'Get the negative value.' + keys = getKeys(value) + if keys == None: + return self.getValueFromSingleValue(value) + for key in keys: + value[key] = self.getNegativeValue(value[key]) + return value + + def getValueFromSingleValue( self, value ): + 'Minus value.' + return -value + + def getValueFromValuePair(self, leftValue, rightValue): + 'Subtract two values.' + return leftValue - rightValue + + +class EvaluatorAnd(EvaluatorAddition): + 'Class to compare two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel == 12: + self.executePair(evaluators, evaluatorIndex) + + def getBooleanFromValuePair(self, leftValue, rightValue): + 'And two values.' + return leftValue and rightValue + + def getValueFromValuePair(self, leftValue, rightValue): + 'Get value from comparison.' + return self.getBooleanFromValuePair(leftValue, rightValue) + + +class EvaluatorAttribute(Evaluator): + 'Class to handle an attribute.' + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + executeNextEvaluatorArguments(self, evaluators, evaluatorIndex, nextEvaluator) + + def executeRightOperation( self, evaluators, evaluatorIndex ): + 'Execute operator which acts from the right.' + attributeName = self.word[1 :] + previousIndex = evaluatorIndex - 1 + previousEvaluator = evaluators[previousIndex] + if previousEvaluator.value.__class__ == dict: + from fabmetheus_utilities.geometry.geometry_utilities.evaluate_enumerables import dictionary_attribute + self.value = dictionary_attribute._getAccessibleAttribute(attributeName, previousEvaluator.value) + elif previousEvaluator.value.__class__ == list: + from fabmetheus_utilities.geometry.geometry_utilities.evaluate_enumerables import list_attribute + self.value = list_attribute._getAccessibleAttribute(attributeName, previousEvaluator.value) + elif previousEvaluator.value.__class__ == str: + from fabmetheus_utilities.geometry.geometry_utilities.evaluate_enumerables import string_attribute + self.value = string_attribute._getAccessibleAttribute(attributeName, previousEvaluator.value) + else: + attributeKeywords = attributeName.split('.') + self.value = previousEvaluator.value + for attributeKeyword in attributeKeywords: + self.value = getattr(self.value, '_getAccessibleAttribute', None)(attributeKeyword) + if self.value == None: + print('Warning, EvaluatorAttribute in evaluate can not get a getAccessibleAttributeFunction for:') + print(attributeName) + print(previousEvaluator.value) + print(self) + return + del evaluators[previousIndex] + + +class EvaluatorBracketCurly(Evaluator): + 'Class to evaluate a string.' + def executeBracket(self, bracketBeginIndex, bracketEndIndex, evaluators): + 'Execute the bracket.' + for evaluatorIndex in xrange(bracketEndIndex - 3, bracketBeginIndex, - 1): + bracketEndIndex = getEndIndexConvertEquationValue(bracketEndIndex, evaluatorIndex, evaluators) + evaluatedExpressionValueEvaluators = getBracketEvaluators(bracketBeginIndex, bracketEndIndex, evaluators) + self.value = {} + for evaluatedExpressionValueEvaluator in evaluatedExpressionValueEvaluators: + keyValue = evaluatedExpressionValueEvaluator.value + self.value[keyValue.key] = keyValue.value + del evaluators[bracketBeginIndex + 1: bracketEndIndex + 1] + + +class EvaluatorBracketRound(Evaluator): + 'Class to evaluate a string.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.arguments = [] + self.value = None + self.word = word + + def executeBracket( self, bracketBeginIndex, bracketEndIndex, evaluators ): + 'Execute the bracket.' + self.arguments = getBracketValuesDeleteEvaluator(bracketBeginIndex, bracketEndIndex, evaluators) + if len( self.arguments ) < 1: + return + if len( self.arguments ) > 1: + self.value = self.arguments + else: + self.value = self.arguments[0] + + def executeRightOperation( self, evaluators, evaluatorIndex ): + 'Evaluate the statement and delete the evaluators.' + previousIndex = evaluatorIndex - 1 + if previousIndex < 0: + return + evaluators[ previousIndex ].executeFunction( evaluators, previousIndex, self ) + + +class EvaluatorBracketSquare(Evaluator): + 'Class to evaluate a string.' + def executeBracket( self, bracketBeginIndex, bracketEndIndex, evaluators ): + 'Execute the bracket.' + self.value = getBracketValuesDeleteEvaluator(bracketBeginIndex, bracketEndIndex, evaluators) + + def executeRightOperation( self, evaluators, evaluatorIndex ): + 'Evaluate the statement and delete the evaluators.' + previousIndex = evaluatorIndex - 1 + if previousIndex < 0: + return + if self.value.__class__ != list: + return + evaluators[ previousIndex ].executeKey( evaluators, self.value, previousIndex, self ) + + +class EvaluatorClass(Evaluator): + 'Class evaluator class.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.elementNode = elementNode + self.value = None + self.word = word + + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + if self.elementNode.xmlObject == None: + self.elementNode.xmlObject = FunctionVariable(self.elementNode) + nextEvaluator.value = ClassObject(self.elementNode) + initializeFunction = None + if '_init' in self.elementNode.xmlObject.functionDictionary: + function = self.elementNode.xmlObject.functionDictionary['_init'] + function.classObject = nextEvaluator.value + setFunctionLocalDictionary(nextEvaluator.arguments, function) + function.getReturnValue() + del evaluators[evaluatorIndex] + + +class EvaluatorComma(Evaluator): + 'Class to join two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel != 0: + return + previousIndex = evaluatorIndex - 1 + if previousIndex < 0: + evaluators[evaluatorIndex].value = None + return + if evaluators[previousIndex].word == ',': + evaluators[evaluatorIndex].value = None + return + del evaluators[evaluatorIndex] + + +class EvaluatorConcatenate(Evaluator): + 'Class to join two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel != 80: + return + leftIndex = evaluatorIndex - 1 + if leftIndex < 0: + del evaluators[evaluatorIndex] + return + rightIndex = evaluatorIndex + 1 + if rightIndex >= len(evaluators): + del evaluators[ leftIndex : rightIndex ] + return + leftValue = evaluators[leftIndex].value + rightValue = evaluators[rightIndex].value + if leftValue.__class__ == rightValue.__class__ and (leftValue.__class__ == list or rightValue.__class__ == str): + evaluators[leftIndex].value = leftValue + rightValue + del evaluators[ evaluatorIndex : evaluatorIndex + 2 ] + return + if leftValue.__class__ == list and rightValue.__class__ == int: + if rightValue > 0: + originalList = leftValue[:] + for copyIndex in xrange( rightValue - 1 ): + leftValue += originalList + evaluators[leftIndex].value = leftValue + del evaluators[ evaluatorIndex : evaluatorIndex + 2 ] + return + if leftValue.__class__ == dict and rightValue.__class__ == dict: + leftValue.update(rightValue) + evaluators[leftIndex].value = leftValue + del evaluators[ evaluatorIndex : evaluatorIndex + 2 ] + return + del evaluators[ leftIndex : evaluatorIndex + 2 ] + + +class EvaluatorDictionary(Evaluator): + 'Class to join two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel != 10: + return + leftEvaluatorIndex = evaluatorIndex - 1 + if leftEvaluatorIndex < 0: + print('Warning, leftEvaluatorIndex is less than zero in EvaluatorDictionary for:') + print(self) + print(evaluators) + return + rightEvaluatorIndex = evaluatorIndex + 1 + if rightEvaluatorIndex >= len(evaluators): + print('Warning, rightEvaluatorIndex too high in EvaluatorDictionary for:') + print(rightEvaluatorIndex) + print(self) + print(evaluators) + return + evaluators[rightEvaluatorIndex].value = KeyValue(evaluators[leftEvaluatorIndex].value, evaluators[rightEvaluatorIndex].value) + del evaluators[ leftEvaluatorIndex : rightEvaluatorIndex ] + + +class EvaluatorDivision(EvaluatorAddition): + 'Class to divide two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel == 40: + self.executePair(evaluators, evaluatorIndex) + + def getValueFromValuePair(self, leftValue, rightValue): + 'Divide two values.' + return leftValue / rightValue + + +class EvaluatorElement(Evaluator): + 'Element evaluator class.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.elementNode = elementNode + self.value = None + self.word = word + + def executeCenterOperation(self, evaluators, evaluatorIndex): + 'Execute operator which acts on the center.' + dotIndex = self.word.find('.') + if dotIndex < 0: + print('Warning, EvaluatorElement in evaluate can not find the dot for:') + print(functionName) + print(self) + return + attributeName = self.word[dotIndex + 1 :] + moduleName = self.word[: dotIndex] + if moduleName in globalModuleFunctionsDictionary: + self.value = globalModuleFunctionsDictionary[moduleName](attributeName, self.elementNode) + return + pluginModule = None + if moduleName in globalElementNameSet: + pluginModule = archive.getModuleWithPath(archive.getElementsPath(moduleName)) + if pluginModule == None: + print('Warning, EvaluatorElement in evaluate can not get a pluginModule for:') + print(moduleName) + print(self) + return + getAccessibleAttributeFunction = pluginModule._getAccessibleAttribute + globalModuleFunctionsDictionary[moduleName] = getAccessibleAttributeFunction + self.value = getAccessibleAttributeFunction(attributeName, self.elementNode) + + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + executeNextEvaluatorArguments(self, evaluators, evaluatorIndex, nextEvaluator) + + +class EvaluatorFalse(Evaluator): + 'Class to evaluate a string.' + def __init__(self, elementNode, word): + 'Set value to zero.' + self.value = False + self.word = word + + +class EvaluatorFunction(Evaluator): + 'Function evaluator class.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.elementNode = elementNode + self.value = None + self.word = word + + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + if self.elementNode.xmlObject == None: + if 'return' in self.elementNode.attributes: + value = self.elementNode.attributes['return'] + self.elementNode.xmlObject = getEvaluatorSplitWords(value) + else: + self.elementNode.xmlObject = [] + self.function = Function(self.elementNode ) + setFunctionLocalDictionary(nextEvaluator.arguments, self.function) + nextEvaluator.value = self.function.getReturnValue() + del evaluators[evaluatorIndex] + + +class EvaluatorFundamental(Evaluator): + 'Fundamental evaluator class.' + def executeCenterOperation(self, evaluators, evaluatorIndex): + 'Execute operator which acts on the center.' + dotIndex = self.word.find('.') + if dotIndex < 0: + print('Warning, EvaluatorFundamental in evaluate can not find the dot for:') + print(functionName) + print(self) + return + attributeName = self.word[dotIndex + 1 :] + moduleName = self.word[: dotIndex] + if moduleName in globalModuleFunctionsDictionary: + self.value = globalModuleFunctionsDictionary[moduleName](attributeName) + return + pluginModule = None + if moduleName in globalFundamentalNameSet: + pluginModule = archive.getModuleWithPath(archive.getFundamentalsPath(moduleName)) + else: + underscoredName = '_' + moduleName + if underscoredName in globalFundamentalNameSet: + pluginModule = archive.getModuleWithPath(archive.getFundamentalsPath(underscoredName)) + if pluginModule == None: + print('Warning, EvaluatorFundamental in evaluate can not get a pluginModule for:') + print(moduleName) + print(self) + return + getAccessibleAttributeFunction = pluginModule._getAccessibleAttribute + globalModuleFunctionsDictionary[moduleName] = getAccessibleAttributeFunction + self.value = getAccessibleAttributeFunction(attributeName) + + def executeFunction(self, evaluators, evaluatorIndex, nextEvaluator): + 'Execute the function.' + executeNextEvaluatorArguments(self, evaluators, evaluatorIndex, nextEvaluator) + + +class EvaluatorGreaterEqual( EvaluatorEqual ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue >= rightValue + + +class EvaluatorGreater( EvaluatorEqual ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue > rightValue + + +class EvaluatorLessEqual( EvaluatorEqual ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue <= rightValue + + +class EvaluatorLess( EvaluatorEqual ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue < rightValue + + +class EvaluatorLocal(EvaluatorElement): + 'Class to get a local variable.' + def executeCenterOperation(self, evaluators, evaluatorIndex): + 'Execute operator which acts on the center.' + functions = self.elementNode.getXMLProcessor().functions + if len(functions) < 1: + print('Warning, there are no functions in EvaluatorLocal in evaluate for:') + print(self.word) + return + attributeKeywords = self.word.split('.') + self.value = functions[-1].localDictionary[attributeKeywords[0]] + for attributeKeyword in attributeKeywords[1 :]: + self.value = self.value._getAccessibleAttribute(attributeKeyword) + + +class EvaluatorModulo( EvaluatorDivision ): + 'Class to modulo two evaluators.' + def getValueFromValuePair(self, leftValue, rightValue): + 'Modulo two values.' + return leftValue % rightValue + + +class EvaluatorMultiplication( EvaluatorDivision ): + 'Class to multiply two evaluators.' + def getValueFromValuePair(self, leftValue, rightValue): + 'Multiply two values.' + return leftValue * rightValue + + +class EvaluatorNone(Evaluator): + 'Class to evaluate None.' + def __init__(self, elementNode, word): + 'Set value to none.' + self.value = None + self.word = str(word) + + +class EvaluatorNot(EvaluatorSubtraction): + 'Class to compare two evaluators.' + def executeLeftOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Minus the value to the right.' + if operationLevel == 13: + self.executeLeft(evaluators, evaluatorIndex) + + def getValueFromSingleValue( self, value ): + 'Minus value.' + return not value + + +class EvaluatorNotEqual( EvaluatorEqual ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Compare two values.' + return leftValue != rightValue + + +class EvaluatorNumeric(Evaluator): + 'Class to evaluate a string.' + def __init__(self, elementNode, word): + 'Set value.' + self.value = None + self.word = word + try: + if '.' in word: + self.value = float(word) + else: + self.value = int(word) + except: + print('Warning, EvaluatorNumeric in evaluate could not get a numeric value for:') + print(word) + print(elementNode) + + +class EvaluatorOr( EvaluatorAnd ): + 'Class to compare two evaluators.' + def getBooleanFromValuePair(self, leftValue, rightValue): + 'Or two values.' + return leftValue or rightValue + + +class EvaluatorPower(EvaluatorAddition): + 'Class to power two evaluators.' + def executePairOperation(self, evaluators, evaluatorIndex, operationLevel): + 'Operate on two evaluators.' + if operationLevel == 60: + self.executePair(evaluators, evaluatorIndex) + + def getValueFromValuePair(self, leftValue, rightValue): + 'Power of two values.' + return leftValue ** rightValue + + +class EvaluatorSelf(EvaluatorElement): + 'Class to handle self.' + def executeCenterOperation(self, evaluators, evaluatorIndex): + 'Execute operator which acts on the center.' + functions = self.elementNode.getXMLProcessor().functions + if len(functions) < 1: + print('Warning, there are no functions in executeCenterOperation in EvaluatorSelf in evaluate for:') + print(self.elementNode) + return + function = functions[-1] + attributeKeywords = self.word.split('.') + self.value = function.classObject + for attributeKeyword in attributeKeywords[1 :]: + self.value = self.value._getAccessibleAttribute(attributeKeyword) + + +class EvaluatorTrue(Evaluator): + 'Class to evaluate a string.' + def __init__(self, elementNode, word): + 'Set value to true.' + self.value = True + self.word = word + + +class EvaluatorValue(Evaluator): + 'Class to evaluate a string.' + def __init__(self, word): + 'Set value to none.' + self.value = word + self.word = str(word) + + +class Function(BaseFunction): + 'Class to get equation results.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + self.evaluatorSplitLine = elementNode.xmlObject + self.localDictionary = {} + self.xmlProcessor = elementNode.getXMLProcessor() + + def getReturnValueWithoutDeletion(self): + 'Get return value without deleting last function.' + self.returnValue = None + self.xmlProcessor.functions.append(self) + if len(self.evaluatorSplitLine) < 1: + self.shouldReturn = False + self.processChildNodes(self.elementNode) + else: + self.returnValue = getEvaluatedExpressionValueBySplitLine(self.elementNode, self.evaluatorSplitLine) + return self.returnValue + + +class FunctionVariable: + 'Class to hold class functions and variable set.' + def __init__(self, elementNode): + 'Initialize.' + self.functionDictionary = {} + self.variables = [] + self.processClass(elementNode) + + def addToVariableSet(self, elementNode): + 'Add to variables.' + setLocalAttribute(elementNode) + keySplitLine = elementNode.xmlObject.key.split('.') + if len(keySplitLine) == 2: + if keySplitLine[0] == 'self': + variable = keySplitLine[1] + if variable not in self.variables: + self.variables.append(variable) + + def processClass(self, elementNode): + 'Add class to FunctionVariable.' + for childNode in elementNode.childNodes: + self.processFunction(childNode) + if 'parentNode' in elementNode.attributes: + self.processClass(elementNode.getElementNodeByID(elementNode.attributes['parentNode'])) + + def processFunction(self, elementNode): + 'Add function to function dictionary.' + if elementNode.getNodeName() != 'function': + return + idKey = elementNode.attributes['id'] + if idKey in self.functionDictionary: + return + self.functionDictionary[idKey] = ClassFunction(elementNode) + for childNode in elementNode.childNodes: + self.processStatement(childNode) + + def processStatement(self, elementNode): + 'Add self statement to variables.' + if elementNode.getNodeName() == 'statement': + self.addToVariableSet(elementNode) + for childNode in elementNode.childNodes: + self.processStatement(childNode) + + +class KeyValue: + 'Class to hold a key value.' + def __init__(self, key=None, value=None): + 'Get key value.' + self.key = key + self.value = value + + def __repr__(self): + 'Get the string representation of this KeyValue.' + return str(self.__dict__) + + def getByCharacter( self, character, line ): + 'Get by character.' + dotIndex = line.find( character ) + if dotIndex < 0: + self.key = line + self.value = None + return self + self.key = line[: dotIndex] + self.value = line[dotIndex + 1 :] + return self + + def getByDot(self, line): + 'Get by dot.' + return self.getByCharacter('.', line ) + + def getByEqual(self, line): + 'Get by dot.' + return self.getByCharacter('=', line ) + + +class ModuleElementNode: + 'Class to get the in attribute, the index name and the value name.' + def __init__( self, elementNode): + 'Initialize.' + self.conditionSplitWords = None + self.elseElement = None + if 'condition' in elementNode.attributes: + self.conditionSplitWords = getEvaluatorSplitWords( elementNode.attributes['condition'] ) + else: + print('Warning, could not find the condition attribute in ModuleElementNode in evaluate for:') + print(elementNode) + return + if len( self.conditionSplitWords ) < 1: + self.conditionSplitWords = None + print('Warning, could not get split words for the condition attribute in ModuleElementNode in evaluate for:') + print(elementNode) + nextIndex = getNextChildIndex(elementNode) + if nextIndex >= len( elementNode.parentNode.childNodes ): + return + nextElementNode = elementNode.parentNode.childNodes[ nextIndex ] + lowerLocalName = nextElementNode.getNodeName().lower() + if lowerLocalName != 'else' and lowerLocalName != 'elif': + return + xmlProcessor = elementNode.getXMLProcessor() + if lowerLocalName not in xmlProcessor.namePathDictionary: + return + self.pluginModule = archive.getModuleWithPath( xmlProcessor.namePathDictionary[ lowerLocalName ] ) + if self.pluginModule == None: + return + self.elseElement = nextElementNode + + def processElse(self, elementNode): + 'Process the else statement.' + if self.elseElement != None: + self.pluginModule.processElse( self.elseElement) + + +globalCreationDictionary = archive.getGeometryDictionary('creation') +globalDictionaryOperatorBegin = { + '||' : EvaluatorConcatenate, + '==' : EvaluatorEqual, + '>=' : EvaluatorGreaterEqual, + '<=' : EvaluatorLessEqual, + '!=' : EvaluatorNotEqual, + '**' : EvaluatorPower } +globalModuleEvaluatorDictionary = {} +globalFundamentalNameSet = set(archive.getPluginFileNamesFromDirectoryPath(archive.getFundamentalsPath())) +addPrefixDictionary(globalModuleEvaluatorDictionary, globalFundamentalNameSet, EvaluatorFundamental) +globalElementNameSet = set(archive.getPluginFileNamesFromDirectoryPath(archive.getElementsPath())) +addPrefixDictionary(globalModuleEvaluatorDictionary, globalElementNameSet, EvaluatorElement) +globalModuleEvaluatorDictionary['self'] = EvaluatorSelf +globalSplitDictionaryOperator = { + '+' : EvaluatorAddition, + '{' : EvaluatorBracketCurly, + '}' : Evaluator, + '(' : EvaluatorBracketRound, + ')' : Evaluator, + '[' : EvaluatorBracketSquare, + ']' : Evaluator, + ',' : EvaluatorComma, + ':' : EvaluatorDictionary, + '/' : EvaluatorDivision, + '>' : EvaluatorGreater, + '<' : EvaluatorLess, + '%' : EvaluatorModulo, + '*' : EvaluatorMultiplication, + '-' : EvaluatorSubtraction } +globalSplitDictionary = getSplitDictionary() # must be after globalSplitDictionaryOperator diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/__init__.py new file mode 100644 index 0000000..58ec332 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 4 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/creation.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/creation.py new file mode 100644 index 0000000..86f79af --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/creation.py @@ -0,0 +1,60 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, elementNode): + 'Get the accessible attribute.' + functionName = attributeName[len('get') :].lower() + if functionName not in evaluate.globalCreationDictionary: + print('Warning, functionName not in globalCreationDictionary in _getAccessibleAttribute in creation for:') + print(functionName) + print(elementNode) + return None + pluginModule = archive.getModuleWithPath(evaluate.globalCreationDictionary[functionName]) + if pluginModule == None: + print('Warning, _getAccessibleAttribute in creation can not get a pluginModule for:') + print(functionName) + print(elementNode) + return None + return Creation(elementNode, pluginModule).getCreation + + +class Creation: + 'Class to handle a creation.' + def __init__(self, elementNode, pluginModule): + 'Initialize.' + self.elementNode = elementNode + self.pluginModule = pluginModule + + def __repr__(self): + "Get the string representation of this creation." + return self.elementNode + + def getCreation(self, *arguments): + "Get creation." + dictionary = {'_fromCreationEvaluator': 'true'} + firstArgument = None + if len(arguments) > 0: + firstArgument = arguments[0] + if firstArgument.__class__ == dict: + dictionary.update(firstArgument) + return self.pluginModule.getGeometryOutput(None, self.elementNode.getCopyShallow(dictionary)) + copyShallow = self.elementNode.getCopyShallow(dictionary) + return self.pluginModule.getGeometryOutputByArguments(arguments, copyShallow) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/document.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/document.py new file mode 100644 index 0000000..f956591 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/document.py @@ -0,0 +1,97 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, elementNode): + 'Get the accessible attribute.' + if attributeName in globalGetAccessibleAttributeSet: + return getattr(Document(elementNode), attributeName, None) + return None + + +class Document: + 'Class to handle elementNodes in a document.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + + def __repr__(self): + 'Get the string representation of this Document.' + return self.elementNode + + def getCascadeBoolean(self, defaultBoolean, key): + 'Get cascade boolean.' + return self.elementNode.getCascadeBoolean(defaultBoolean, key) + + def getCascadeFloat(self, defaultFloat, key): + 'Get cascade float.' + return self.elementNode.getCascadeFloat(defaultFloat, key) + + def getDocumentElement(self): + 'Get document element element.' + return self.elementNode.getDocumentElement() + + def getElementByID(self, idKey): + 'Get element by id.' + elementByID = self.elementNode.getElementNodeByID(idKey) + if elementByID == None: + print('Warning, could not get elementByID in getElementByID in document for:') + print(idKey) + print(self.elementNode) + return elementByID + + def getElementsByName(self, nameKey): + 'Get element by name.' + elementsByName = self.elementNode.getElementNodesByName(nameKey) + if elementsByName == None: + print('Warning, could not get elementsByName in getElementsByName in document for:') + print(nameKey) + print(self.elementNode) + return elementsByName + + def getElementsByTag(self, tagKey): + 'Get element by tag.' + elementsByTag = self.elementNode.getElementNodesByTag(tagKey) + if elementsByTag == None: + print('Warning, could not get elementsByTag in getElementsByTag in document for:') + print(tagKey) + print(self.elementNode) + return elementsByTag + + def getParentNode(self): + 'Get parentNode element.' + return self.elementNode.parentNode + + def getPrevious(self): + 'Get previous element.' + return self.getPreviousElement() + + def getPreviousElement(self): + 'Get previous element.' + return self.elementNode.getPreviousElementNode() + + def getPreviousVertex(self): + 'Get previous element.' + return self.elementNode.getPreviousVertex() + + def getSelfElement(self): + 'Get self element.' + return self.elementNode + + +globalAccessibleAttributeDictionary = 'getCascadeBoolean getCascadeFloat getDocumentElement getElementByID getElementsByName'.split() +globalAccessibleAttributeDictionary += 'getElementsByTag getParentNode getPrevious getPreviousElement getPreviousVertex'.split() +globalAccessibleAttributeDictionary += 'getSelfElement'.split() +globalGetAccessibleAttributeSet = set(globalAccessibleAttributeDictionary) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/setting.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/setting.py new file mode 100644 index 0000000..5c16679 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_elements/setting.py @@ -0,0 +1,180 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from skeinforge_application.skeinforge_utilities import skeinforge_craft +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, elementNode): + 'Get the accessible attribute.' + if attributeName in globalGetAccessibleAttributeSet: + return getattr(Setting(elementNode), attributeName, None) + return None + +def getCascadeFloatWithoutSelf(defaultFloat, elementNode, key): + 'Get the cascade float.' + if key in elementNode.attributes: + value = elementNode.attributes[key] + functionName = 'get' + key[0].upper() + key[1 :] + if functionName in value: + if elementNode.parentNode == None: + return defaultFloat + else: + elementNode = elementNode.parentNode + return elementNode.getCascadeFloat(defaultFloat, key) + +def getEdgeWidth(elementNode): + 'Get the edge width.' + if elementNode == None: + return 0.72 + preferences = skeinforge_craft.getCraftPreferences('carve') + layerHeight = skeinforge_craft.getCraftValue('Layer Height', preferences) + layerHeight = getCascadeFloatWithoutSelf(layerHeight, elementNode, 'layerHeight') + edgeWidthOverHeight = skeinforge_craft.getCraftValue('Edge Width over Height', preferences) + edgeWidthOverHeight = getCascadeFloatWithoutSelf(edgeWidthOverHeight, elementNode, 'edgeWidthOverHeight') + return getCascadeFloatWithoutSelf(edgeWidthOverHeight * layerHeight, elementNode, 'edgeWidth') + +def getImportCoarseness(elementNode, preferences=None): + 'Get the importCoarseness.' + if elementNode == None: + return 1.0 + if preferences == None: + preferences = skeinforge_craft.getCraftPreferences('carve') + importCoarseness = skeinforge_craft.getCraftValue('Import Coarseness', preferences) + return getCascadeFloatWithoutSelf(importCoarseness, elementNode, 'importCoarseness') + +def getImportRadius(elementNode): + 'Get the importRadius.' + if elementNode == None: + return 0.36 + preferences = skeinforge_craft.getCraftPreferences('carve') + importCoarseness = getImportCoarseness(elementNode, preferences) + layerHeight = skeinforge_craft.getCraftValue('Layer Height', preferences) + layerHeight = getCascadeFloatWithoutSelf(layerHeight, elementNode, 'layerHeight') + edgeWidthOverHeight = skeinforge_craft.getCraftValue('Edge Width over Height', preferences) + edgeWidthOverHeight = getCascadeFloatWithoutSelf(edgeWidthOverHeight, elementNode, 'edgeWidthOverHeight') + return getCascadeFloatWithoutSelf(0.5 * importCoarseness * layerHeight * edgeWidthOverHeight, elementNode, 'importRadius') + +def getInteriorOverhangAngle(elementNode): + 'Get the interior overhang support angle in degrees.' + return getCascadeFloatWithoutSelf(30.0, elementNode, 'interiorOverhangAngle') + +def getInteriorOverhangRadians(elementNode): + 'Get the interior overhang support angle in radians.' + return math.radians(getInteriorOverhangAngle(elementNode)) + +def getLayerHeight(elementNode): + 'Get the layer height.' + if elementNode == None: + return 0.4 + preferences = skeinforge_craft.getCraftPreferences('carve') + return getCascadeFloatWithoutSelf(skeinforge_craft.getCraftValue('Layer Height', preferences), elementNode, 'layerHeight') + +def getOverhangAngle(elementNode): + 'Get the overhang support angle in degrees.' + return getCascadeFloatWithoutSelf(45.0, elementNode, 'overhangAngle') + +def getOverhangRadians(elementNode): + 'Get the overhang support angle in radians.' + return math.radians(getOverhangAngle(elementNode)) + +def getOverhangSpan(elementNode): + 'Get the overhang span.' + return getCascadeFloatWithoutSelf(2.0 * getLayerHeight(elementNode), elementNode, 'overhangSpan') + +def getPrecision(elementNode): + 'Get the cascade precision.' + return getCascadeFloatWithoutSelf(0.2 * getLayerHeight(elementNode), elementNode, 'precision') + +def getSheetThickness(elementNode): + 'Get the sheet thickness.' + return getCascadeFloatWithoutSelf(3.0, elementNode, 'sheetThickness') + +def getTwistPrecision(elementNode): + 'Get the twist precision in degrees.' + return getCascadeFloatWithoutSelf(5.0, elementNode, 'twistPrecision') + +def getTwistPrecisionRadians(elementNode): + 'Get the twist precision in radians.' + return math.radians(getTwistPrecision(elementNode)) + + +class Setting: + 'Class to get handle elementNodes in a setting.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + + def __repr__(self): + 'Get the string representation of this Setting.' + return self.elementNode + + def getEdgeWidth(self): + 'Get the edge width.' + return getEdgeWidth(self.elementNode) + + def getImportCoarseness(self): + 'Get the importCoarseness.' + return getImportCoarseness(self.elementNode) + + def getImportRadius(self): + 'Get the importRadius.' + return getImportRadius(self.elementNode) + + def getInteriorOverhangAngle(self): + 'Get the interior overhang support angle in degrees.' + return getInteriorOverhangAngle(self.elementNode) + + def getInteriorOverhangRadians(self): + 'Get the interior overhang support angle in radians.' + return getInteriorOverhangRadians(self.elementNode) + + def getLayerHeight(self): + 'Get the layer height.' + return getLayerHeight(self.elementNode) + + def getOverhangAngle(self): + 'Get the overhang support angle in degrees.' + return getOverhangAngle(self.elementNode) + + def getOverhangRadians(self): + 'Get the overhang support angle in radians.' + return getOverhangRadians(self.elementNode) + + def getOverhangSpan(self): + 'Get the overhang span.' + return getOverhangSpan(self.elementNode) + + def getPrecision(self): + 'Get the cascade precision.' + return getPrecision(self.elementNode) + + def getSheetThickness(self): + 'Get the sheet thickness.' + return getSheetThickness(self.elementNode) + + def getTwistPrecision(self): + 'Get the twist precision in degrees.' + return getTwistPrecision(self.elementNode) + + def getTwistPrecisionRadians(self): + 'Get the twist precision in radians.' + return getTwistPrecisionRadians(self.elementNode) + + +globalAccessibleAttributeDictionary = 'getEdgeWidth getImportCoarseness getImportRadius getInteriorOverhangAngle getInteriorOverhangRadians'.split() +globalAccessibleAttributeDictionary += 'getLayerHeight getOverhangSpan getOverhangAngle getOverhangRadians'.split() +globalAccessibleAttributeDictionary += 'getPrecision getSheetThickness getTwistPrecision getTwistPrecisionRadians'.split() +globalGetAccessibleAttributeSet = set(globalAccessibleAttributeDictionary) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/__init__.py new file mode 100644 index 0000000..58ec332 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 4 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/dictionary_attribute.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/dictionary_attribute.py new file mode 100644 index 0000000..3c8abef --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/dictionary_attribute.py @@ -0,0 +1,102 @@ +""" +Dictionary object attributes. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, dictionaryObject): + 'Get the accessible attribute.' + if attributeName in globalNativeFunctionSet: + return getattr(dictionaryObject, attributeName, None) + if attributeName in globalGetAccessibleAttributeSet: + stringAttribute = DictionaryAttribute(dictionaryObject) + return getattr(stringAttribute, attributeName, None) + return None + + +class DictionaryAttribute: + 'Class to handle a dictionary.' + def __init__(self, dictionaryObject): + 'Initialize.' + self.dictionaryObject = dictionaryObject + + def __repr__(self): + "Get the dictionary representation of this DictionaryAttribute." + return str(self.dictionaryObject) + + def count(self, value): + 'Get the count.' + countTotal = 0 + for key, iteratorValue in self.dictionaryObject.iteritems(): + if iteratorValue == value: + countTotal += 1 + return countTotal + + def delete(self, arguments): + 'Get the delete dictionary.' + if arguments.__class__ != list: + del self.dictionaryObject[arguments] + return self.dictionaryObject + if len(arguments) == 0: + self.dictionaryObject.clear() + return self.dictionaryObject + if len(arguments) == 1: + del self.dictionaryObject[arguments[0]] + return self.dictionaryObject + for enumeratorKey in euclidean.getEnumeratorKeysAlwaysList(self.dictionaryObject, arguments): + del self.dictionaryObject[enumeratorKey] + return self.dictionaryObject + + def getIsIn(self, value): + 'Determine if the value is in.' + return value in self.dictionaryObject + + def getIsNotIn(self, value): + 'Determine if the value is in.' + return not(value in self.dictionaryObject) + + def getLength(self): + 'Get the length.' + return len(self.dictionaryObject) + + def getMax(self): + 'Get the max.' + return max(self.dictionaryObject) + + def getMin(self): + 'Get the min.' + return min(self.dictionaryObject) + + def index(self, value): + 'Get the index element.' + for key, iteratorValue in self.dictionaryObject.iteritems(): + if iteratorValue == value: + return key + raise ValueError('Value (%s) not found in index in DictionaryAttribute for (%s).' % (value, self.dictionaryObject)) + + def length(self): + 'Get the length.' + return len(self.dictionaryObject) + + def set(self, itemIndex, value): + 'Set value.' + self.dictionaryObject[itemIndex] = value + return self.dictionaryObject + + +globalAccessibleAttributeDictionary = 'count delete getIsIn getIsNotIn getLength getMax getMin index length set'.split() +globalGetAccessibleAttributeSet = set(globalAccessibleAttributeDictionary) +globalNativeFunctions = 'clear copy fromkeys get items keys pop popitem remove setdefault update values'.split() +globalNativeFunctionSet = set(globalNativeFunctions) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/list_attribute.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/list_attribute.py new file mode 100644 index 0000000..81711e9 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/list_attribute.py @@ -0,0 +1,123 @@ +""" +List object attributes. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, listObject): + 'Get the accessible attribute.' + if attributeName in globalNativeFunctionSet: + return getattr(listObject, attributeName, None) + if attributeName in globalGetAccessibleAttributeSet: + stringAttribute = ListAttribute(listObject) + return getattr(stringAttribute, attributeName, None) + return None + + +class ListAttribute: + 'Class to handle a list.' + def __init__(self, listObject): + 'Initialize.' + self.listObject = listObject + + def __repr__(self): + "Get the list representation of this ListAttribute." + return str(self.listObject) + + def add(self, value): + 'Get the concatenation, same as append.' + return self.listObject + [value] + + def copy(self): + 'Get the copy.' + return self.listObject[:] + + def delete(self, arguments): + 'Get the delete list.' + deleteList = [] + enumeratorSet = set(euclidean.getEnumeratorKeysAlwaysList(self.listObject, arguments)) + for elementIndex, element in enumerate(self.listObject): + if elementIndex not in enumeratorSet: + deleteList.append(element) + return deleteList + + def get(self, itemIndex): + 'Get value by index' + return self.listObject[itemIndex] + + def getExpansion(self, items): + 'Get the concatenated copies.' + expansion = [] + for itemIndex in xrange(items): + expansion += self.listObject[:] + return expansion + + def getIsIn(self, value): + 'Determine if the value is in.' + return value in self.listObject + + def getIsNotIn(self, value): + 'Determine if the value is in.' + return not(value in self.listObject) + + def getLength(self): + 'Get the length.' + return len(self.listObject) + + def getMax(self): + 'Get the max.' + return max(self.listObject) + + def getMin(self): + 'Get the min.' + return min(self.listObject) + + def insert(self, insertIndex, value): + 'Get the insert list.' + if insertIndex < 0: + insertIndex += len(self.listObject) + insertIndex = max(0, insertIndex) + return self.listObject[: insertIndex] + [value] + self.listObject[insertIndex :] + + def keys(self): + 'Get the keys.' + return range(len(self.listObject)) + + def length(self): + 'Get the length.' + return len(self.listObject) + + def rindex(self, value): + 'Get the rindex element.' + for elementIndex, element in enumerate(self.listObject): + if element == value: + return elementIndex + raise ValueError('Value (%s) not found in rindex in ListAttribute for (%s).' % (value, self.listObject)) + + def set(self, itemIndex, value): + 'Set value.' + self.listObject[itemIndex] = value + return self.listObject + + def values(self, arguments=None): + 'Get the values.' + return self.listObject + + +globalAccessibleAttributeDictionary = 'add copy count delete get getExpansion getIsIn getIsNotIn getLength getMax getMin'.split() +globalAccessibleAttributeDictionary += 'insert keys length rindex set values'.split() +globalGetAccessibleAttributeSet = set(globalAccessibleAttributeDictionary) +globalNativeFunctions = 'append extend index pop remove reverse sort'.split() +globalNativeFunctionSet = set(globalNativeFunctions) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/string_attribute.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/string_attribute.py new file mode 100644 index 0000000..f3ab451 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_enumerables/string_attribute.py @@ -0,0 +1,136 @@ +""" +String object attributes. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName, stringObject): + 'Get the accessible attribute.' + if attributeName in globalNativeFunctionSet: + return getattr(stringObject, attributeName, None) + if attributeName in globalGetAccessibleAttributeSet: + stringAttribute = StringAttribute(stringObject) + return getattr(stringAttribute, attributeName, None) + return None + + +class StringAttribute: + 'Class to handle a string.' + def __init__(self, stringObject): + 'Initialize.' + self.stringObject = stringObject + + def __repr__(self): + "Get the string representation of this StringAttribute." + return self.stringObject + + def add(self, nextString): + 'Get the add string, same as append.' + return self.stringObject + nextString + + def append(self, nextString): + 'Get the append string.' + return self.stringObject + nextString + + def copy(self): + 'Get the copy.' + return self.stringObject[:] + + def delete(self, arguments): + 'Get the delete string.' + deleteString = '' + enumeratorSet = set(euclidean.getEnumeratorKeysAlwaysList(self.stringObject, arguments)) + for characterIndex, character in enumerate(self.stringObject): + if characterIndex not in enumeratorSet: + deleteString += character + return deleteString + + def get(self, itemIndex): + 'Get value by characterIndex' + return self.stringObject[itemIndex] + + def getExpansion(self, items): + 'Get the concatenated copies.' + expansion = '' + for itemIndex in xrange(items): + expansion += self.stringObject + return expansion + + def getIsIn(self, value): + 'Determine if the value is in.' + return value in self.stringObject + + def getIsNotIn(self, value): + 'Determine if the value is in.' + return not(value in self.stringObject) + + def getLength(self): + 'Get the length.' + return len(self.stringObject) + + def getMax(self): + 'Get the max.' + return max(self.stringObject) + + def getMin(self): + 'Get the min.' + return min(self.stringObject) + + def insert(self, insertIndex, value): + 'Get the insert string.' + if insertIndex < 0: + insertIndex += len(self.stringObject) + insertIndex = max(0, insertIndex) + return self.stringObject[: insertIndex] + value + self.stringObject[insertIndex :] + + def keys(self): + 'Get the keys.' + return range(len(self.stringObject)) + + def length(self): + 'Get the length.' + return len(self.stringObject) + + def remove(self, value): + 'Get the remove string.' + removeIndex = self.stringObject.find(value) + if removeIndex > -1: + return self.stringObject[: removeIndex] + self.stringObject[removeIndex + len(value) :] + return self.stringObject + + def reverse(self): + 'Get the reverse string.' + return self.stringObject[: : -1] + + def set(self, itemIndex, value): + 'Set value.' + self.stringObject[itemIndex] = value + return self.stringObject + + def values(self): + 'Get the values.' + values = [] + for character in self.stringObject: + values.append(character) + return values + + +globalAccessibleAttributeDictionary = 'add append copy delete get getExpansion getIsIn getIsNotIn getLength getMax getMin'.split() +globalAccessibleAttributeDictionary += 'insert keys length remove reverse set values'.split() +globalGetAccessibleAttributeSet = set(globalAccessibleAttributeDictionary) +globalNativeFunctions = 'capitalize center count decode encode endswith expandtabs find format index isalnum join'.split() +globalNativeFunctions += 'isalpha isdigit islower isspace istitle isupper ljust lower lstrip partition replace rfind rindex'.split() +globalNativeFunctions += 'rjust rpartition rsplit rstrip split splitlines startswith strip swapcase title translate upper zfill'.split() +globalNativeFunctionSet = set(globalNativeFunctions) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/__init__.py new file mode 100644 index 0000000..58ec332 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 4 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/_math.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/_math.py new file mode 100644 index 0000000..9a244f7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/_math.py @@ -0,0 +1,101 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalNativeFunctions = 'acos asin atan atan2 ceil cos cosh degrees e exp fabs floor fmod frexp hypot'.split() +globalNativeFunctions += 'ldexp log log10 modf pi pow radians sin sinh sqrt tan tanh trunc'.split() +globalNativeFunctionSet = set(globalNativeFunctions) +#Constants from: http://www.physlink.com/reference/MathConstants.cfm +#Tau is from: http://tauday.com/ +#If anyone wants to add stuff, more constants are at: http://en.wikipedia.org/wiki/Mathematical_constant +globalMathConstantDictionary = { + 'euler' : 0.5772156649015328606065120, + 'golden' : euclidean.globalGoldenRatio, + 'goldenAngle' : euclidean.globalGoldenAngle, + 'goldenRatio' : euclidean.globalGoldenRatio, + 'tau' : euclidean.globalTau} + + +def _getAccessibleAttribute(attributeName): + 'Get the accessible attribute.' + if attributeName in globalMathConstantDictionary: + return globalMathConstantDictionary[attributeName] + if attributeName in globalNativeFunctionSet: + return math.__dict__[attributeName] + if attributeName in globalAccessibleAttributeDictionary: + return globalAccessibleAttributeDictionary[attributeName] + return None + + +def getAbs(value): + 'Get the abs.' + return abs(value) + +def getBoolean(value): + 'Get the boolean.' + return bool(value) + +def getDivmod(x, y): + 'Get the divmod.' + return divmod(x, y) + +def getFloat(value): + 'Get the float.' + return float(value) + +def getHex(value): + 'Get the hex.' + return hex(value) + +def getInt(value): + 'Get the int.' + return int(value) + +def getLong(value): + 'Get the long.' + return long(value) + +def getMax(first, second): + 'Get the max.' + return max(first, second) + +def getMin(first, second): + 'Get the min.' + return min(first, second) + +def getRound(value): + 'Get the round.' + return round(value) + +def getString(value): + 'Get the string.' + return str(value) + + +globalAccessibleAttributeDictionary = { + 'abs' : getAbs, + 'boolean' : getBoolean, + 'divmod' : getDivmod, + 'float' : getFloat, + 'hex' : getHex, + 'int' : getInt, + 'long' : getLong, + 'max' : getMax, + 'min' : getMin, + 'round' : getRound, + 'string' : getString} diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/euclid.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/euclid.py new file mode 100644 index 0000000..bf4d410 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/euclid.py @@ -0,0 +1,95 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName): + 'Get the accessible attribute.' + if attributeName in globalAccessibleAttributeDictionary: + return globalAccessibleAttributeDictionary[attributeName] + return None + +def getComplex(x=0.0, y=0.0): + 'Get the complex.' + return complex(x, y) + +def getCylindrical(azimuthDegrees, radius=1.0, z=0.0): + 'Get the cylindrical vector3 by degrees.' + return getCylindricalByRadians(math.radians(azimuthDegrees), radius, z) + +def getCylindricalByRadians(azimuthRadians, radius=1.0, z=0.0): + 'Get the cylindrical vector3 by radians.' + polar = radius * euclidean.getWiddershinsUnitPolar(azimuthRadians) + return Vector3(polar.real, polar.imag, z) + +def getNestedVectorTestExample(x=0.0, y=0.0, z=0.0): + 'Get the NestedVectorTestExample.' + return NestedVectorTestExample(Vector3(x, y, z)) + +def getPolar(angleDegrees, radius=1.0): + 'Get the complex polar by degrees.' + return radius * euclidean.getWiddershinsUnitPolar(math.radians(angleDegrees)) + +def getPolarByRadians(angleRadians, radius=1.0): + 'Get the complex polar by radians.' + return radius * euclidean.getWiddershinsUnitPolar(angleRadians) + +def getSpherical(azimuthDegrees, elevationDegrees, radius=1.0): + 'Get the spherical vector3 unit by degrees.' + return getSphericalByRadians(math.radians(azimuthDegrees), math.radians(elevationDegrees), radius) + +def getSphericalByRadians(azimuthRadians, elevationRadians, radius=1.0): + 'Get the spherical vector3 unit by radians.' + elevationComplex = euclidean.getWiddershinsUnitPolar(elevationRadians) + azimuthComplex = euclidean.getWiddershinsUnitPolar(azimuthRadians) * elevationComplex.real + return Vector3(azimuthComplex.real, azimuthComplex.imag, elevationComplex.imag) * radius + +def getVector3(x=0.0, y=0.0, z=0.0): + 'Get the vector3.' + return Vector3(x, y, z) + +def getVector3Index(index=0, x=0.0, y=0.0, z=0.0): + 'Get the vector3.' + return Vector3Index(index, x, y, z) + + +class NestedVectorTestExample: + 'Class to test local attribute.' + def __init__(self, vector3): + 'Get the accessible attribute.' + self.vector3 = vector3 + + def _getAccessibleAttribute(self, attributeName): + "Get the accessible attribute." + if attributeName == 'vector3': + return getattr(self, attributeName, None) + return None + + +globalAccessibleAttributeDictionary = { + 'complex' : getComplex, + 'getCylindrical' : getCylindrical, + 'getCylindricalByRadians' : getCylindricalByRadians, + 'getPolar' : getPolar, + 'getPolarByRadians' : getPolarByRadians, + 'getSpherical' : getSpherical, + 'getSphericalByRadians' : getSphericalByRadians, + 'NestedVectorTestExample' : getNestedVectorTestExample, + 'Vector3' : getVector3, + 'Vector3Index' : getVector3Index} diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/measure.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/measure.py new file mode 100644 index 0000000..f3ec99e --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/measure.py @@ -0,0 +1,63 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName): + 'Get the accessible attribute.' + if attributeName in globalAccessibleAttributeDictionary: + return globalAccessibleAttributeDictionary[attributeName] + return None + +def getBoundingBoxByPaths(elementNode): + 'Get bounding box of the transformed paths of the xmlObject of the elementNode.' + transformedPaths = elementNode.xmlObject.getTransformedPaths() + maximum = euclidean.getMaximumByVector3Paths(transformedPaths) + minimum = euclidean.getMinimumByVector3Paths(transformedPaths) + return [minimum, maximum] + +def getCenterByPaths(elementNode): + 'Get center of the transformed paths of the xmlObject of the elementNode.' + transformedPaths = elementNode.xmlObject.getTransformedPaths() + return 0.5 * (euclidean.getMaximumByVector3Paths(transformedPaths) + euclidean.getMinimumByVector3Paths(transformedPaths)) + +def getExtentByPaths(elementNode): + 'Get extent of the transformed paths of the xmlObject of the elementNode.' + transformedPaths = elementNode.xmlObject.getTransformedPaths() + return euclidean.getMaximumByVector3Paths(transformedPaths) - euclidean.getMinimumByVector3Paths(transformedPaths) + +def getInradiusByPaths(elementNode): + 'Get inradius of the transformed paths of the xmlObject of the elementNode.' + return 0.5 * getExtentByPaths(elementNode) + +def getMinimumByPaths(elementNode): + 'Get minimum of the transformed paths of the xmlObject of the elementNode.' + return euclidean.getMinimumByVector3Paths(elementNode.xmlObject.getTransformedPaths()) + +def getMaximumByPaths(elementNode): + 'Get maximum of the transformed paths of the xmlObject of the elementNode.' + return euclidean.getMaximumByVector3Paths(elementNode.xmlObject.getTransformedPaths()) + + +globalAccessibleAttributeDictionary = { + 'getBoundingBoxByPaths' : getBoundingBoxByPaths, + 'getCenterByPaths' : getCenterByPaths, + 'getExtentByPaths' : getExtentByPaths, + 'getInradiusByPaths' : getInradiusByPaths, + 'getMaximumByPaths' : getMaximumByPaths, + 'getMinimumByPaths' : getMinimumByPaths} diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/print.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/print.py new file mode 100644 index 0000000..199d08f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/evaluate_fundamentals/print.py @@ -0,0 +1,36 @@ +""" +Boolean geometry utilities. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def _getAccessibleAttribute(attributeName): + 'Get the accessible attribute.' + if attributeName in globalAccessibleAttributeDictionary: + return globalAccessibleAttributeDictionary[attributeName] + return None + +def continuous(valueString): + 'Print continuous.' + sys.stdout.write(str(valueString)) + return valueString + +def line(valueString): + 'Print line.' + print(valueString) + return valueString + + +globalAccessibleAttributeDictionary = {'continuous' : continuous, 'line' : line} diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/example.csv b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/example.csv new file mode 100644 index 0000000..eeecad1 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/example.csv @@ -0,0 +1,22 @@ +"A" +0 0 +2 8 +4 0 + +1 4 +3 4 + +"B" +0 4 +2 4 +4 5 +4 7 +3 8 +0 8 +0 0 +3 0 +4 1 +4 3 +2 4 + +"C" diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/matrix.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/matrix.py new file mode 100644 index 0000000..b409705 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/geometry_utilities/matrix.py @@ -0,0 +1,495 @@ +""" +Boolean geometry four by four matrix. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import xml_simple_writer +import cStringIO +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 300 + + +def addVertexes(geometryOutput, vertexes): + 'Add the vertexes.' + if geometryOutput.__class__ == list: + for element in geometryOutput: + addVertexes(element, vertexes) + return + if geometryOutput.__class__ == dict: + for geometryOutputKey in geometryOutput.keys(): + if geometryOutputKey == 'vertex': + vertexes += geometryOutput[geometryOutputKey] + else: + addVertexes(geometryOutput[geometryOutputKey], vertexes) + +def getBranchMatrix(elementNode): + 'Get matrix starting from the object if it exists, otherwise get a matrix starting from stratch.' + branchMatrix = Matrix() + matrixChildElement = elementNode.getFirstChildByLocalName('matrix') + if matrixChildElement != None: + branchMatrix = branchMatrix.getFromElementNode(matrixChildElement, '') + branchMatrix = branchMatrix.getFromElementNode(elementNode, 'matrix.') + if elementNode.xmlObject == None: + return branchMatrix + elementNodeMatrix = elementNode.xmlObject.getMatrix4X4() + if elementNodeMatrix == None: + return branchMatrix + return elementNodeMatrix.getOtherTimesSelf(branchMatrix.tetragrid) + +def getBranchMatrixSetElementNode(elementNode): + 'Get matrix starting from the object if it exists, otherwise get a matrix starting from stratch.' + branchMatrix = getBranchMatrix(elementNode) + setElementNodeDictionaryMatrix(elementNode, branchMatrix) + return branchMatrix + +def getCumulativeVector3Remove(defaultVector3, elementNode, prefix): + 'Get cumulative vector3 and delete the prefixed attributes.' + if prefix == '': + defaultVector3.x = evaluate.getEvaluatedFloat(defaultVector3.x, elementNode, 'x') + defaultVector3.y = evaluate.getEvaluatedFloat(defaultVector3.y, elementNode, 'y') + defaultVector3.z = evaluate.getEvaluatedFloat(defaultVector3.z, elementNode, 'z') + euclidean.removeElementsFromDictionary(elementNode.attributes, ['x', 'y', 'z']) + prefix = 'cartesian' + defaultVector3 = evaluate.getVector3ByPrefix(defaultVector3, elementNode, prefix) + euclidean.removePrefixFromDictionary(elementNode.attributes, prefix) + return defaultVector3 + +def getDiagonalSwitchedTetragrid(angleDegrees, diagonals): + 'Get the diagonals and switched matrix by degrees.' + return getDiagonalSwitchedTetragridByRadians(math.radians(angleDegrees), diagonals) + +def getDiagonalSwitchedTetragridByPolar(diagonals, unitPolar): + 'Get the diagonals and switched matrix by unitPolar.' + diagonalSwitchedTetragrid = getIdentityTetragrid() + for diagonal in diagonals: + diagonalSwitchedTetragrid[diagonal][diagonal] = unitPolar.real + diagonalSwitchedTetragrid[diagonals[0]][diagonals[1]] = -unitPolar.imag + diagonalSwitchedTetragrid[diagonals[1]][diagonals[0]] = unitPolar.imag + return diagonalSwitchedTetragrid + +def getDiagonalSwitchedTetragridByRadians(angleRadians, diagonals): + 'Get the diagonals and switched matrix by radians.' + return getDiagonalSwitchedTetragridByPolar(diagonals, euclidean.getWiddershinsUnitPolar(angleRadians)) + +def getIdentityTetragrid(tetragrid=None): + 'Get four by four matrix with diagonal elements set to one.' + if tetragrid == None: + return [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]] + return tetragrid + +def getIsIdentityTetragrid(tetragrid): + 'Determine if the tetragrid is the identity tetragrid.' + for column in xrange(4): + for row in xrange(4): + if column == row: + if tetragrid[column][row] != 1.0: + return False + elif tetragrid[column][row] != 0.0: + return False + return True + +def getIsIdentityTetragridOrNone(tetragrid): + 'Determine if the tetragrid is None or if it is the identity tetragrid.' + if tetragrid == None: + return True + return getIsIdentityTetragrid(tetragrid) + +def getKeyA(row, column, prefix=''): + 'Get the a format key string from row & column, counting from zero.' + return '%sa%s%s' % (prefix, row, column) + +def getKeyM(row, column, prefix=''): + 'Get the m format key string from row & column, counting from one.' + return '%sm%s%s' % (prefix, row + 1, column + 1) + +def getKeysA(prefix=''): + 'Get the matrix keys, counting from zero.' + keysA = [] + for row in xrange(4): + for column in xrange(4): + key = getKeyA(row, column, prefix) + keysA.append(key) + return keysA + +def getKeysM(prefix=''): + 'Get the matrix keys, counting from one.' + keysM = [] + for row in xrange(4): + for column in xrange(4): + key = getKeyM(row, column, prefix) + keysM.append(key) + return keysM + +def getRemovedFloat(defaultFloat, elementNode, key, prefix): + 'Get the float by the key and the prefix.' + prefixKey = prefix + key + if prefixKey in elementNode.attributes: + floatValue = evaluate.getEvaluatedFloat(None, elementNode, prefixKey) + if floatValue == None: + print('Warning, evaluated value in getRemovedFloatByKeys in matrix is None for key:') + print(prefixKey) + print('for elementNode dictionary value:') + print(elementNode.attributes[prefixKey]) + print('for elementNode dictionary:') + print(elementNode.attributes) + else: + defaultFloat = floatValue + del elementNode.attributes[prefixKey] + return defaultFloat + +def getRemovedFloatByKeys(defaultFloat, elementNode, keys, prefix): + 'Get the float by the keys and the prefix.' + for key in keys: + defaultFloat = getRemovedFloat(defaultFloat, elementNode, key, prefix) + return defaultFloat + +def getRotateAroundAxisTetragrid(elementNode, prefix): + 'Get rotate around axis tetragrid and delete the axis and angle attributes.' + angle = getRemovedFloatByKeys(0.0, elementNode, ['angle', 'counterclockwise'], prefix) + angle -= getRemovedFloat(0.0, elementNode, 'clockwise', prefix) + if angle == 0.0: + return None + angleRadians = math.radians(angle) + axis = getCumulativeVector3Remove(Vector3(), elementNode, prefix + 'axis') + axisLength = abs(axis) + if axisLength <= 0.0: + print('Warning, axisLength was zero in getRotateAroundAxisTetragrid in matrix so nothing will be done for:') + print(elementNode) + return None + axis /= axisLength + tetragrid = getIdentityTetragrid() + cosAngle = math.cos(angleRadians) + sinAngle = math.sin(angleRadians) + oneMinusCos = 1.0 - math.cos(angleRadians) + xx = axis.x * axis.x + xy = axis.x * axis.y + xz = axis.x * axis.z + yy = axis.y * axis.y + yz = axis.y * axis.z + zz = axis.z * axis.z + tetragrid[0] = [cosAngle + xx * oneMinusCos, xy * oneMinusCos - axis.z * sinAngle, xz * oneMinusCos + axis.y * sinAngle, 0.0] + tetragrid[1] = [xy * oneMinusCos + axis.z * sinAngle, cosAngle + yy * oneMinusCos, yz * oneMinusCos - axis.x * sinAngle, 0.0] + tetragrid[2] = [xz * oneMinusCos - axis.y * sinAngle, yz * oneMinusCos + axis.x * sinAngle, cosAngle + zz * oneMinusCos, 0.0] + return tetragrid + +def getRotateTetragrid(elementNode, prefix): + 'Get rotate tetragrid and delete the rotate attributes.' + # http://en.wikipedia.org/wiki/Rotation_matrix + rotateMatrix = Matrix() + rotateMatrix.tetragrid = getRotateAroundAxisTetragrid(elementNode, prefix) + zAngle = getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisez', 'observerclockwisez', 'z'], prefix) + zAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisez', 'observercounterclockwisez'], prefix) + if zAngle != 0.0: + rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(-zAngle, [0, 1]), rotateMatrix.tetragrid) + xAngle = getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisex', 'observerclockwisex', 'x'], prefix) + xAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisex', 'observercounterclockwisex'], prefix) + if xAngle != 0.0: + rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(-xAngle, [1, 2]), rotateMatrix.tetragrid) + yAngle = getRemovedFloatByKeys(0.0, elementNode, ['axiscounterclockwisey', 'observerclockwisey', 'y'], prefix) + yAngle -= getRemovedFloatByKeys(0.0, elementNode, ['axisclockwisey', 'observercounterclockwisey'], prefix) + if yAngle != 0.0: + rotateMatrix.tetragrid = getTetragridTimesOther(getDiagonalSwitchedTetragrid(yAngle, [0, 2]), rotateMatrix.tetragrid) + return rotateMatrix.tetragrid + +def getScaleTetragrid(elementNode, prefix): + 'Get scale matrix and delete the scale attributes.' + scaleDefaultVector3 = Vector3(1.0, 1.0, 1.0) + scale = getCumulativeVector3Remove(scaleDefaultVector3.copy(), elementNode, prefix) + if scale == scaleDefaultVector3: + return None + return [[scale.x, 0.0, 0.0, 0.0], [0.0, scale.y, 0.0, 0.0], [0.0, 0.0, scale.z, 0.0], [0.0, 0.0, 0.0, 1.0]] + +def getTetragridA(elementNode, prefix, tetragrid): + 'Get the tetragrid from the elementNode letter a values.' + keysA = getKeysA(prefix) + evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, keysA) + if len(evaluatedDictionary.keys()) < 1: + return tetragrid + for row in xrange(4): + for column in xrange(4): + key = getKeyA(row, column, prefix) + if key in evaluatedDictionary: + value = evaluatedDictionary[key] + if value == None or value == 'None': + print('Warning, value in getTetragridA in matrix is None for key for dictionary:') + print(key) + print(evaluatedDictionary) + else: + tetragrid = getIdentityTetragrid(tetragrid) + tetragrid[row][column] = float(value) + euclidean.removeElementsFromDictionary(elementNode.attributes, keysA) + return tetragrid + +def getTetragridC(elementNode, prefix, tetragrid): + 'Get the matrix Tetragrid from the elementNode letter c values.' + columnKeys = 'Pc1 Pc2 Pc3 Pc4'.replace('P', prefix).split() + evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, columnKeys) + if len(evaluatedDictionary.keys()) < 1: + return tetragrid + for columnKeyIndex, columnKey in enumerate(columnKeys): + if columnKey in evaluatedDictionary: + value = evaluatedDictionary[columnKey] + if value == None or value == 'None': + print('Warning, value in getTetragridC in matrix is None for columnKey for dictionary:') + print(columnKey) + print(evaluatedDictionary) + else: + tetragrid = getIdentityTetragrid(tetragrid) + for elementIndex, element in enumerate(value): + tetragrid[elementIndex][columnKeyIndex] = element + euclidean.removeElementsFromDictionary(elementNode.attributes, columnKeys) + return tetragrid + +def getTetragridCopy(tetragrid): + 'Get tetragrid copy.' + if tetragrid == None: + return None + tetragridCopy = [] + for tetragridRow in tetragrid: + tetragridCopy.append(tetragridRow[:]) + return tetragridCopy + +def getTetragridM(elementNode, prefix, tetragrid): + 'Get the tetragrid from the elementNode letter m values.' + keysM = getKeysM(prefix) + evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, keysM) + if len(evaluatedDictionary.keys()) < 1: + return tetragrid + for row in xrange(4): + for column in xrange(4): + key = getKeyM(row, column, prefix) + if key in evaluatedDictionary: + value = evaluatedDictionary[key] + if value == None or value == 'None': + print('Warning, value in getTetragridM in matrix is None for key for dictionary:') + print(key) + print(evaluatedDictionary) + else: + tetragrid = getIdentityTetragrid(tetragrid) + tetragrid[row][column] = float(value) + euclidean.removeElementsFromDictionary(elementNode.attributes, keysM) + return tetragrid + +def getTetragridMatrix(elementNode, prefix, tetragrid): + 'Get the tetragrid from the elementNode matrix value.' + matrixKey = prefix + 'matrix' + evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, [matrixKey]) + if len(evaluatedDictionary.keys()) < 1: + return tetragrid + value = evaluatedDictionary[matrixKey] + if value == None or value == 'None': + print('Warning, value in getTetragridMatrix in matrix is None for matrixKey for dictionary:') + print(matrixKey) + print(evaluatedDictionary) + else: + tetragrid = getIdentityTetragrid(tetragrid) + for rowIndex, row in enumerate(value): + for elementIndex, element in enumerate(row): + tetragrid[rowIndex][elementIndex] = element + euclidean.removeElementsFromDictionary(elementNode.attributes, [matrixKey]) + return tetragrid + +def getTetragridR(elementNode, prefix, tetragrid): + 'Get the tetragrid from the elementNode letter r values.' + rowKeys = 'Pr1 Pr2 Pr3 Pr4'.replace('P', prefix).split() + evaluatedDictionary = evaluate.getEvaluatedDictionaryByEvaluationKeys(elementNode, rowKeys) + if len(evaluatedDictionary.keys()) < 1: + return tetragrid + for rowKeyIndex, rowKey in enumerate(rowKeys): + if rowKey in evaluatedDictionary: + value = evaluatedDictionary[rowKey] + if value == None or value == 'None': + print('Warning, value in getTetragridR in matrix is None for rowKey for dictionary:') + print(rowKey) + print(evaluatedDictionary) + else: + tetragrid = getIdentityTetragrid(tetragrid) + for elementIndex, element in enumerate(value): + tetragrid[rowKeyIndex][elementIndex] = element + euclidean.removeElementsFromDictionary(elementNode.attributes, rowKeys) + return tetragrid + +def getTetragridTimesOther(firstTetragrid, otherTetragrid ): + 'Get this matrix multiplied by the other matrix.' + #A down, B right from http://en.wikipedia.org/wiki/Matrix_multiplication + if firstTetragrid == None: + return otherTetragrid + if otherTetragrid == None: + return firstTetragrid + tetragridTimesOther = [] + for row in xrange(4): + matrixRow = firstTetragrid[row] + tetragridTimesOtherRow = [] + tetragridTimesOther.append(tetragridTimesOtherRow) + for column in xrange(4): + dotProduct = 0 + for elementIndex in xrange(4): + dotProduct += matrixRow[elementIndex] * otherTetragrid[elementIndex][column] + tetragridTimesOtherRow.append(dotProduct) + return tetragridTimesOther + +def getTransformedByList(floatList, point): + 'Get the point transformed by the array.' + return floatList[0] * point.x + floatList[1] * point.y + floatList[2] * point.z + floatList[3] + +def getTransformedVector3(tetragrid, vector3): + 'Get the vector3 multiplied by a matrix.' + if getIsIdentityTetragridOrNone(tetragrid): + return vector3.copy() + return getTransformedVector3Blindly(tetragrid, vector3) + +def getTransformedVector3Blindly(tetragrid, vector3): + 'Get the vector3 multiplied by a tetragrid without checking if the tetragrid exists.' + return Vector3( + getTransformedByList(tetragrid[0], vector3), + getTransformedByList(tetragrid[1], vector3), + getTransformedByList(tetragrid[2], vector3)) + +def getTransformedVector3s(tetragrid, vector3s): + 'Get the vector3s multiplied by a matrix.' + if getIsIdentityTetragridOrNone(tetragrid): + return euclidean.getPathCopy(vector3s) + transformedVector3s = [] + for vector3 in vector3s: + transformedVector3s.append(getTransformedVector3Blindly(tetragrid, vector3)) + return transformedVector3s + +def getTransformTetragrid(elementNode, prefix): + 'Get the tetragrid from the elementNode.' + tetragrid = getTetragridA(elementNode, prefix, None) + tetragrid = getTetragridC(elementNode, prefix, tetragrid) + tetragrid = getTetragridM(elementNode, prefix, tetragrid) + tetragrid = getTetragridMatrix(elementNode, prefix, tetragrid) + tetragrid = getTetragridR(elementNode, prefix, tetragrid) + return tetragrid + +def getTranslateTetragrid(elementNode, prefix): + 'Get translate matrix and delete the translate attributes.' + translation = getCumulativeVector3Remove(Vector3(), elementNode, prefix) + if translation.getIsDefault(): + return None + return getTranslateTetragridByTranslation(translation) + +def getTranslateTetragridByTranslation(translation): + 'Get translate tetragrid by translation.' + return [[1.0, 0.0, 0.0, translation.x], [0.0, 1.0, 0.0, translation.y], [0.0, 0.0, 1.0, translation.z], [0.0, 0.0, 0.0, 1.0]] + +def getVertexes(geometryOutput): + 'Get the vertexes.' + vertexes = [] + addVertexes(geometryOutput, vertexes) + return vertexes + +def setAttributesToMultipliedTetragrid(elementNode, tetragrid): + 'Set the element attribute dictionary and element matrix to the matrix times the tetragrid.' + setElementNodeDictionaryMatrix(elementNode, getBranchMatrix(elementNode).getOtherTimesSelf(tetragrid)) + +def setElementNodeDictionaryMatrix(elementNode, matrix4X4): + 'Set the element attribute dictionary or element matrix to the matrix.' + if elementNode.xmlObject == None: + elementNode.attributes.update(matrix4X4.getAttributes('matrix.')) + else: + elementNode.xmlObject.matrix4X4 = matrix4X4 + +def transformVector3Blindly(tetragrid, vector3): + 'Transform the vector3 by a tetragrid without checking to see if it exists.' + x = getTransformedByList(tetragrid[0], vector3) + y = getTransformedByList(tetragrid[1], vector3) + z = getTransformedByList(tetragrid[2], vector3) + vector3.x = x + vector3.y = y + vector3.z = z + +def transformVector3ByMatrix(tetragrid, vector3): + 'Transform the vector3 by a matrix.' + if getIsIdentityTetragridOrNone(tetragrid): + return + transformVector3Blindly(tetragrid, vector3) + +def transformVector3sByMatrix(tetragrid, vector3s): + 'Transform the vector3s by a matrix.' + if getIsIdentityTetragridOrNone(tetragrid): + return + for vector3 in vector3s: + transformVector3Blindly(tetragrid, vector3) + + +class Matrix: + 'A four by four matrix.' + def __init__(self, tetragrid=None): + 'Add empty lists.' + self.tetragrid = getTetragridCopy(tetragrid) + + def __eq__(self, other): + 'Determine whether this matrix is identical to other one.' + if other == None: + return False + if other.__class__ != self.__class__: + return False + return other.tetragrid == self.tetragrid + + def __ne__(self, other): + 'Determine whether this vector is not identical to other one.' + return not self.__eq__(other) + + def __repr__(self): + 'Get the string representation of this four by four matrix.' + output = cStringIO.StringIO() + self.addXML(0, output) + return output.getvalue() + + def addXML(self, depth, output): + 'Add xml for this object.' + attributes = self.getAttributes() + if len(attributes) > 0: + xml_simple_writer.addClosedXMLTag(attributes, depth, self.__class__.__name__.lower(), output) + + def getAttributes(self, prefix=''): + 'Get the attributes from row column attribute strings, counting from one.' + attributes = {} + if self.tetragrid == None: + return attributes + for row in xrange(4): + for column in xrange(4): + default = float(column == row) + value = self.tetragrid[row][column] + if abs( value - default ) > 0.00000000000001: + if abs(value) < 0.00000000000001: + value = 0.0 + attributes[prefix + getKeyM(row, column)] = value + return attributes + + def getFromElementNode(self, elementNode, prefix): + 'Get the values from row column attribute strings, counting from one.' + attributes = elementNode.attributes + if attributes == None: + return self + self.tetragrid = getTetragridTimesOther(getTransformTetragrid(elementNode, prefix), self.tetragrid) + self.tetragrid = getTetragridTimesOther(getScaleTetragrid(elementNode, 'scale.'), self.tetragrid) + self.tetragrid = getTetragridTimesOther(getRotateTetragrid(elementNode, 'rotate.'), self.tetragrid) + self.tetragrid = getTetragridTimesOther(getTranslateTetragrid(elementNode, 'translate.'), self.tetragrid) + return self + + def getOtherTimesSelf(self, otherTetragrid): + 'Get this matrix reverse multiplied by the other matrix.' + return Matrix(getTetragridTimesOther(otherTetragrid, self.tetragrid)) + + def getSelfTimesOther(self, otherTetragrid): + 'Get this matrix multiplied by the other matrix.' + return Matrix(getTetragridTimesOther(self.tetragrid, otherTetragrid)) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/_scale.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/_scale.py new file mode 100644 index 0000000..787ea1c --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/_scale.py @@ -0,0 +1,68 @@ +""" +Boolean geometry scale. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 340 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + "Get equated geometryOutput." + scalePoints( elementNode, matrix.getVertexes(geometryOutput), prefix ) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get equated paths." + scalePoints( elementNode, loop, prefix ) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return ScaleDerivation(elementNode) + +def manipulateElementNode(elementNode, target): + "Manipulate the xml element." + derivation = ScaleDerivation(elementNode) + if derivation.scaleTetragrid == None: + print('Warning, scaleTetragrid was None in scale so nothing will be done for:') + print(elementNode) + return + matrix.setAttributesToMultipliedTetragrid(target, derivation.scaleTetragrid) + +def processElementNode(elementNode): + "Process the xml element." + solid.processElementNodeByFunction(elementNode, manipulateElementNode) + +def scalePoints(elementNode, points, prefix): + "Scale the points." + scaleVector3Default = Vector3(1.0, 1.0, 1.0) + scaleVector3 = matrix.getCumulativeVector3Remove(scaleVector3Default.copy(), elementNode, prefix) + if scaleVector3 == scaleVector3Default: + return + for point in points: + point.x *= scaleVector3.x + point.y *= scaleVector3.y + point.z *= scaleVector3.z + + +class ScaleDerivation: + "Class to hold scale variables." + def __init__(self, elementNode): + 'Set defaults.' + self.scaleTetragrid = matrix.getScaleTetragrid(elementNode, '') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/rotate.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/rotate.py new file mode 100644 index 0000000..a64b2d8 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/rotate.py @@ -0,0 +1,67 @@ +""" +Boolean geometry rotate. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 360 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get equated geometryOutput.' + rotatePoints(elementNode, matrix.getVertexes(geometryOutput), prefix) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + 'Get equated paths.' + rotatePoints(elementNode, loop, prefix) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return RotateDerivation(elementNode, prefix) + +def manipulateElementNode(elementNode, target): + 'Manipulate the xml element.' + derivation = RotateDerivation(elementNode, '') + if derivation.rotateTetragrid == None: + print('Warning, rotateTetragrid was None in rotate so nothing will be done for:') + print(elementNode) + return + matrix.setAttributesToMultipliedTetragrid(target, derivation.rotateTetragrid) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunction(elementNode, manipulateElementNode) + +def rotatePoints(elementNode, points, prefix): + 'Rotate the points.' + derivation = RotateDerivation(elementNode, prefix) + if derivation.rotateTetragrid == None: + print('Warning, rotateTetragrid was None in rotate so nothing will be done for:') + print(elementNode) + return + matrix.transformVector3sByMatrix(derivation.rotateTetragrid, points) + + +class RotateDerivation: + "Class to hold rotate variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.rotateTetragrid = matrix.getRotateTetragrid(elementNode, prefix) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/transform.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/transform.py new file mode 100644 index 0000000..3df0b61 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/transform.py @@ -0,0 +1,67 @@ +""" +Boolean geometry transform. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 320 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get equated geometryOutput.' + transformPoints(elementNode, matrix.getVertexes(geometryOutput), prefix) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + 'Get equated paths.' + transformPoints(elementNode, loop, prefix) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return TransformDerivation(elementNode, prefix) + +def manipulateElementNode(elementNode, target): + 'Manipulate the xml element.' + derivation = TransformDerivation(elementNode, '') + if derivation.transformTetragrid == None: + print('Warning, transformTetragrid was None in transform so nothing will be done for:') + print(elementNode) + return + matrix.setAttributesToMultipliedTetragrid(target, derivation.transformTetragrid) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunction(elementNode, manipulateElementNode) + +def transformPoints(elementNode, points, prefix): + 'Transform the points.' + derivation = TransformDerivation(elementNode, prefix) + if derivation.transformTetragrid == None: + print('Warning, transformTetragrid was None in transform so nothing will be done for:') + print(elementNode) + return + matrix.transformVector3sByMatrix(derivation.transformTetragrid, points) + + +class TransformDerivation: + "Class to hold transform variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.transformTetragrid = matrix.getTransformTetragrid(elementNode, prefix) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/translate.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/translate.py new file mode 100644 index 0000000..fb68398 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_matrix/translate.py @@ -0,0 +1,68 @@ +""" +Boolean geometry translation. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 380 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + "Get equated geometryOutput." + translatePoints(elementNode, matrix.getVertexes(geometryOutput), prefix) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get equated paths." + translatePoints(elementNode, loop, prefix) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return TranslateDerivation(elementNode) + +def manipulateElementNode(elementNode, target): + "Manipulate the xml element." + derivation = TranslateDerivation(elementNode) + if derivation.translateTetragrid == None: + print('Warning, translateTetragrid was None in translate so nothing will be done for:') + print(elementNode) + return + matrix.setAttributesToMultipliedTetragrid(target, derivation.translateTetragrid) + +def processElementNode(elementNode): + "Process the xml element." + solid.processElementNodeByFunction(elementNode, manipulateElementNode) + +def translateNegativesPositives(negatives, positives, translation): + 'Translate the negatives and postives.' + euclidean.translateVector3Path(matrix.getVertexes(negatives), translation) + euclidean.translateVector3Path(matrix.getVertexes(positives), translation) + +def translatePoints(elementNode, points, prefix): + "Translate the points." + translateVector3 = matrix.getCumulativeVector3Remove(Vector3(), elementNode, prefix) + if abs(translateVector3) > 0.0: + euclidean.translateVector3Path(points, translateVector3) + + +class TranslateDerivation: + "Class to hold translate variables." + def __init__(self, elementNode): + 'Set defaults.' + self.translateTetragrid = matrix.getTranslateTetragrid(elementNode, '') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_array.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_array.py new file mode 100644 index 0000000..4f62546 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_array.py @@ -0,0 +1,129 @@ +""" +Boolean geometry array. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import vertex +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addPathToGroup(derivation, groupDictionaryCopy, path, targetMatrix, totalIndex): + 'Add path to the array group.' + for pointIndex, point in enumerate(path): + arrayElement = derivation.target.getCopy(derivation.elementNode.getIDSuffix(totalIndex), derivation.elementNode) + arrayDictionary = arrayElement.attributes + arrayDictionary['visible'] = str(derivation.visible).lower() + arrayDictionary.update(groupDictionaryCopy) + euclidean.removeTrueFromDictionary(arrayDictionary, 'visible') + vertexMatrix = matrix.Matrix(matrix.getTranslateTetragridByTranslation(point)) + zAngle = totalIndex * 50.0 + rotationMatrix = getRotationMatrix(arrayDictionary, derivation, path, point, pointIndex) + arrayElementMatrix = vertexMatrix.getSelfTimesOther(rotationMatrix.getSelfTimesOther(targetMatrix.tetragrid).tetragrid) + arrayDictionary.update(arrayElementMatrix.getAttributes('matrix.')) + arrayDictionary['_arrayIndex'] = totalIndex + arrayDictionary['_arrayPoint'] = point + totalIndex += 1 + +def getNewDerivation(elementNode): + 'Get new derivation.' + return ArrayDerivation(elementNode) + +def getRotationMatrix(arrayDictionary, derivation, path, point, pointIndex): + 'Get rotationMatrix.' + if len(path) < 2 or not derivation.track: + return matrix.Matrix() + point = point.dropAxis() + begin = path[(pointIndex + len(path) - 1) % len(path)].dropAxis() + end = path[(pointIndex + 1) % len(path)].dropAxis() + pointMinusBegin = point - begin + pointMinusBeginLength = abs(pointMinusBegin) + endMinusPoint = end - point + endMinusPointLength = abs(endMinusPoint) + if not derivation.closed: + if pointIndex == 0 and endMinusPointLength > 0.0: + return getRotationMatrixByPolar(arrayDictionary, endMinusPoint, endMinusPointLength) + elif pointIndex == len(path) - 1 and pointMinusBeginLength > 0.0: + return getRotationMatrixByPolar(arrayDictionary, pointMinusBegin, pointMinusBeginLength) + if pointMinusBeginLength <= 0.0: + print('Warning, point equals previous point in getRotationMatrix in array for:') + print(path) + print(pointIndex) + print(derivation.elementNode) + return matrix.Matrix() + pointMinusBegin /= pointMinusBeginLength + if endMinusPointLength <= 0.0: + print('Warning, point equals next point in getRotationMatrix in array for:') + print(path) + print(pointIndex) + print(derivation.elementNode) + return matrix.Matrix() + endMinusPoint /= endMinusPointLength + averagePolar = pointMinusBegin + endMinusPoint + averagePolarLength = abs(averagePolar) + if averagePolarLength <= 0.0: + print('Warning, averagePolarLength is zero in getRotationMatrix in array for:') + print(path) + print(pointIndex) + print(derivation.elementNode) + return matrix.Matrix() + return getRotationMatrixByPolar(arrayDictionary, averagePolar, averagePolarLength) + +def getRotationMatrixByPolar(arrayDictionary, polar, polarLength): + 'Get rotationMatrix by polar and polarLength.' + polar /= polarLength + arrayDictionary['_arrayRotation'] = math.degrees(math.atan2(polar.imag, polar.real)) + return matrix.Matrix(matrix.getDiagonalSwitchedTetragridByPolar([0, 1], polar)) + +def processElementNode(elementNode): + "Process the xml element." + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = ArrayDerivation(elementNode) + if derivation.target == None: + print('Warning, array could not get target for:') + print(elementNode) + return + if len(derivation.paths) < 1: + print('Warning, array could not get paths for:') + print(elementNode) + return + groupDictionaryCopy = elementNode.attributes.copy() + euclidean.removeElementsFromDictionary(groupDictionaryCopy, ['closed', 'paths', 'target', 'track', 'vertexes']) + evaluate.removeIdentifiersFromDictionary(groupDictionaryCopy) + targetMatrix = matrix.getBranchMatrixSetElementNode(derivation.target) + elementNode.localName = 'group' + totalIndex = 0 + for path in derivation.paths: + addPathToGroup(derivation, groupDictionaryCopy, path, targetMatrix, totalIndex) + elementNode.getXMLProcessor().processElementNode(elementNode) + + +class ArrayDerivation: + "Class to hold array variables." + def __init__(self, elementNode): + 'Set defaults.' + self.closed = evaluate.getEvaluatedBoolean(True, elementNode, 'closed') + self.elementNode = elementNode + self.paths = evaluate.getTransformedPathsByKey([], elementNode, 'paths') + vertexTargets = evaluate.getElementNodesByKey(elementNode, 'vertexes') + for vertexTarget in vertexTargets: + self.paths.append(vertexTarget.getVertexes()) + self.target = evaluate.getElementNodeByKey(elementNode, 'target') + self.track = evaluate.getEvaluatedBoolean(True, elementNode, 'track') + self.visible = evaluate.getEvaluatedBoolean(True, elementNode, 'visible') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_carve.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_carve.py new file mode 100644 index 0000000..223fb23 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_carve.py @@ -0,0 +1,95 @@ +""" +Boolean geometry carve. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_geometry +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import xml_simple_reader + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getLinkedElementNode(idSuffix, parentNode, target): + 'Get elementNode with identifiers and parentNode.' + linkedElementNode = xml_simple_reader.ElementNode() + euclidean.overwriteDictionary(target.attributes, ['id', 'name', 'quantity'], linkedElementNode.attributes) + linkedElementNode.addSuffixToID(idSuffix) + tagKeys = target.getTagKeys() + tagKeys.append('carve') + tagKeys.sort() + tags = ', '.join(tagKeys) + linkedElementNode.attributes['tags'] = tags + linkedElementNode.setParentAddToChildNodes(parentNode) + linkedElementNode.addToIdentifierDictionaries() + return linkedElementNode + +def getNewDerivation(elementNode): + 'Get new derivation.' + return CarveDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = CarveDerivation(elementNode) + targetElementNode = derivation.targetElementNode + if targetElementNode == None: + print('Warning, carve could not get target for:') + print(elementNode) + return + xmlObject = targetElementNode.xmlObject + if xmlObject == None: + print('Warning, processElementNodeByDerivation in carve could not get xmlObject for:') + print(targetElementNode) + print(derivation.elementNode) + return + matrix.getBranchMatrixSetElementNode(targetElementNode) + transformedVertexes = xmlObject.getTransformedVertexes() + if len(transformedVertexes) < 1: + print('Warning, transformedVertexes is zero in processElementNodeByDerivation in carve for:') + print(xmlObject) + print(targetElementNode) + print(derivation.elementNode) + return + elementNode.localName = 'group' + elementNode.getXMLProcessor().processElementNode(elementNode) + minimumZ = boolean_geometry.getMinimumZ(xmlObject) + maximumZ = euclidean.getTopPath(transformedVertexes) + zoneArrangement = triangle_mesh.ZoneArrangement(derivation.layerHeight, transformedVertexes) + oldVisibleString = targetElementNode.attributes['visible'] + targetElementNode.attributes['visible'] = True + z = minimumZ + 0.5 * derivation.layerHeight + loopLayers = boolean_geometry.getLoopLayers([xmlObject], derivation.importRadius, derivation.layerHeight, maximumZ, False, z, zoneArrangement) + targetElementNode.attributes['visible'] = oldVisibleString + for loopLayerIndex, loopLayer in enumerate(loopLayers): + if len(loopLayer.loops) > 0: + pathElement = getLinkedElementNode('_carve_%s' % loopLayerIndex, elementNode, targetElementNode) + vector3Loops = euclidean.getVector3Paths(loopLayer.loops, loopLayer.z) + path.convertElementNode(pathElement, vector3Loops) + + +class CarveDerivation: + "Class to hold carve variables." + def __init__(self, elementNode): + 'Set defaults.' + self.elementNode = elementNode + self.importRadius = setting.getImportRadius(elementNode) + self.layerHeight = setting.getLayerHeight(elementNode) + self.targetElementNode = evaluate.getElementNodeByKey(elementNode, 'target') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_copy.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_copy.py new file mode 100644 index 0000000..ade3b95 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/_copy.py @@ -0,0 +1,72 @@ +""" +Boolean geometry copy. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewDerivation(elementNode): + 'Get new derivation.' + return CopyDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = CopyDerivation(elementNode) + if derivation.target == None: + print('Warning, copy could not get target for:') + print(elementNode) + return + del elementNode.attributes['target'] + copyMatrix = matrix.getBranchMatrixSetElementNode(elementNode) + targetMatrix = matrix.getBranchMatrixSetElementNode(derivation.target) + targetDictionaryCopy = evaluate.removeIdentifiersFromDictionary(derivation.target.attributes.copy()) + targetDictionaryCopy.update(elementNode.attributes) + elementNode.attributes = targetDictionaryCopy + euclidean.removeTrueFromDictionary(elementNode.attributes, 'visible') + elementNode.localName = derivation.target.localName + derivation.target.copyXMLChildNodes(elementNode.getIDSuffix(), elementNode) + elementNode.getXMLProcessor().processElementNode(elementNode) + if copyMatrix != None and targetMatrix != None: + elementNode.xmlObject.matrix4X4 = copyMatrix.getSelfTimesOther(targetMatrix.tetragrid) + if elementNode.xmlObject == None: + return + if len(elementNode.xmlObject.getPaths()) > 0: + lineation.processElementNode(elementNode) + return + geometryOutput = elementNode.xmlObject.getGeometryOutput() + if geometryOutput == None: + return + solidMatchingPlugins = solid.getSolidMatchingPlugins(elementNode) + if len(solidMatchingPlugins) == 0: + return + geometryOutput = solid.getGeometryOutputByManipulation(elementNode, geometryOutput) + elementNode.xmlObject.transformGeometryOutput(geometryOutput) + lineation.removeChildNodesFromElementObject(elementNode) + elementNode.getXMLProcessor().convertElementNode(elementNode, geometryOutput) + + +class CopyDerivation: + "Class to hold copy variables." + def __init__(self, elementNode): + 'Set defaults.' + self.target = evaluate.getElementNodeByKey(elementNode, 'target') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/disjoin.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/disjoin.py new file mode 100644 index 0000000..32ebd49 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/disjoin.py @@ -0,0 +1,114 @@ +""" +Boolean geometry disjoin. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import path +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_geometry +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import difference +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import xml_simple_reader +from fabmetheus_utilities.vector3 import Vector3 + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getLinkedElementNode(idSuffix, parentNode, target): + 'Get elementNode with identifiers and parentNode.' + linkedElementNode = xml_simple_reader.ElementNode() + euclidean.overwriteDictionary(target.attributes, ['id', 'name', 'quantity'], linkedElementNode.attributes) + linkedElementNode.addSuffixToID(idSuffix) + tagKeys = target.getTagKeys() + tagKeys.append('disjoin') + tagKeys.sort() + tags = ', '.join(tagKeys) + linkedElementNode.attributes['tags'] = tags + linkedElementNode.setParentAddToChildNodes(parentNode) + linkedElementNode.addToIdentifierDictionaries() + return linkedElementNode + +def getNewDerivation(elementNode): + 'Get new derivation.' + return DisjoinDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = DisjoinDerivation(elementNode) + targetElementNode = derivation.targetElementNode + if targetElementNode == None: + print('Warning, disjoin could not get target for:') + print(elementNode) + return + xmlObject = targetElementNode.xmlObject + if xmlObject == None: + print('Warning, processElementNodeByDerivation in disjoin could not get xmlObject for:') + print(targetElementNode) + print(derivation.elementNode) + return + matrix.getBranchMatrixSetElementNode(targetElementNode) + transformedVertexes = xmlObject.getTransformedVertexes() + if len(transformedVertexes) < 1: + print('Warning, transformedVertexes is zero in processElementNodeByDerivation in disjoin for:') + print(xmlObject) + print(targetElementNode) + print(derivation.elementNode) + return + elementNode.localName = 'group' + elementNode.getXMLProcessor().processElementNode(elementNode) + targetChainMatrix = matrix.Matrix(xmlObject.getMatrixChainTetragrid()) + minimumZ = boolean_geometry.getMinimumZ(xmlObject) + z = minimumZ + 0.5 * derivation.sheetThickness + zoneArrangement = triangle_mesh.ZoneArrangement(derivation.layerHeight, transformedVertexes) + oldVisibleString = targetElementNode.attributes['visible'] + targetElementNode.attributes['visible'] = True + loops = boolean_geometry.getEmptyZLoops([xmlObject], derivation.importRadius, False, z, zoneArrangement) + targetElementNode.attributes['visible'] = oldVisibleString + vector3Loops = euclidean.getVector3Paths(loops, z) + pathElement = getLinkedElementNode('_sheet', elementNode, targetElementNode) + path.convertElementNode(pathElement, vector3Loops) + targetOutput = xmlObject.getGeometryOutput() + differenceElement = getLinkedElementNode('_solid', elementNode, targetElementNode) + targetElementCopy = targetElementNode.getCopy('_positive', differenceElement) + targetElementCopy.attributes['visible'] = True + targetElementCopy.attributes.update(targetChainMatrix.getAttributes('matrix.')) + complexMaximum = euclidean.getMaximumByVector3Path(transformedVertexes).dropAxis() + complexMinimum = euclidean.getMinimumByVector3Path(transformedVertexes).dropAxis() + centerComplex = 0.5 * (complexMaximum + complexMinimum) + centerVector3 = Vector3(centerComplex.real, centerComplex.imag, minimumZ) + slightlyMoreThanHalfExtent = 0.501 * (complexMaximum - complexMinimum) + inradius = Vector3(slightlyMoreThanHalfExtent.real, slightlyMoreThanHalfExtent.imag, derivation.sheetThickness) + cubeElement = xml_simple_reader.ElementNode() + cubeElement.attributes['inradius'] = str(inradius) + if not centerVector3.getIsDefault(): + cubeElement.attributes['translate.'] = str(centerVector3) + cubeElement.localName = 'cube' + cubeElement.setParentAddToChildNodes(differenceElement) + difference.processElementNode(differenceElement) + + +class DisjoinDerivation: + "Class to hold disjoin variables." + def __init__(self, elementNode): + 'Set defaults.' + self.elementNode = elementNode + self.importRadius = setting.getImportRadius(elementNode) + self.layerHeight = setting.getLayerHeight(elementNode) + self.sheetThickness = setting.getSheetThickness(elementNode) + self.targetElementNode = evaluate.getElementNodeByKey(elementNode, 'target') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/import.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/import.py new file mode 100644 index 0000000..f457a84 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/import.py @@ -0,0 +1,99 @@ +""" +Boolean geometry group of solids. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import group +from fabmetheus_utilities import xml_simple_reader +from fabmetheus_utilities import xml_simple_writer +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +import cStringIO +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def appendAttributes(fromElementNode, toElementNode): + 'Append the attributes from the child nodes of fromElementNode to the attributes of toElementNode.' + for childNode in fromElementNode.childNodes: + toElementNode.attributes.update(evaluate.removeIdentifiersFromDictionary(childNode.attributes.copy())) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return ImportDerivation(elementNode) + +def getXMLFromCarvingFileName(fileName): + 'Get xml text from xml text.' + carving = fabmetheus_interpret.getCarving(fileName) + if carving == None: + return '' + output = xml_simple_writer.getBeginGeometryXMLOutput() + carving.addXML(0, output) + return xml_simple_writer.getEndGeometryXMLString(output) + +def processElementNode(elementNode): + "Process the xml element." + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = ImportDerivation(elementNode) + if derivation.fileName == None: + return + parserFileName = elementNode.getOwnerDocument().fileName + absoluteFileName = archive.getAbsoluteFolderPath(parserFileName, derivation.fileName) + if 'models/' not in absoluteFileName: + print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:') + print(elementNode) + print('For which the absolute file path is:') + print(absoluteFileName) + print('The import tool can only read a file which has models/ in the file path.') + print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.') + return + xmlText = '' + if derivation.fileName.endswith('.xml'): + xmlText = archive.getFileText(absoluteFileName) + else: + xmlText = getXMLFromCarvingFileName(absoluteFileName) + print('The import tool is opening the file:') + print(absoluteFileName) + if xmlText == '': + print('The file %s could not be found by processElementNode in import.' % derivation.fileName) + return + if derivation.importName == None: + elementNode.attributes['_importName'] = archive.getUntilDot(derivation.fileName) + if derivation.basename: + elementNode.attributes['_importName'] = os.path.basename(elementNode.attributes['_importName']) + xml_simple_reader.createAppendByText(elementNode, xmlText) + if derivation.appendDocumentElement: + appendAttributes(elementNode, elementNode.getDocumentElement()) + if derivation.appendElement: + appendAttributes(elementNode, elementNode) + elementNode.localName = 'group' + evaluate.processArchivable(group.Group, elementNode) + + +class ImportDerivation: + "Class to hold import variables." + def __init__(self, elementNode): + 'Set defaults.' + self.appendDocumentElement = evaluate.getEvaluatedBoolean(False, elementNode, 'appendDocumentElement') + self.appendElement = evaluate.getEvaluatedBoolean(False, elementNode, 'appendElement') + self.basename = evaluate.getEvaluatedBoolean(True, elementNode, 'basename') + self.elementNode = elementNode + self.fileName = evaluate.getEvaluatedString('', elementNode, 'file') + self.importName = evaluate.getEvaluatedString(None, elementNode, '_importName') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/write.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/write.py new file mode 100644 index 0000000..f581168 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_meta/write.py @@ -0,0 +1,98 @@ +""" +Boolean geometry write. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +import os + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewDerivation(elementNode): + 'Get new derivation.' + return WriteDerivation(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = WriteDerivation(elementNode) + if len(derivation.targets) < 1: + print('Warning, processElementNode in write could not get targets for:') + print(elementNode) + return + fileNames = [] + for target in derivation.targets: + writeElementNode(derivation, fileNames, target) + +def writeElementNode(derivation, fileNames, target): + "Write a quantity of the target." + xmlObject = target.xmlObject + if xmlObject == None: + print('Warning, writeTarget in write could not get xmlObject for:') + print(target) + print(derivation.elementNode) + return + parserDirectory = os.path.dirname(derivation.elementNode.getOwnerDocument().fileName) + absoluteFolderDirectory = os.path.abspath(os.path.join(parserDirectory, derivation.folderName)) + if '/models' not in absoluteFolderDirectory: + print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:') + print(derivation.elementNode) + print('For which the absolute folder path is:') + print(absoluteFolderDirectory) + print('The write tool can only write a file which has models/ in the file path.') + print('To write the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.') + return + quantity = evaluate.getEvaluatedInt(1, target, 'quantity') + for itemIndex in xrange(quantity): + writeXMLObject(absoluteFolderDirectory, derivation, fileNames, target, xmlObject) + +def writeXMLObject(absoluteFolderDirectory, derivation, fileNames, target, xmlObject): + "Write one instance of the xmlObject." + extension = evaluate.getEvaluatedString(xmlObject.getFabricationExtension(), derivation.elementNode, 'extension') + fileNameRoot = derivation.fileName + if fileNameRoot == '': + fileNameRoot = evaluate.getEvaluatedString('', target, 'name') + fileNameRoot = evaluate.getEvaluatedString(fileNameRoot, target, 'id') + fileNameRoot += derivation.suffix + fileName = '%s.%s' % (fileNameRoot, extension) + suffixIndex = 2 + while fileName in fileNames: + fileName = '%s_%s.%s' % (fileNameRoot, suffixIndex, extension) + suffixIndex += 1 + absoluteFileName = os.path.join(absoluteFolderDirectory, fileName) + fileNames.append(fileName) + archive.makeDirectory(absoluteFolderDirectory) + if not derivation.writeMatrix: + xmlObject.matrix4X4 = matrix.Matrix() + print('The write tool generated the file:') + print(absoluteFileName) + archive.writeFileText(absoluteFileName, xmlObject.getFabricationText(derivation.addLayerTemplate)) + + +class WriteDerivation: + "Class to hold write variables." + def __init__(self, elementNode): + 'Set defaults.' + self.addLayerTemplate = evaluate.getEvaluatedBoolean(False, elementNode, 'addLayerTemplate') + self.elementNode = elementNode + self.fileName = evaluate.getEvaluatedString('', elementNode, 'file') + self.folderName = evaluate.getEvaluatedString('', elementNode, 'folder') + self.suffix = evaluate.getEvaluatedString('', elementNode, 'suffix') + self.targets = evaluate.getElementNodesByKey(elementNode, 'target') + self.writeMatrix = evaluate.getEvaluatedBoolean(True, elementNode, 'writeMatrix') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/bevel.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/bevel.py new file mode 100644 index 0000000..78afcfd --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/bevel.py @@ -0,0 +1,71 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 20 + + +def getBevelPath( begin, center, close, end, radius ): + "Get bevel path." + beginComplex = begin.dropAxis() + centerComplex = center.dropAxis() + endComplex = end.dropAxis() + beginComplexSegmentLength = abs( centerComplex - beginComplex ) + endComplexSegmentLength = abs( centerComplex - endComplex ) + minimumRadius = lineation.getMinimumRadius( beginComplexSegmentLength, endComplexSegmentLength, radius ) + if minimumRadius <= close: + return [ center ] + beginBevel = center + minimumRadius / beginComplexSegmentLength * ( begin - center ) + endBevel = center + minimumRadius / endComplexSegmentLength * ( end - center ) + if radius > 0.0: + return [ beginBevel, endBevel ] + midpointComplex = 0.5 * ( beginBevel.dropAxis() + endBevel.dropAxis() ) + spikeComplex = centerComplex + centerComplex - midpointComplex + return [ beginBevel, Vector3( spikeComplex.real, spikeComplex.imag, center.z ), endBevel ] + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get bevel loop." + if len(loop) < 3: + return [loop] + derivation = BevelDerivation(elementNode, prefix, sideLength) + if derivation.radius == 0.0: + return loop + bevelLoop = [] + for pointIndex in xrange(len(loop)): + begin = loop[(pointIndex + len(loop) - 1) % len(loop)] + center = loop[pointIndex] + end = loop[(pointIndex + 1) % len(loop)] + bevelLoop += getBevelPath(begin, center, close, end, derivation.radius) + return [euclidean.getLoopWithoutCloseSequentialPoints(close, bevelLoop)] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return BevelDerivation(elementNode, prefix, sideLength) + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class BevelDerivation: + "Class to hold bevel variables." + def __init__(self, elementNode, prefix, sideLength): + 'Set defaults.' + self.radius = lineation.getFloatByPrefixSide(0.0, elementNode, prefix + 'radius', sideLength) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/convex.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/convex.py new file mode 100644 index 0000000..f3adbf2 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/convex.py @@ -0,0 +1,37 @@ +""" +Create outline. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 80 + + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get path with overhangs removed or filled in." + if len(loop) < 4: + return [loop] + loopComplex = euclidean.getComplexPath(loop) + return euclidean.getVector3Paths([euclidean.getLoopConvex(loopComplex)], loop[0].z) + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return evaluate.EmptyObject() + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/outline.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/outline.py new file mode 100644 index 0000000..9c27bc5 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/outline.py @@ -0,0 +1,53 @@ +""" +Create outline. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import intercircle + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 80 + + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get path with outline." + if len(loop) < 2: + return [loop] + derivation = OutlineDerivation(elementNode, prefix, sideLength) + loopComplex = euclidean.getComplexPath(loop) + if derivation.isClosed: + loopComplexes = intercircle.getAroundsFromLoop(loopComplex, derivation.radius) + else: + loopComplexes = intercircle.getAroundsFromPath(loopComplex, derivation.radius) + return euclidean.getVector3Paths(loopComplexes, loop[0].z) + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return OutlineDerivation(elementNode, prefix, sideLength) + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class OutlineDerivation: + "Class to hold outline variables." + def __init__(self, elementNode, prefix, sideLength): + 'Set defaults.' + self.isClosed = evaluate.getEvaluatedBoolean(False, elementNode, prefix + 'closed') + self.radius = evaluate.getEvaluatedFloat(setting.getEdgeWidth(elementNode), elementNode, prefix + 'radius') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/overhang.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/overhang.py new file mode 100644 index 0000000..c913e7d --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/overhang.py @@ -0,0 +1,397 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 100 + + +def addUnsupportedPointIndexes( alongAway ): + "Add the indexes of the unsupported points." + addedUnsupportedPointIndexes = [] + for pointIndex in xrange( len( alongAway.loop ) ): + point = alongAway.loop[pointIndex] + if pointIndex not in alongAway.unsupportedPointIndexes: + if not alongAway.getIsClockwisePointSupported(point): + alongAway.unsupportedPointIndexes.append( pointIndex ) + addedUnsupportedPointIndexes.append( pointIndex ) + for pointIndex in addedUnsupportedPointIndexes: + point = alongAway.loop[pointIndex] + point.y += alongAway.maximumYPlus + +def alterClockwiseSupportedPath( alongAway, elementNode ): + "Get clockwise path with overhangs carved out." + alongAway.bottomPoints = [] + alongAway.overhangSpan = setting.getOverhangSpan(elementNode) + maximumY = - 987654321.0 + minimumYPointIndex = 0 + for pointIndex in xrange( len( alongAway.loop ) ): + point = alongAway.loop[pointIndex] + if point.y < alongAway.loop[ minimumYPointIndex ].y: + minimumYPointIndex = pointIndex + maximumY = max( maximumY, point.y ) + alongAway.maximumYPlus = 2.0 * ( maximumY - alongAway.loop[ minimumYPointIndex ].y ) + alongAway.loop = euclidean.getAroundLoop( minimumYPointIndex, minimumYPointIndex, alongAway.loop ) + overhangClockwise = OverhangClockwise( alongAway ) + alongAway.unsupportedPointIndexes = [] + oldUnsupportedPointIndexesLength = - 987654321.0 + while len( alongAway.unsupportedPointIndexes ) > oldUnsupportedPointIndexesLength: + oldUnsupportedPointIndexesLength = len( alongAway.unsupportedPointIndexes ) + addUnsupportedPointIndexes( alongAway ) + for pointIndex in alongAway.unsupportedPointIndexes: + point = alongAway.loop[pointIndex] + point.y -= alongAway.maximumYPlus + alongAway.unsupportedPointIndexes.sort() + alongAway.unsupportedPointIndexLists = [] + oldUnsupportedPointIndex = - 987654321.0 + unsupportedPointIndexList = None + for unsupportedPointIndex in alongAway.unsupportedPointIndexes: + if unsupportedPointIndex > oldUnsupportedPointIndex + 1: + unsupportedPointIndexList = [] + alongAway.unsupportedPointIndexLists.append( unsupportedPointIndexList ) + oldUnsupportedPointIndex = unsupportedPointIndex + unsupportedPointIndexList.append( unsupportedPointIndex ) + alongAway.unsupportedPointIndexLists.reverse() + for unsupportedPointIndexList in alongAway.unsupportedPointIndexLists: + overhangClockwise.alterLoop( unsupportedPointIndexList ) + +def alterWiddershinsSupportedPath( alongAway, close ): + "Get widdershins path with overhangs filled in." + alongAway.bottomPoints = [] + alongAway.minimumY = getMinimumYByPath( alongAway.loop ) + for point in alongAway.loop: + if point.y - alongAway.minimumY < close: + alongAway.addToBottomPoints(point) + ascendingYPoints = alongAway.loop[:] + ascendingYPoints.sort( compareYAscending ) + overhangWiddershinsLeft = OverhangWiddershinsLeft( alongAway ) + overhangWiddershinsRight = OverhangWiddershinsRight( alongAway ) + for point in ascendingYPoints: + alterWiddershinsSupportedPathByPoint( alongAway, overhangWiddershinsLeft, overhangWiddershinsRight, point ) + +def alterWiddershinsSupportedPathByPoint( alongAway, overhangWiddershinsLeft, overhangWiddershinsRight, point ): + "Get widdershins path with overhangs filled in for point." + if alongAway.getIsWiddershinsPointSupported(point): + return + overhangWiddershins = overhangWiddershinsLeft + if overhangWiddershinsRight.getDistance() < overhangWiddershinsLeft.getDistance(): + overhangWiddershins = overhangWiddershinsRight + overhangWiddershins.alterLoop() + +def compareYAscending( point, pointOther ): + "Get comparison in order to sort points in ascending y." + if point.y < pointOther.y: + return - 1 + return int( point.y > pointOther.y ) + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get path with overhangs removed or filled in." + if len(loop) < 3: + print('Warning, loop has less than three sides in getManipulatedPaths in overhang for:') + print(elementNode) + return [loop] + derivation = OverhangDerivation(elementNode, prefix) + overhangPlaneAngle = euclidean.getWiddershinsUnitPolar(0.5 * math.pi - derivation.overhangRadians) + if derivation.overhangInclinationRadians != 0.0: + overhangInclinationCosine = abs(math.cos(derivation.overhangInclinationRadians)) + if overhangInclinationCosine == 0.0: + return [loop] + imaginaryTimesCosine = overhangPlaneAngle.imag * overhangInclinationCosine + overhangPlaneAngle = euclidean.getNormalized(complex(overhangPlaneAngle.real, imaginaryTimesCosine)) + alongAway = AlongAway(loop, overhangPlaneAngle) + if euclidean.getIsWiddershinsByVector3(loop): + alterWiddershinsSupportedPath(alongAway, close) + else: + alterClockwiseSupportedPath(alongAway, elementNode) + return [euclidean.getLoopWithoutCloseSequentialPoints(close, alongAway.loop)] + +def getMinimumYByPath(path): + "Get path with overhangs removed or filled in." + minimumYByPath = path[0].y + for point in path: + minimumYByPath = min( minimumYByPath, point.y ) + return minimumYByPath + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return OverhangDerivation(elementNode, prefix) + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class AlongAway: + "Class to derive the path along the point and away from the point." + def __init__( self, loop, overhangPlaneAngle ): + "Initialize." + self.loop = loop + self.overhangPlaneAngle = overhangPlaneAngle + self.ySupport = - self.overhangPlaneAngle.imag + + def __repr__(self): + "Get the string representation of AlongAway." + return '%s' % ( self.overhangPlaneAngle ) + + def addToBottomPoints(self, point): + "Add point to bottom points and set y to minimumY." + self.bottomPoints.append(point) + point.y = self.minimumY + + def getIsClockwisePointSupported(self, point): + "Determine if the point on the clockwise loop is supported." + self.point = point + self.pointIndex = None + self.awayIndexes = [] + numberOfIntersectionsBelow = 0 + for pointIndex in xrange( len( self.loop ) ): + begin = self.loop[pointIndex] + end = self.loop[ (pointIndex + 1) % len( self.loop ) ] + if begin != point and end != point: + self.awayIndexes.append( pointIndex ) + yIntersection = euclidean.getYIntersectionIfExists( begin.dropAxis(), end.dropAxis(), point.x ) + if yIntersection != None: + numberOfIntersectionsBelow += ( yIntersection < point.y ) + if begin == point: + self.pointIndex = pointIndex + if numberOfIntersectionsBelow % 2 == 0: + return True + if self.pointIndex == None: + return True + if self.getIsPointSupportedBySegment( self.pointIndex - 1 + len( self.loop ) ): + return True + return self.getIsPointSupportedBySegment( self.pointIndex + 1 ) + + def getIsPointSupportedBySegment( self, endIndex ): + "Determine if the point on the widdershins loop is supported." + endComplex = self.loop[ ( endIndex % len( self.loop ) ) ].dropAxis() + endMinusPointComplex = euclidean.getNormalized( endComplex - self.point.dropAxis() ) + return endMinusPointComplex.imag < self.ySupport + + def getIsWiddershinsPointSupported(self, point): + "Determine if the point on the widdershins loop is supported." + if point.y <= self.minimumY: + return True + self.point = point + self.pointIndex = None + self.awayIndexes = [] + numberOfIntersectionsBelow = 0 + for pointIndex in xrange( len( self.loop ) ): + begin = self.loop[pointIndex] + end = self.loop[ (pointIndex + 1) % len( self.loop ) ] + if begin != point and end != point: + self.awayIndexes.append( pointIndex ) + yIntersection = euclidean.getYIntersectionIfExists( begin.dropAxis(), end.dropAxis(), point.x ) + if yIntersection != None: + numberOfIntersectionsBelow += ( yIntersection < point.y ) + if begin == point: + self.pointIndex = pointIndex + if numberOfIntersectionsBelow % 2 == 1: + return True + if self.pointIndex == None: + return True + if self.getIsPointSupportedBySegment( self.pointIndex - 1 + len( self.loop ) ): + return True + return self.getIsPointSupportedBySegment( self.pointIndex + 1 ) + + +class OverhangClockwise: + "Class to get the intersection up from the point." + def __init__( self, alongAway ): + "Initialize." + self.alongAway = alongAway + self.halfRiseOverWidth = 0.5 * alongAway.overhangPlaneAngle.imag / alongAway.overhangPlaneAngle.real + self.widthOverRise = alongAway.overhangPlaneAngle.real / alongAway.overhangPlaneAngle.imag + + def __repr__(self): + "Get the string representation of OverhangClockwise." + return '%s' % ( self.intersectionPlaneAngle ) + + def alterLoop( self, unsupportedPointIndexes ): + "Alter alongAway loop." + unsupportedBeginIndex = unsupportedPointIndexes[0] + unsupportedEndIndex = unsupportedPointIndexes[-1] + beginIndex = unsupportedBeginIndex - 1 + endIndex = unsupportedEndIndex + 1 + begin = self.alongAway.loop[ beginIndex ] + end = self.alongAway.loop[ endIndex ] + truncatedOverhangSpan = self.alongAway.overhangSpan + width = end.x - begin.x + heightDifference = abs( end.y - begin.y ) + remainingWidth = width - self.widthOverRise * heightDifference + if remainingWidth <= 0.0: + del self.alongAway.loop[ unsupportedBeginIndex : endIndex ] + return + highest = begin + supportX = begin.x + remainingWidth + if end.y > begin.y: + highest = end + supportX = end.x - remainingWidth + tipY = highest.y + self.halfRiseOverWidth * remainingWidth + highestBetween = - 987654321.0 + for unsupportedPointIndex in unsupportedPointIndexes: + highestBetween = max( highestBetween, self.alongAway.loop[ unsupportedPointIndex ].y ) + if highestBetween > highest.y: + truncatedOverhangSpan = 0.0 + if highestBetween < tipY: + below = tipY - highestBetween + truncatedOverhangSpan = min( self.alongAway.overhangSpan, below / self.halfRiseOverWidth ) + truncatedOverhangSpanRadius = 0.5 * truncatedOverhangSpan + if remainingWidth <= truncatedOverhangSpan: + supportPoint = Vector3( supportX, highest.y, highest.z ) + self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = [ supportPoint ] + return + midSupportX = 0.5 * ( supportX + highest.x ) + if truncatedOverhangSpan <= 0.0: + supportPoint = Vector3( midSupportX, tipY, highest.z ) + self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = [ supportPoint ] + return + supportXLeft = midSupportX - truncatedOverhangSpanRadius + supportXRight = midSupportX + truncatedOverhangSpanRadius + supportY = tipY - self.halfRiseOverWidth * truncatedOverhangSpan + supportPoints = [ Vector3( supportXLeft, supportY, highest.z ), Vector3( supportXRight, supportY, highest.z ) ] + self.alongAway.loop[ unsupportedBeginIndex : endIndex ] = supportPoints + + +class OverhangDerivation: + "Class to hold overhang variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.overhangRadians = setting.getOverhangRadians(elementNode) + self.overhangInclinationRadians = math.radians(evaluate.getEvaluatedFloat(0.0, elementNode, prefix + 'inclination')) + + +class OverhangWiddershinsLeft: + "Class to get the intersection from the point down to the left." + def __init__( self, alongAway ): + "Initialize." + self.alongAway = alongAway + self.intersectionPlaneAngle = - alongAway.overhangPlaneAngle + self.setRatios() + + def __repr__(self): + "Get the string representation of OverhangWiddershins." + return '%s' % ( self.intersectionPlaneAngle ) + + def alterLoop(self): + "Alter alongAway loop." + insertedPoint = self.alongAway.point.copy() + if self.closestXIntersectionIndex != None: + self.alongAway.loop = self.getIntersectLoop() + intersectionRelativeComplex = self.closestXDistance * self.intersectionPlaneAngle + intersectionPoint = insertedPoint + Vector3( intersectionRelativeComplex.real, intersectionRelativeComplex.imag ) + self.alongAway.loop.append( intersectionPoint ) + return + if self.closestBottomPoint == None: + return + if self.closestBottomPoint not in self.alongAway.loop: + return + insertedPoint.x = self.bottomX + closestBottomIndex = self.alongAway.loop.index( self.closestBottomPoint ) + self.alongAway.addToBottomPoints( insertedPoint ) + self.alongAway.loop = self.getBottomLoop( closestBottomIndex, insertedPoint ) + self.alongAway.loop.append( insertedPoint ) + + def getBottomLoop( self, closestBottomIndex, insertedPoint ): + "Get loop around bottom." + endIndex = closestBottomIndex + len( self.alongAway.loop ) + 1 + return euclidean.getAroundLoop( self.alongAway.pointIndex, endIndex, self.alongAway.loop ) + + def getDistance(self): + "Get distance between point and closest intersection or bottom point along line." + self.pointMinusBottomY = self.alongAway.point.y - self.alongAway.minimumY + self.diagonalDistance = self.pointMinusBottomY * self.diagonalRatio + if self.alongAway.pointIndex == None: + return self.getDistanceToBottom() + rotatedLoop = euclidean.getRotatedComplexes( self.intersectionYMirror, euclidean.getComplexPath( self.alongAway.loop ) ) + rotatedPointComplex = rotatedLoop[ self.alongAway.pointIndex ] + beginX = rotatedPointComplex.real + endX = beginX + self.diagonalDistance + self.diagonalDistance + xIntersectionIndexList = [] + for pointIndex in self.alongAway.awayIndexes: + beginComplex = rotatedLoop[pointIndex] + endComplex = rotatedLoop[ (pointIndex + 1) % len( rotatedLoop ) ] + xIntersection = euclidean.getXIntersectionIfExists( beginComplex, endComplex, rotatedPointComplex.imag ) + if xIntersection != None: + if xIntersection >= beginX and xIntersection < endX: + xIntersectionIndexList.append( euclidean.XIntersectionIndex( pointIndex, xIntersection ) ) + self.closestXDistance = 987654321.0 + self.closestXIntersectionIndex = None + for xIntersectionIndex in xIntersectionIndexList: + xDistance = abs( xIntersectionIndex.x - beginX ) + if xDistance < self.closestXDistance: + self.closestXIntersectionIndex = xIntersectionIndex + self.closestXDistance = xDistance + if self.closestXIntersectionIndex != None: + return self.closestXDistance + return self.getDistanceToBottom() + + def getDistanceToBottom(self): + "Get distance between point and closest bottom point along line." + self.bottomX = self.alongAway.point.x + self.pointMinusBottomY * self.xRatio + self.closestBottomPoint = None + closestDistanceX = 987654321.0 + for point in self.alongAway.bottomPoints: + distanceX = abs( point.x - self.bottomX ) + if self.getIsOnside(point.x): + if distanceX < closestDistanceX: + closestDistanceX = distanceX + self.closestBottomPoint = point + return closestDistanceX + self.diagonalDistance + + def getIntersectLoop(self): + "Get intersection loop." + endIndex = self.closestXIntersectionIndex.index + len( self.alongAway.loop ) + 1 + return euclidean.getAroundLoop( self.alongAway.pointIndex, endIndex, self.alongAway.loop ) + + def getIsOnside( self, x ): + "Determine if x is on the side along the direction of the intersection line." + return x <= self.alongAway.point.x + + def setRatios(self): + "Set ratios." + self.diagonalRatio = 1.0 / abs( self.intersectionPlaneAngle.imag ) + self.intersectionYMirror = complex( self.intersectionPlaneAngle.real, - self.intersectionPlaneAngle.imag ) + self.xRatio = self.intersectionPlaneAngle.real / abs( self.intersectionPlaneAngle.imag ) + + +class OverhangWiddershinsRight( OverhangWiddershinsLeft ): + "Class to get the intersection from the point down to the right." + def __init__( self, alongAway ): + "Initialize." + self.alongAway = alongAway + self.intersectionPlaneAngle = complex( alongAway.overhangPlaneAngle.real, - alongAway.overhangPlaneAngle.imag ) + self.setRatios() + + def getBottomLoop( self, closestBottomIndex, insertedPoint ): + "Get loop around bottom." + endIndex = self.alongAway.pointIndex + len( self.alongAway.loop ) + 1 + return euclidean.getAroundLoop( closestBottomIndex, endIndex, self.alongAway.loop ) + + def getIntersectLoop(self): + "Get intersection loop." + beginIndex = self.closestXIntersectionIndex.index + len( self.alongAway.loop ) + 1 + endIndex = self.alongAway.pointIndex + len( self.alongAway.loop ) + 1 + return euclidean.getAroundLoop( beginIndex, endIndex, self.alongAway.loop ) + + def getIsOnside( self, x ): + "Determine if x is on the side along the direction of the intersection line." + return x >= self.alongAway.point.x diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/round.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/round.py new file mode 100644 index 0000000..ef96224 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/round.py @@ -0,0 +1,94 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 40 + + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get round loop." + if len(loop) < 3: + return [loop] + derivation = RoundDerivation(elementNode, prefix, sideLength) + if derivation.radius == 0.0: + return loop + roundLoop = [] + sidesPerRadian = 0.5 / math.pi * evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, sideLength) + for pointIndex in xrange(len(loop)): + begin = loop[(pointIndex + len(loop) - 1) % len(loop)] + center = loop[pointIndex] + end = loop[(pointIndex + 1) % len(loop)] + roundLoop += getRoundPath(begin, center, close, end, derivation.radius, sidesPerRadian) + return [euclidean.getLoopWithoutCloseSequentialPoints(close, roundLoop)] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return RoundDerivation(elementNode, prefix, sideLength) + +def getRoundPath( begin, center, close, end, radius, sidesPerRadian ): + "Get round path." + beginComplex = begin.dropAxis() + centerComplex = center.dropAxis() + endComplex = end.dropAxis() + beginComplexSegmentLength = abs( centerComplex - beginComplex ) + endComplexSegmentLength = abs( centerComplex - endComplex ) + minimumRadius = lineation.getMinimumRadius( beginComplexSegmentLength, endComplexSegmentLength, radius ) + if minimumRadius <= close: + return [ center ] + beginBevel = center + minimumRadius / beginComplexSegmentLength * ( begin - center ) + endBevel = center + minimumRadius / endComplexSegmentLength * ( end - center ) + beginBevelComplex = beginBevel.dropAxis() + endBevelComplex = endBevel.dropAxis() + midpointComplex = 0.5 * ( beginBevelComplex + endBevelComplex ) + if radius < 0.0: + centerComplex = midpointComplex + midpointComplex - centerComplex + midpointMinusCenterComplex = midpointComplex - centerComplex + midpointCenterLength = abs( midpointMinusCenterComplex ) + midpointEndLength = abs( midpointComplex - endBevelComplex ) + midpointCircleCenterLength = midpointEndLength * midpointEndLength / midpointCenterLength + circleRadius = math.sqrt( midpointCircleCenterLength * midpointCircleCenterLength + midpointEndLength * midpointEndLength ) + circleCenterComplex = midpointComplex + midpointMinusCenterComplex * midpointCircleCenterLength / midpointCenterLength + circleCenter = Vector3( circleCenterComplex.real, circleCenterComplex.imag, center.z ) + endMinusCircleCenterComplex = endBevelComplex - circleCenterComplex + beginMinusCircleCenter = beginBevel - circleCenter + beginMinusCircleCenterComplex = beginMinusCircleCenter.dropAxis() + angleDifference = euclidean.getAngleDifferenceByComplex( endMinusCircleCenterComplex, beginMinusCircleCenterComplex ) + steps = int( math.ceil( abs( angleDifference ) * sidesPerRadian ) ) + stepPlaneAngle = euclidean.getWiddershinsUnitPolar( angleDifference / float( steps ) ) + deltaZStep = ( end.z - begin.z ) / float( steps ) + roundPath = [ beginBevel ] + for step in xrange( 1, steps ): + beginMinusCircleCenterComplex = beginMinusCircleCenterComplex * stepPlaneAngle + arcPointComplex = circleCenterComplex + beginMinusCircleCenterComplex + arcPoint = Vector3( arcPointComplex.real, arcPointComplex.imag, begin.z + deltaZStep * step ) + roundPath.append( arcPoint ) + return roundPath + [ endBevel ] + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class RoundDerivation: + "Class to hold round variables." + def __init__(self, elementNode, prefix, sideLength): + 'Set defaults.' + self.radius = lineation.getFloatByPrefixSide(0.0, elementNode, prefix + 'radius', sideLength) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/segment.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/segment.py new file mode 100644 index 0000000..57ce3a7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/segment.py @@ -0,0 +1,166 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 60 + + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get segment loop." + if len(loop) < 3: + return [loop] + derivation = SegmentDerivation(elementNode, prefix) + if derivation.path == getSegmentPathDefault(): + return [loop] + path = getXNormalizedVector3Path(derivation.path) + if euclidean.getIsWiddershinsByVector3(loop): + path = path[: : -1] + for point in path: + point.x = 1.0 - point.x + if derivation.center == None: + point.y = - point.y + segmentLoop = [] + startEnd = StartEnd(elementNode, len(loop), prefix) + for pointIndex in xrange(len(loop)): + if pointIndex >= startEnd.start and pointIndex < startEnd.end: + segmentLoop += getSegmentPath(derivation.center, loop, path, pointIndex) + else: + segmentLoop.append(loop[pointIndex]) + return [euclidean.getLoopWithoutCloseSequentialPoints( close, segmentLoop)] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return SegmentDerivation(elementNode, prefix) + +def getRadialPath(begin, center, end, path): + "Get radial path." + beginComplex = begin.dropAxis() + endComplex = end.dropAxis() + centerComplex = center.dropAxis() + beginMinusCenterComplex = beginComplex - centerComplex + endMinusCenterComplex = endComplex - centerComplex + beginMinusCenterComplexRadius = abs( beginMinusCenterComplex ) + endMinusCenterComplexRadius = abs( endMinusCenterComplex ) + if beginMinusCenterComplexRadius == 0.0 or endMinusCenterComplexRadius == 0.0: + return [ begin ] + beginMinusCenterComplex /= beginMinusCenterComplexRadius + endMinusCenterComplex /= endMinusCenterComplexRadius + angleDifference = euclidean.getAngleDifferenceByComplex( endMinusCenterComplex, beginMinusCenterComplex ) + radialPath = [] + for point in path: + weightEnd = point.x + weightBegin = 1.0 - weightEnd + weightedRadius = beginMinusCenterComplexRadius * weightBegin + endMinusCenterComplexRadius * weightEnd * ( 1.0 + point.y ) + radialComplex = weightedRadius * euclidean.getWiddershinsUnitPolar( angleDifference * point.x ) * beginMinusCenterComplex + polygonPoint = center + Vector3( radialComplex.real, radialComplex.imag, point.z ) + radialPath.append( polygonPoint ) + return radialPath + +def getSegmentPath(center, loop, path, pointIndex): + "Get segment path." + centerBegin = loop[pointIndex] + centerEnd = loop[(pointIndex + 1) % len(loop)] + centerEndMinusBegin = centerEnd - centerBegin + if abs( centerEndMinusBegin ) <= 0.0: + return [ centerBegin ] + if center != None: + return getRadialPath(centerBegin, center, centerEnd, path) + begin = loop[(pointIndex + len(loop) - 1) % len(loop)] + end = loop[(pointIndex + 2) % len(loop)] + return getWedgePath(begin, centerBegin, centerEnd, centerEndMinusBegin, end, path) + +def getSegmentPathDefault(): + "Get segment path default." + return [Vector3(), Vector3(0.0, 1.0)] + +def getWedgePath( begin, centerBegin, centerEnd, centerEndMinusBegin, end, path ): + "Get segment path." + beginComplex = begin.dropAxis() + centerBeginComplex = centerBegin.dropAxis() + centerEndComplex = centerEnd.dropAxis() + endComplex = end.dropAxis() + wedgePath = [] + centerBeginMinusBeginComplex = euclidean.getNormalized( centerBeginComplex - beginComplex ) + centerEndMinusCenterBeginComplexOriginal = centerEndComplex - centerBeginComplex + centerEndMinusCenterBeginComplexLength = abs( centerEndMinusCenterBeginComplexOriginal ) + if centerEndMinusCenterBeginComplexLength <= 0.0: + return [ centerBegin ] + centerEndMinusCenterBeginComplex = centerEndMinusCenterBeginComplexOriginal / centerEndMinusCenterBeginComplexLength + endMinusCenterEndComplex = euclidean.getNormalized( endComplex - centerEndComplex ) + widdershinsBegin = getWiddershinsAverageByVector3( centerBeginMinusBeginComplex, centerEndMinusCenterBeginComplex ) + widdershinsEnd = getWiddershinsAverageByVector3( centerEndMinusCenterBeginComplex, endMinusCenterEndComplex ) + for point in path: + weightEnd = point.x + weightBegin = 1.0 - weightEnd + polygonPoint = centerBegin + centerEndMinusBegin * point.x + weightedWiddershins = widdershinsBegin * weightBegin + widdershinsEnd * weightEnd + polygonPoint += weightedWiddershins * point.y * centerEndMinusCenterBeginComplexLength + polygonPoint.z += point.z + wedgePath.append( polygonPoint ) + return wedgePath + +def getWiddershinsAverageByVector3( centerMinusBeginComplex, endMinusCenterComplex ): + "Get the normalized average of the widdershins vectors." + centerMinusBeginWiddershins = Vector3( - centerMinusBeginComplex.imag, centerMinusBeginComplex.real ) + endMinusCenterWiddershins = Vector3( - endMinusCenterComplex.imag, endMinusCenterComplex.real ) + return ( centerMinusBeginWiddershins + endMinusCenterWiddershins ).getNormalized() + +def getXNormalizedVector3Path(path): + "Get path where the x ranges from 0 to 1." + if len(path) < 1: + return path + minimumX = path[0].x + for point in path[1 :]: + minimumX = min( minimumX, point.x ) + for point in path: + point.x -= minimumX + maximumX = path[0].x + for point in path[1 :]: + maximumX = max( maximumX, point.x ) + for point in path: + point.x /= maximumX + return path + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class SegmentDerivation: + "Class to hold segment variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.center = evaluate.getVector3ByPrefix(None, elementNode, prefix + 'center') + self.path = evaluate.getPathByPrefix(elementNode, getSegmentPathDefault(), prefix) + + +class StartEnd: + 'Class to get a start through end range.' + def __init__(self, elementNode, modulo, prefix): + "Initialize." + self.start = evaluate.getEvaluatedInt(0, elementNode, prefix + 'start') + self.extent = evaluate.getEvaluatedInt(modulo - self.start, elementNode, prefix + 'extent') + self.end = evaluate.getEvaluatedInt(self.start + self.extent, elementNode, prefix + 'end') + self.revolutions = evaluate.getEvaluatedInt(1, elementNode, prefix + 'revolutions') + if self.revolutions > 1: + self.end += modulo * (self.revolutions - 1) + + def __repr__(self): + "Get the string representation of this StartEnd." + return '%s, %s, %s' % (self.start, self.end, self.revolutions) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/wedge.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/wedge.py new file mode 100644 index 0000000..70800f1 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_paths/wedge.py @@ -0,0 +1,43 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.vector3 import Vector3 + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = -200 + + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get wedge loop." + derivation = WedgeDerivation(elementNode, prefix) + loop.append(derivation.center) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return WedgeDerivation(elementNode, prefix) + +def processElementNode(elementNode): + "Process the xml element." + lineation.processElementNodeByFunction(elementNode, getManipulatedPaths) + + +class WedgeDerivation: + "Class to hold wedge variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.center = evaluate.getVector3ByPrefix(Vector3(), elementNode, prefix + 'center') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_bottom.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_bottom.py new file mode 100644 index 0000000..7e10912 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_bottom.py @@ -0,0 +1,104 @@ +""" +Boolean geometry bottom. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_geometry +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 400 + + +def bottomElementNode(derivation, target): + "Bottom target." + xmlObject = target.xmlObject + if xmlObject == None: + print('Warning, bottomTarget in bottom could not get xmlObject for:') + print(target) + print(derivation.elementNode) + return + targetMatrix = matrix.getBranchMatrixSetElementNode(target) + lift = derivation.altitude + transformedPaths = xmlObject.getTransformedPaths() + if len(transformedPaths) > 0: + lift += derivation.getAdditionalPathLift() - euclidean.getBottomByPaths(transformedPaths) + else: + lift -= boolean_geometry.getMinimumZ(xmlObject) + targetMatrix.tetragrid = matrix.getIdentityTetragrid(targetMatrix.tetragrid) + targetMatrix.tetragrid[2][3] += lift + matrix.setElementNodeDictionaryMatrix(target, targetMatrix) + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get bottomed geometryOutput.' + derivation = BottomDerivation(elementNode, prefix) + copyShallow = elementNode.getCopyShallow() + solid.processElementNodeByGeometry(copyShallow, geometryOutput) + targetMatrix = matrix.getBranchMatrixSetElementNode(elementNode) + matrix.setElementNodeDictionaryMatrix(copyShallow, targetMatrix) + minimumZ = boolean_geometry.getMinimumZ(copyShallow.xmlObject) + copyShallow.parentNode.xmlObject.archivableObjects.remove(copyShallow.xmlObject) + lift = derivation.altitude - minimumZ + vertexes = matrix.getVertexes(geometryOutput) + for vertex in vertexes: + vertex.z += lift + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + 'Get flipped paths.' + if len(loop) < 1: + return [[]] + derivation = BottomDerivation(elementNode, prefix) + targetMatrix = matrix.getBranchMatrixSetElementNode(elementNode) + transformedLoop = matrix.getTransformedVector3s(matrix.getIdentityTetragrid(targetMatrix.tetragrid), loop) + lift = derivation.altitude + derivation.getAdditionalPathLift() - euclidean.getBottomByPath(transformedLoop) + for point in loop: + point.z += lift + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return BottomDerivation(elementNode, '') + +def processElementNode(elementNode): + "Process the xml element." + processElementNodeByDerivation(None, elementNode) + +def processElementNodeByDerivation(derivation, elementNode): + 'Process the xml element by derivation.' + if derivation == None: + derivation = BottomDerivation(elementNode, '') + targets = evaluate.getElementNodesByKey(elementNode, 'target') + if len(targets) < 1: + print('Warning, processElementNode in bottom could not get targets for:') + print(elementNode) + return + for target in targets: + bottomElementNode(derivation, target) + + +class BottomDerivation: + "Class to hold bottom variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.altitude = evaluate.getEvaluatedFloat(0.0, elementNode, prefix + 'altitude') + self.elementNode = elementNode + self.liftPath = evaluate.getEvaluatedBoolean(True, elementNode, prefix + 'liftPath') + + def getAdditionalPathLift(self): + "Get path lift." + return 0.5 * setting.getLayerHeight(self.elementNode) * float(self.liftPath) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_inset.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_inset.py new file mode 100644 index 0000000..6e9e2de --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_inset.py @@ -0,0 +1,85 @@ +""" +Create inset. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import intercircle +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 80 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get inset geometryOutput.' + derivation = InsetDerivation(elementNode, prefix) + if derivation.radius == 0.0: + return geometryOutput + halfLayerHeight = 0.5 * derivation.radius + importRadius = 0.5 * derivation.radius * setting.getImportCoarseness(elementNode) + loopLayers = solid.getLoopLayersSetCopy(elementNode, geometryOutput, importRadius, derivation.radius) + triangleAltitude = math.sqrt(0.75) * derivation.radius + loops = [] + vertexes = [] + for loopLayerIndex in xrange(1, len(loopLayers), 2): + loopLayer = loopLayers[loopLayerIndex] + loopLayer.loops[0] = intercircle.getLargestInsetLoopFromLoop(loopLayer.loops[0], triangleAltitude) + for loopLayerIndex in xrange(0, len(loopLayers), 2): + loopLayer = loopLayers[loopLayerIndex] + loopLists = [[solid.getLoopOrEmpty(loopLayerIndex - 2, loopLayers)]] + loopLists.append([solid.getLoopOrEmpty(loopLayerIndex - 1, loopLayers)]) + loopLists.append([intercircle.getLargestInsetLoopFromLoop(loopLayer.loops[0], derivation.radius)]) + if evaluate.getEvaluatedBoolean(True, elementNode, prefix + 'insetTop'): + loopLists.append([solid.getLoopOrEmpty(loopLayerIndex + 1, loopLayers)]) + loopLists.append([solid.getLoopOrEmpty(loopLayerIndex + 2, loopLayers)]) + largestLoop = euclidean.getLargestLoop(boolean_solid.getLoopsIntersection(importRadius, loopLists)) + triangle_mesh.addVector3Loop(largestLoop, loops, vertexes, loopLayer.z) + if evaluate.getEvaluatedBoolean(False, elementNode, prefix + 'addExtraTopLayer') and len(loops) > 0: + topLoop = loops[-1] + vector3Loop = [] + loops.append(vector3Loop) + z = topLoop[0].z + derivation.radius + for point in topLoop: + vector3Index = Vector3Index(len(vertexes), point.x, point.y, z) + vector3Loop.append(vector3Index) + vertexes.append(vector3Index) + return triangle_mesh.getMeldedPillarOutput(loops) + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get inset path." + derivation = InsetDerivation(elementNode, prefix) + return intercircle.getInsetLoopsFromVector3Loop(loop, derivation.radius) + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return OutsetDerivation(elementNode, prefix) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunctionPair(elementNode, getManipulatedGeometryOutput, getManipulatedPaths) + + +class InsetDerivation: + "Class to hold inset variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.radius = evaluate.getEvaluatedFloat(2.0 * setting.getEdgeWidth(elementNode), elementNode, prefix + 'radius') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_outset.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_outset.py new file mode 100644 index 0000000..6217214 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/_outset.py @@ -0,0 +1,78 @@ +""" +Create inset. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities.evaluate_elements import setting +from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import intercircle +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 80 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get outset geometryOutput.' + derivation = OutsetDerivation(elementNode, prefix) + if derivation.radius == 0.0: + return geometryOutput + halfLayerHeight = 0.5 * derivation.radius + importRadius = 0.5 * derivation.radius * setting.getImportCoarseness(elementNode) + loopLayers = solid.getLoopLayersSetCopy(elementNode, geometryOutput, importRadius, derivation.radius) + if len(loopLayers) == 0: + return triangle_mesh.getMeldedPillarOutput([]) + triangleAltitude = math.sqrt(0.75) * derivation.radius + loops = [] + vertexes = [] + for loopLayerIndex in xrange(1, len(loopLayers), 2): + loopLayer = loopLayers[loopLayerIndex] + loopLayer.loops[0] = intercircle.getLargestInsetLoopFromLoop(loopLayer.loops[0], triangleAltitude) + z = loopLayers[0].z - derivation.radius + for loopIndex in xrange(-2, len(loopLayers) + 2, 2): + loopLists = [[solid.getLoopOrEmpty(loopIndex - 2, loopLayers)]] + loopLists.append([solid.getLoopOrEmpty(loopIndex - 1, loopLayers)]) + loopLists.append([intercircle.getLargestInsetLoopFromLoop(solid.getLoopOrEmpty(loopIndex, loopLayers), -derivation.radius)]) + loopLists.append([solid.getLoopOrEmpty(loopIndex + 1, loopLayers)]) + loopLists.append([solid.getLoopOrEmpty(loopIndex + 2, loopLayers)]) + largestLoop = euclidean.getLargestLoop(boolean_solid.getLoopsUnion(importRadius, loopLists)) + triangle_mesh.addVector3Loop(largestLoop, loops, vertexes, z) + z += derivation.radius + return triangle_mesh.getMeldedPillarOutput(loops) + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get outset path." + derivation = OutsetDerivation(elementNode, prefix) + return intercircle.getInsetLoopsFromVector3Loop(loop, -derivation.radius) + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return OutsetDerivation(elementNode, prefix) + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunctionPair(elementNode, getManipulatedGeometryOutput, getManipulatedPaths) + + +class OutsetDerivation: + "Class to hold outset variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.radius = evaluate.getEvaluatedFloat(2.0 * setting.getEdgeWidth(elementNode), elementNode, prefix + 'radius') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/equation.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/equation.py new file mode 100644 index 0000000..76b610b --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/equation.py @@ -0,0 +1,116 @@ +""" +Equation for vertexes. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = -100 + + +def equate(point, returnValue): + "Get equation for rectangular." + point.setToVector3(evaluate.getVector3ByDictionaryListValue(returnValue, point)) + +def equatePoints(elementNode, points, prefix, revolutions): + "Equate the points." + derivation = EquationDerivation(elementNode, prefix) + for equationResult in derivation.equationResults: + for point in points: + returnValue = equationResult.getReturnValue(point, revolutions) + if returnValue == None: + print('Warning, returnValue in alterVertexesByEquation in equation is None for:') + print(point) + print(elementNode) + else: + equationResult.equationFunction(point, returnValue) + +def equateX(point, returnValue): + "Get equation for rectangular x." + point.x = returnValue + +def equateY(point, returnValue): + "Get equation for rectangular y." + point.y = returnValue + +def equateZ(point, returnValue): + "Get equation for rectangular z." + point.z = returnValue + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + "Get equated geometryOutput." + equatePoints(elementNode, matrix.getVertexes(geometryOutput), prefix, None) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + "Get equated paths." + equatePoints(elementNode, loop, prefix, 0.0) + return [loop] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return EquationDerivation(elementNode, prefix) + + +class EquationDerivation: + "Class to hold equation variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.equationResults = [] + self.addEquationResult(elementNode, equate, prefix) + self.addEquationResult(elementNode, equateX, prefix) + self.addEquationResult(elementNode, equateY, prefix) + self.addEquationResult(elementNode, equateZ, prefix) + + def addEquationResult(self, elementNode, equationFunction, prefix): + 'Add equation result to equationResults.' + prefixedEquationName = prefix + equationFunction.__name__[ len('equate') : ].replace('Dot', '.').lower() + if prefixedEquationName in elementNode.attributes: + self.equationResults.append(EquationResult(elementNode, equationFunction, prefixedEquationName)) + + +class EquationResult: + "Class to get equation results." + def __init__(self, elementNode, equationFunction, key): + "Initialize." + self.distance = 0.0 + elementNode.xmlObject = evaluate.getEvaluatorSplitWords(elementNode.attributes[key]) + self.equationFunction = equationFunction + self.function = evaluate.Function(elementNode) + self.points = [] + + def getReturnValue(self, point, revolutions): + "Get return value." + if self.function == None: + return point + self.function.localDictionary['azimuth'] = math.degrees(math.atan2(point.y, point.x)) + if len(self.points) > 0: + self.distance += abs(point - self.points[-1]) + self.function.localDictionary['distance'] = self.distance + self.function.localDictionary['radius'] = abs(point.dropAxis()) + if revolutions != None: + if len( self.points ) > 0: + revolutions += 0.5 / math.pi * euclidean.getAngleAroundZAxisDifference(point, self.points[-1]) + self.function.localDictionary['revolutions'] = revolutions + self.function.localDictionary['vertex'] = point + self.function.localDictionary['vertexes'] = self.points + self.function.localDictionary['vertexindex'] = len(self.points) + self.function.localDictionary['x'] = point.x + self.function.localDictionary['y'] = point.y + self.function.localDictionary['z'] = point.z + self.points.append(point) + return self.function.getReturnValueWithoutDeletion() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/flip.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/flip.py new file mode 100644 index 0000000..3212552 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/flip.py @@ -0,0 +1,92 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.vector3 import Vector3 + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 200 + + +# http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=269576 +# http://www.opengl.org/resources/code/samples/sig99/advanced99/notes/node159.html +# m.a00 = -2 * norm.x * norm.x + 1; +# m.a10 = -2 * norm.y * norm.x; +# m.a20 = -2 * norm.z * norm.x; +# m.a30 = 0; + +# m.a01 = -2 * norm.x * norm.y; +# m.a11 = -2 * norm.y * norm.y + 1; +# m.a21 = -2 * norm.z * norm.y; +# m.a31 = 0; + +# m.a02 = -2 * norm.x * norm.z; +# m.a12 = -2 * norm.y * norm.z; +# m.a22 = -2 * norm.z * norm.z + 1; +# m.a32 = 0; + +# m.a03 = -2 * norm.x * d; +# m.a13 = -2 * norm.y * d; +# m.a23 = -2 * norm.z * d; +# m.a33 = 1; + +# normal = unit_vector(normal[:3]) +# M = numpy.identity(4) +# M[:3, :3] -= 2.0 * numpy.outer(normal, normal) +# M[:3, 3] = (2.0 * numpy.dot(point[:3], normal)) * normal +# return M +def flipPoints(elementNode, points, prefix): + 'Flip the points.' + derivation = FlipDerivation(elementNode, prefix) + for point in points: + point.setToVector3(point - 2.0 * derivation.axis.dot(point - derivation.origin) * derivation.axis) + +def getFlippedLoop(elementNode, loop, prefix): + 'Get flipped loop.' + flipPoints(elementNode, loop, prefix) + if getShouldReverse(elementNode, prefix): + loop.reverse() + return loop + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get equated geometryOutput.' + flipPoints(elementNode, matrix.getVertexes(geometryOutput), prefix) + return geometryOutput + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + 'Get flipped paths.' + return [getFlippedLoop(elementNode, loop, prefix)] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return FlipDerivation(elementNode, prefix) + +def getShouldReverse(elementNode, prefix): + 'Determine if the loop should be reversed.' + return evaluate.getEvaluatedBoolean(True, elementNode, prefix + 'reverse') + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunctionPair(elementNode, getManipulatedGeometryOutput, getManipulatedPaths) + + +class FlipDerivation: + "Class to hold flip variables." + def __init__(self, elementNode, prefix): + 'Set defaults.' + self.origin = evaluate.getVector3ByPrefix(Vector3(), elementNode, prefix + 'origin') + self.axis = evaluate.getVector3ByPrefix(Vector3(1.0, 0.0, 0.0), elementNode, prefix + 'axis').getNormalized() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/mirror.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/mirror.py new file mode 100644 index 0000000..4b997d7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/manipulation_shapes/mirror.py @@ -0,0 +1,49 @@ +""" +Add material to support overhang or remove material at the overhang angle. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_tools import face +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.manipulation_shapes import flip +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalExecutionOrder = 200 + + +def getManipulatedGeometryOutput(elementNode, geometryOutput, prefix): + 'Get equated geometryOutput.' + flippedGeometryOutput = triangle_mesh.getGeometryOutputCopy(geometryOutput) + flip.flipPoints(elementNode, matrix.getVertexes(flippedGeometryOutput), prefix) + if flip.getShouldReverse(elementNode, prefix): + flippedFaces = face.getFaces(flippedGeometryOutput) + for flippedFace in flippedFaces: + flippedFace.vertexIndexes.reverse() + return {'union' : {'shapes' : [flippedGeometryOutput, geometryOutput]}} + +def getManipulatedPaths(close, elementNode, loop, prefix, sideLength): + 'Get flipped paths.' + return [loop + flip.getFlippedLoop(elementNode, euclidean.getPathCopy(loop), prefix)] + +def getNewDerivation(elementNode, prefix, sideLength): + 'Get new derivation.' + return evaluate.EmptyObject() + +def processElementNode(elementNode): + 'Process the xml element.' + solid.processElementNodeByFunctionPair(elementNode, getManipulatedGeometryOutput, getManipulatedPaths) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/__init__.py new file mode 100644 index 0000000..cefa3e7 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/__init__.py @@ -0,0 +1,12 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cube.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cube.py new file mode 100644 index 0000000..088904f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cube.py @@ -0,0 +1,78 @@ +""" +Boolean geometry cube. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addCube(elementNode, faces, inradius, vertexes): + 'Add cube by inradius.' + square = [ + complex(-inradius.x, -inradius.y), + complex(inradius.x, -inradius.y), + complex(inradius.x, inradius.y), + complex(-inradius.x, inradius.y)] + bottomTopSquare = triangle_mesh.getAddIndexedLoops(square, vertexes, [-inradius.z, inradius.z]) + triangle_mesh.addPillarByLoops(faces, bottomTopSquare) + +def getGeometryOutput(elementNode, inradius): + 'Get cube triangle mesh by inradius.' + faces = [] + vertexes = [] + addCube(elementNode, faces, inradius, vertexes) + return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}} + +def getNewDerivation(elementNode): + 'Get new derivation.' + return CubeDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(Cube, elementNode) + + +class Cube(triangle_mesh.TriangleMesh): + 'A cube object.' + def addXMLSection(self, depth, output): + 'Add the xml section for this object.' + pass + + def createShape(self): + 'Create the shape.' + addCube(self.elementNode, self.faces, self.inradius, self.vertexes) + + def setToElementNode(self, elementNode): + 'Set to elementNode.' + attributes = elementNode.attributes + self.elementNode = elementNode + self.inradius = CubeDerivation(elementNode).inradius + attributes['inradius.x'] = self.inradius.x + attributes['inradius.y'] = self.inradius.y + attributes['inradius.z'] = self.inradius.z + if 'inradius' in attributes: + del attributes['inradius'] + self.createShape() + solid.processArchiveRemoveSolid(elementNode, self.getGeometryOutput()) + + +class CubeDerivation: + "Class to hold cube variables." + def __init__(self, elementNode): + 'Set defaults.' + self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(1.0, 1.0, 1.0)) + self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cylinder.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cylinder.py new file mode 100644 index 0000000..aa2c46e --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/cylinder.py @@ -0,0 +1,111 @@ +""" +Boolean geometry cylinder. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import lineation +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import cube +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addCylinder(faces, inradius, sides, topOverBottom, vertexes): + 'Add cylinder by inradius.' + polygonBottom = euclidean.getComplexPolygonByComplexRadius(complex(inradius.x, inradius.y), sides) + polygonTop = polygonBottom + if topOverBottom <= 0.0: + polygonTop = [complex()] + elif topOverBottom != 1.0: + polygonTop = euclidean.getComplexPathByMultiplier(topOverBottom, polygonTop) + bottomTopPolygon = [ + triangle_mesh.getAddIndexedLoop(polygonBottom, vertexes, -inradius.z), + triangle_mesh.getAddIndexedLoop(polygonTop, vertexes, inradius.z)] + triangle_mesh.addPillarByLoops(faces, bottomTopPolygon) + +def addCylinderOutputByEndStart(endZ, inradiusComplex, outputs, sides, start, topOverBottom=1.0): + 'Add cylinder triangle mesh by endZ, inradius and start.' + inradius = Vector3(inradiusComplex.real, inradiusComplex.imag, 0.5 * abs(endZ - start.z)) + cylinderOutput = getGeometryOutput(inradius, sides, topOverBottom) + vertexes = matrix.getVertexes(cylinderOutput) + if endZ < start.z: + for vertex in vertexes: + vertex.z = -vertex.z + translation = Vector3(start.x, start.y, inradius.z + min(start.z, endZ)) + euclidean.translateVector3Path(vertexes, translation) + outputs.append(cylinderOutput) + +def getGeometryOutput(inradius, sides, topOverBottom): + 'Get cylinder triangle mesh by inradius.' + faces = [] + vertexes = [] + addCylinder(faces, inradius, sides, topOverBottom, vertexes) + return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}} + +def getNewDerivation(elementNode): + 'Get new derivation.' + return CylinderDerivation(elementNode) + +def getTopOverBottom(angle, endZ, inradiusComplex, startZ): + 'Get topOverBottom by angle in radians, endZ, inradius and start.' + return max(1.0 - abs(endZ - startZ) * math.tan(angle) / lineation.getRadiusAverage(inradiusComplex), 0.0) + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(Cylinder, elementNode) + + +class Cylinder( cube.Cube ): + 'A cylinder object.' + def __init__(self): + 'Add empty lists.' + cube.Cube.__init__(self) + + def createShape(self): + 'Create the shape.' + sides = evaluate.getSidesMinimumThreeBasedOnPrecision(self.elementNode, max(self.inradius.x, self.inradius.y)) + if self.elementNode.getCascadeBoolean(False, 'radiusAreal'): + radiusArealizedMultiplier = euclidean.getRadiusArealizedMultiplier(sides) + self.inradius.x *= radiusArealizedMultiplier + self.inradius.y *= radiusArealizedMultiplier + addCylinder(self.faces, self.inradius, sides, self.topOverBottom, self.vertexes) + + def setToElementNode(self, elementNode): + 'Set to elementNode.' + attributes = elementNode.attributes + self.elementNode = elementNode + derivation = CylinderDerivation(elementNode) + self.inradius = derivation.inradius + self.topOverBottom = derivation.topOverBottom + if 'inradius' in attributes: + del attributes['inradius'] + attributes['height'] = self.inradius.z + self.inradius.z + attributes['radius.x'] = self.inradius.x + attributes['radius.y'] = self.inradius.y + attributes['topOverBottom'] = self.topOverBottom + self.createShape() + solid.processArchiveRemoveSolid(elementNode, self.getGeometryOutput()) + + +class CylinderDerivation: + "Class to hold cylinder variables." + def __init__(self, elementNode): + 'Set defaults.' + self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius', 'radius'], Vector3(1.0, 1.0, 1.0)) + self.inradius = evaluate.getVector3ByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], self.inradius) + self.inradius.z = 0.5 * evaluate.getEvaluatedFloat(self.inradius.z + self.inradius.z, elementNode, 'height') + self.topOverBottom = evaluate.getEvaluatedFloat(1.0, elementNode, 'topOverBottom') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/difference.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/difference.py new file mode 100644 index 0000000..447b697 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/difference.py @@ -0,0 +1,43 @@ +""" +Boolean geometry difference of solids. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import group + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def convertElementNode(elementNode, geometryOutput): + "Convert the xml element to a difference xml element." + group.convertContainerElementNode(elementNode, geometryOutput, Difference()) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return evaluate.EmptyObject(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + evaluate.processArchivable(Difference, elementNode) + + +class Difference( boolean_solid.BooleanSolid ): + "A difference object." + def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList): + "Get loops from visible object loops list." + return self.getDifference(importRadius, visibleObjectLoopsList) + + def getXMLLocalName(self): + "Get xml class name." + return self.__class__.__name__.lower() diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/group.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/group.py new file mode 100644 index 0000000..37de4ed --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/group.py @@ -0,0 +1,82 @@ +""" +Boolean geometry group of solids. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import dictionary +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import euclidean + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def convertContainerElementNode(elementNode, geometryOutput, xmlObject): + "Convert the xml element to a group xml element." + elementNode.linkObject(xmlObject) + matrix.getBranchMatrixSetElementNode(elementNode) + elementNode.getXMLProcessor().createChildNodes(geometryOutput['shapes'], elementNode) + +def convertElementNode(elementNode, geometryOutput): + "Convert the xml element to a group xml element." + convertContainerElementNode(elementNode, geometryOutput, Group()) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return evaluate.EmptyObject(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + evaluate.processArchivable(Group, elementNode) + + +class Group(dictionary.Dictionary): + "A group." + def __init__(self): + "Add empty lists." + dictionary.Dictionary.__init__(self) + self.matrix4X4 = matrix.Matrix() + + def addXMLInnerSection(self, depth, output): + "Add xml inner section for this object." + if self.matrix4X4 != None: + self.matrix4X4.addXML(depth, output) + self.addXMLSection(depth, output) + + def addXMLSection(self, depth, output): + "Add the xml section for this object." + pass + + def getLoops(self, importRadius, z): + "Get loops sliced through shape." + visibleObjects = evaluate.getVisibleObjects(self.archivableObjects) + loops = [] + for visibleObject in visibleObjects: + loops += visibleObject.getLoops(importRadius, z) + return loops + + def getMatrix4X4(self): + "Get the matrix4X4." + return self.matrix4X4 + + def getMatrixChainTetragrid(self): + "Get the matrix chain tetragrid." + return matrix.getTetragridTimesOther(self.elementNode.parentNode.xmlObject.getMatrixChainTetragrid(), self.matrix4X4.tetragrid) + + def getVisible(self): + "Get visible." + return euclidean.getBooleanFromDictionary(True, self.getAttributes(), 'visible') + + def setToElementNode(self, elementNode): + 'Set to elementNode.' + self.elementNode = elementNode + elementNode.parentNode.xmlObject.archivableObjects.append(self) + matrix.getBranchMatrixSetElementNode(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/intersection.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/intersection.py new file mode 100644 index 0000000..04aa15a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/intersection.py @@ -0,0 +1,39 @@ +""" +Boolean geometry intersection of solids. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import difference +from fabmetheus_utilities.geometry.solids import group + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def convertElementNode(elementNode, geometryOutput): + "Convert the xml element to an intersection xml element." + group.convertContainerElementNode(elementNode, geometryOutput, Intersection()) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return evaluate.EmptyObject(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + evaluate.processArchivable(Intersection, elementNode) + + +class Intersection(difference.Difference): + "An intersection object." + def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList): + "Get loops from visible object loops list." + return self.getIntersection(importRadius, visibleObjectLoopsList) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/sphere.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/sphere.py new file mode 100644 index 0000000..9104056 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/sphere.py @@ -0,0 +1,83 @@ +""" +Boolean geometry sphere. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.creation import solid +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import cube +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addSphere(elementNode, faces, radius, vertexes): + 'Add sphere by radius.' + bottom = -radius.z + sides = evaluate.getSidesMinimumThreeBasedOnPrecision(elementNode, max(radius.x, radius.y, radius.z)) + sphereSlices = max(sides / 2, 2) + equator = euclidean.getComplexPolygonByComplexRadius(complex(radius.x, radius.y), sides) + polygons = [triangle_mesh.getAddIndexedLoop([complex()], vertexes, bottom)] + zIncrement = (radius.z + radius.z) / float(sphereSlices) + z = bottom + for sphereSlice in xrange(1, sphereSlices): + z += zIncrement + zPortion = abs(z) / radius.z + multipliedPath = euclidean.getComplexPathByMultiplier(math.sqrt(1.0 - zPortion * zPortion), equator) + polygons.append(triangle_mesh.getAddIndexedLoop(multipliedPath, vertexes, z)) + polygons.append(triangle_mesh.getAddIndexedLoop([complex()], vertexes, radius.z)) + triangle_mesh.addPillarByLoops(faces, polygons) + +def getGeometryOutput(elementNode, radius): + 'Get triangle mesh from attribute dictionary.' + faces = [] + vertexes = [] + addSphere(elementNode, faces, radius, vertexes) + return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}} + +def getNewDerivation(elementNode): + 'Get new derivation.' + return SphereDerivation(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(Sphere, elementNode) + + +class Sphere(cube.Cube): + 'A sphere object.' + def createShape(self): + 'Create the shape.' + addSphere(self.elementNode, self.faces, self.radius, self.vertexes) + + def setToElementNode(self, elementNode): + 'Set to elementNode.' + attributes = elementNode.attributes + self.elementNode = elementNode + self.radius = SphereDerivation(elementNode).radius + if 'radius' in attributes: + del attributes['radius'] + attributes['radius.x'] = self.radius.x + attributes['radius.y'] = self.radius.y + attributes['radius.z'] = self.radius.z + self.createShape() + solid.processArchiveRemoveSolid(elementNode, self.getGeometryOutput()) + + +class SphereDerivation: + "Class to hold sphere variables." + def __init__(self, elementNode): + 'Set defaults.' + self.radius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'radius'], Vector3(1.0, 1.0, 1.0)) + self.radius = evaluate.getVector3ByMultiplierPrefixes(elementNode, 2.0, ['diameter', 'size'], self.radius) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/triangle_mesh.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/triangle_mesh.py new file mode 100644 index 0000000..e624ee8 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/triangle_mesh.py @@ -0,0 +1,939 @@ +""" +Triangle Mesh holds the faces and edges of a triangular mesh. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_tools import face +from fabmetheus_utilities.geometry.geometry_tools import dictionary +from fabmetheus_utilities.geometry.geometry_tools import vertex +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities.geometry.solids import group +from fabmetheus_utilities import xml_simple_writer +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.vector3index import Vector3Index +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addEdgePair( edgePairTable, edges, faceEdgeIndex, remainingEdgeIndex, remainingEdgeTable ): + 'Add edge pair to the edge pair table.' + if faceEdgeIndex == remainingEdgeIndex: + return + if not faceEdgeIndex in remainingEdgeTable: + return + edgePair = EdgePair().getFromIndexesEdges( [ remainingEdgeIndex, faceEdgeIndex ], edges ) + edgePairTable[ str( edgePair ) ] = edgePair + +def addFacesByConcaveLoop(faces, indexedLoop): + 'Add faces from a polygon which is concave.' + if len(indexedLoop) < 3: + return + remainingLoop = indexedLoop[:] + while len(remainingLoop) > 2: + remainingLoop = getRemainingLoopAddFace(faces, remainingLoop) + +def addFacesByConvex(faces, indexedLoop): + 'Add faces from a convex polygon.' + if len(indexedLoop) < 3: + return + indexBegin = indexedLoop[0].index + for indexedPointIndex in xrange(1, len(indexedLoop) - 1): + indexCenter = indexedLoop[indexedPointIndex].index + indexEnd = indexedLoop[(indexedPointIndex + 1) % len(indexedLoop) ].index + if indexBegin != indexCenter and indexCenter != indexEnd and indexEnd != indexBegin: + faceFromConvex = face.Face() + faceFromConvex.index = len(faces) + faceFromConvex.vertexIndexes.append(indexBegin) + faceFromConvex.vertexIndexes.append(indexCenter) + faceFromConvex.vertexIndexes.append(indexEnd) + faces.append(faceFromConvex) + +def addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop): + 'Add faces from loops.' + if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0: + return + for indexedPointIndex in xrange(max(len(indexedLoopBottom), len(indexedLoopTop))): + indexedConvex = [] + if len(indexedLoopBottom) > 1: + indexedConvex.append(indexedLoopBottom[indexedPointIndex]) + indexedConvex.append(indexedLoopBottom[(indexedPointIndex + 1) % len(indexedLoopBottom)]) + else: + indexedConvex.append(indexedLoopBottom[0]) + if len(indexedLoopTop) > 1: + indexedConvex.append(indexedLoopTop[(indexedPointIndex + 1) % len(indexedLoopTop)]) + indexedConvex.append(indexedLoopTop[indexedPointIndex]) + else: + indexedConvex.append(indexedLoopTop[0]) + addFacesByConvex(faces, indexedConvex) + +def addFacesByConvexLoops(faces, indexedLoops): + 'Add faces from loops.' + if len(indexedLoops) < 2: + return + for indexedLoopsIndex in xrange(len(indexedLoops) - 2): + addFacesByConvexBottomTopLoop(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1]) + indexedLoopBottom = indexedLoops[-2] + indexedLoopTop = indexedLoops[-1] + if len(indexedLoopTop) < 1: + indexedLoopTop = indexedLoops[0] + addFacesByConvexBottomTopLoop(faces, indexedLoopBottom, indexedLoopTop) + +def addFacesByConvexReversed(faces, indexedLoop): + 'Add faces from a reversed convex polygon.' + addFacesByConvex(faces, indexedLoop[: : -1]) + +def addFacesByGrid(faces, grid): + 'Add faces from grid.' + cellTopLoops = getIndexedCellLoopsFromIndexedGrid(grid) + for cellTopLoop in cellTopLoops: + addFacesByConvex(faces, cellTopLoop) + +def addFacesByLoop(faces, indexedLoop): + 'Add faces from a polygon which may be concave.' + if len(indexedLoop) < 3: + return + lastNormal = None + for pointIndex, point in enumerate(indexedLoop): + center = indexedLoop[(pointIndex + 1) % len(indexedLoop)] + end = indexedLoop[(pointIndex + 2) % len(indexedLoop)] + normal = euclidean.getNormalWeighted(point, center, end) + if abs(normal) > 0.0: + if lastNormal != None: + if lastNormal.dot(normal) < 0.0: + addFacesByConcaveLoop(faces, indexedLoop) + return + lastNormal = normal +# totalNormal = Vector3() +# for pointIndex, point in enumerate(indexedLoop): +# center = indexedLoop[(pointIndex + 1) % len(indexedLoop)] +# end = indexedLoop[(pointIndex + 2) % len(indexedLoop)] +# totalNormal += euclidean.getNormalWeighted(point, center, end) +# totalNormal.normalize() + addFacesByConvex(faces, indexedLoop) + +def addFacesByLoopReversed(faces, indexedLoop): + 'Add faces from a reversed convex polygon.' + addFacesByLoop(faces, indexedLoop[: : -1]) + +def addFacesByMeldedConvexLoops(faces, indexedLoops): + 'Add faces from melded loops.' + if len(indexedLoops) < 2: + return + for indexedLoopsIndex in xrange(len(indexedLoops) - 2): + FaceGenerator(faces, indexedLoops[indexedLoopsIndex], indexedLoops[indexedLoopsIndex + 1]) + indexedLoopBottom = indexedLoops[-2] + indexedLoopTop = indexedLoops[-1] + if len(indexedLoopTop) < 1: + indexedLoopTop = indexedLoops[0] + FaceGenerator(faces, indexedLoopBottom, indexedLoopTop) + +def addLoopToPointTable(loop, pointTable): + 'Add the points in the loop to the point table.' + for point in loop: + pointTable[point] = None + +def addMeldedPillarByLoops(faces, indexedLoops): + 'Add melded pillar by loops which may be concave.' + if len(indexedLoops) < 1: + return + if len(indexedLoops[-1]) < 1: + addFacesByMeldedConvexLoops(faces, indexedLoops) + return + addFacesByLoopReversed(faces, indexedLoops[0]) + addFacesByMeldedConvexLoops(faces, indexedLoops) + addFacesByLoop(faces, indexedLoops[-1]) + +def addPillarByLoops(faces, indexedLoops): + 'Add pillar by loops which may be concave.' + if len(indexedLoops) < 1: + return + if len(indexedLoops[-1]) < 1: + addFacesByConvexLoops(faces, indexedLoops) + return + addFacesByLoopReversed(faces, indexedLoops[0]) + addFacesByConvexLoops(faces, indexedLoops) + addFacesByLoop(faces, indexedLoops[-1]) + +def addPillarFromConvexLoopsGrids(faces, indexedGrids, indexedLoops): + 'Add pillar from convex loops and grids.' + cellBottomLoops = getIndexedCellLoopsFromIndexedGrid(indexedGrids[0]) + for cellBottomLoop in cellBottomLoops: + addFacesByConvexReversed(faces, cellBottomLoop) + addFacesByConvexLoops(faces, indexedLoops) + addFacesByGrid(faces, indexedGrids[-1]) + +def addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops): + 'Add pillar from convex loops and grid top.' + addFacesByLoopReversed(faces, indexedLoops[0]) + addFacesByConvexLoops(faces, indexedLoops) + addFacesByGrid(faces, indexedGridTop) + +def addPointsAtZ(edgePair, points, radius, vertexes, z): + 'Add point complexes on the segment between the edge intersections with z.' + carveIntersectionFirst = getCarveIntersectionFromEdge(edgePair.edges[0], vertexes, z) + carveIntersectionSecond = getCarveIntersectionFromEdge(edgePair.edges[1], vertexes, z) + # threshold radius above 0.8 can create extra holes on Screw Holder, 0.7 should be safe for everything + intercircle.addPointsFromSegment(carveIntersectionFirst, carveIntersectionSecond, points, radius, 0.7) + +def addSymmetricXPath(outputs, path, x): + 'Add x path output to outputs.' + vertexes = [] + loops = [getSymmetricXLoop(path, vertexes, -x), getSymmetricXLoop(path, vertexes, x)] + outputs.append(getPillarOutput(loops)) + +def addSymmetricXPaths(outputs, paths, x): + 'Add x paths outputs to outputs.' + for path in paths: + addSymmetricXPath(outputs, path, x) + +def addSymmetricYPath(outputs, path, y): + 'Add y path output to outputs.' + vertexes = [] + loops = [getSymmetricYLoop(path, vertexes, -y), getSymmetricYLoop(path, vertexes, y)] + outputs.append(getPillarOutput(loops)) + +def addSymmetricYPaths(outputs, paths, y): + 'Add y paths outputs to outputs.' + for path in paths: + addSymmetricYPath(outputs, path, y) + +def addVector3Loop(loop, loops, vertexes, z): + 'Add vector3Loop to loops if there is something in it, for inset and outset.' + vector3Loop = [] + for point in loop: + vector3Index = Vector3Index(len(vertexes), point.real, point.imag, z) + vector3Loop.append(vector3Index) + vertexes.append(vector3Index) + if len(vector3Loop) > 0: + loops.append(vector3Loop) + +def addWithLeastLength(importRadius, loops, point): + 'Insert a point into a loop, at the index at which the loop would be shortest.' + close = 1.65 * importRadius # a bit over the experimental minimum additional loop length to restore a right angle + shortestAdditionalLength = close + shortestLoop = None + shortestPointIndex = None + for loop in loops: + if len(loop) > 3: + for pointIndex in xrange(len(loop)): + additionalLoopLength = getAdditionalLoopLength(loop, point, pointIndex) + if additionalLoopLength < shortestAdditionalLength: + if getIsPointCloseInline(close, loop, point, pointIndex): + shortestAdditionalLength = additionalLoopLength + shortestLoop = loop + shortestPointIndex = pointIndex + if shortestPointIndex != None: + shortestLoop.insert( shortestPointIndex, point ) + +def convertElementNode(elementNode, geometryOutput): + 'Convert the xml element to a TriangleMesh xml element.' + elementNode.linkObject(TriangleMesh()) + matrix.getBranchMatrixSetElementNode(elementNode) + vertex.addGeometryList(elementNode, geometryOutput['vertex']) + face.addGeometryList(elementNode, geometryOutput['face']) + elementNode.getXMLProcessor().processChildNodes(elementNode) + +def getAddIndexedGrid( grid, vertexes, z ): + 'Get and add an indexed grid.' + indexedGrid = [] + for row in grid: + indexedRow = [] + indexedGrid.append( indexedRow ) + for pointComplex in row: + vector3index = Vector3Index( len(vertexes), pointComplex.real, pointComplex.imag, z ) + indexedRow.append(vector3index) + vertexes.append(vector3index) + return indexedGrid + +def getAddIndexedLoop(loop, vertexes, z): + 'Get and add an indexed loop.' + indexedLoop = [] + for index in xrange(len(loop)): + pointComplex = loop[index] + vector3index = Vector3Index(len(vertexes), pointComplex.real, pointComplex.imag, z) + indexedLoop.append(vector3index) + vertexes.append(vector3index) + return indexedLoop + +def getAddIndexedLoops( loop, vertexes, zList ): + 'Get and add indexed loops.' + indexedLoops = [] + for z in zList: + indexedLoop = getAddIndexedLoop( loop, vertexes, z ) + indexedLoops.append(indexedLoop) + return indexedLoops + +def getAdditionalLoopLength(loop, point, pointIndex): + 'Get the additional length added by inserting a point into a loop.' + afterPoint = loop[pointIndex] + beforePoint = loop[(pointIndex + len(loop) - 1) % len(loop)] + return abs(point - beforePoint) + abs(point - afterPoint) - abs(afterPoint - beforePoint) + +def getCarveIntersectionFromEdge(edge, vertexes, z): + 'Get the complex where the carve intersects the edge.' + firstVertex = vertexes[ edge.vertexIndexes[0] ] + firstVertexComplex = firstVertex.dropAxis() + secondVertex = vertexes[ edge.vertexIndexes[1] ] + secondVertexComplex = secondVertex.dropAxis() + zMinusFirst = z - firstVertex.z + up = secondVertex.z - firstVertex.z + return zMinusFirst * ( secondVertexComplex - firstVertexComplex ) / up + firstVertexComplex + +def getClosestDistanceIndexToPoint(point, loop): + 'Get the distance squared to the closest point of the loop and index of that point.' + smallestDistance = 987654321987654321.0 + closestDistanceIndex = None + pointComplex = point.dropAxis() + for otherPointIndex, otherPoint in enumerate(loop): + distance = abs(pointComplex - otherPoint.dropAxis()) + if distance < smallestDistance: + smallestDistance = distance + closestDistanceIndex = euclidean.DistanceIndex(distance, otherPointIndex) + return closestDistanceIndex + +def getDescendingAreaLoops(allPoints, corners, importRadius): + 'Get descending area loops which include most of the points.' + loops = intercircle.getCentersFromPoints(allPoints, importRadius) + descendingAreaLoops = [] + sortLoopsInOrderOfArea(True, loops) + pointDictionary = {} + for loop in loops: + if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.3: + intercircle.directLoop(not euclidean.getIsInFilledRegion(descendingAreaLoops, loop[0]), loop) + descendingAreaLoops.append(loop) + addLoopToPointTable(loop, pointDictionary) + descendingAreaLoops = euclidean.getSimplifiedLoops(descendingAreaLoops, importRadius) + return getLoopsWithCorners(corners, importRadius, descendingAreaLoops, pointDictionary) + +def getDescendingAreaOrientedLoops(allPoints, corners, importRadius): + 'Get descending area oriented loops which include most of the points.' + return getOrientedLoops(getDescendingAreaLoops(allPoints, corners, importRadius)) + +def getGeometryOutputByFacesVertexes(faces, vertexes): + 'Get geometry output dictionary by faces and vertexes.' + return {'trianglemesh' : {'vertex' : vertexes, 'face' : faces}} + +def getGeometryOutputCopy(object): + 'Get the geometry output copy.' + objectClass = object.__class__ + if objectClass == dict: + objectCopy = {} + for key in object: + objectCopy[key] = getGeometryOutputCopy(object[key]) + return objectCopy + if objectClass == list: + objectCopy = [] + for value in object: + objectCopy.append(getGeometryOutputCopy(value)) + return objectCopy + if objectClass == face.Face or objectClass == Vector3 or objectClass == Vector3Index: + return object.copy() + return object + +def getIndexedCellLoopsFromIndexedGrid( grid ): + 'Get indexed cell loops from an indexed grid.' + indexedCellLoops = [] + for rowIndex in xrange( len( grid ) - 1 ): + rowBottom = grid[ rowIndex ] + rowTop = grid[ rowIndex + 1 ] + for columnIndex in xrange( len( rowBottom ) - 1 ): + columnIndexEnd = columnIndex + 1 + indexedConvex = [] + indexedConvex.append( rowBottom[ columnIndex ] ) + indexedConvex.append( rowBottom[ columnIndex + 1 ] ) + indexedConvex.append( rowTop[ columnIndex + 1 ] ) + indexedConvex.append( rowTop[ columnIndex ] ) + indexedCellLoops.append( indexedConvex ) + return indexedCellLoops + +def getIndexedLoopFromIndexedGrid( indexedGrid ): + 'Get indexed loop from around the indexed grid.' + indexedLoop = indexedGrid[0][:] + for row in indexedGrid[1 : -1]: + indexedLoop.append( row[-1] ) + indexedLoop += indexedGrid[-1][: : -1] + for row in indexedGrid[ len( indexedGrid ) - 2 : 0 : - 1 ]: + indexedLoop.append( row[0] ) + return indexedLoop + +def getInfillDictionary(arounds, aroundWidth, infillInset, infillWidth, pixelTable, rotatedLoops, testLoops=None): + 'Get combined fill loops which include most of the points.' + slightlyGreaterThanInfillInset = intercircle.globalIntercircleMultiplier * infillInset + allPoints = intercircle.getPointsFromLoops(rotatedLoops, infillInset, 0.7) + centers = intercircle.getCentersFromPoints(allPoints, slightlyGreaterThanInfillInset) + infillDictionary = {} + for center in centers: + insetCenter = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset) + insetPoint = insetCenter[0] + if len(insetCenter) > 2 and intercircle.getIsLarge(insetCenter, infillInset) and euclidean.getIsInFilledRegion(rotatedLoops, insetPoint): + around = euclidean.getSimplifiedLoop(center, infillInset) + euclidean.addLoopToPixelTable(around, pixelTable, aroundWidth) + arounds.append(around) + insetLoop = intercircle.getSimplifiedInsetFromClockwiseLoop(center, infillInset) + euclidean.addXIntersectionsFromLoopForTable(insetLoop, infillDictionary, infillWidth) + if testLoops != None: + testLoops.append(insetLoop) + return infillDictionary + +def getInsetPoint( loop, tinyRadius ): + 'Get the inset vertex.' + pointIndex = getWideAnglePointIndex(loop) + point = loop[ pointIndex % len(loop) ] + afterPoint = loop[(pointIndex + 1) % len(loop)] + beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ] + afterSegmentNormalized = euclidean.getNormalized( afterPoint - point ) + beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point ) + afterClockwise = complex( afterSegmentNormalized.imag, - afterSegmentNormalized.real ) + beforeWiddershins = complex( - beforeSegmentNormalized.imag, beforeSegmentNormalized.real ) + midpoint = afterClockwise + beforeWiddershins + midpointNormalized = midpoint / abs( midpoint ) + return point + midpointNormalized * tinyRadius + +def getIsPathEntirelyOutsideTriangle(begin, center, end, vector3Path): + 'Determine if a path is entirely outside another loop.' + loop = [begin.dropAxis(), center.dropAxis(), end.dropAxis()] + for vector3 in vector3Path: + point = vector3.dropAxis() + if euclidean.isPointInsideLoop(loop, point): + return False + return True + +def getIsPointCloseInline(close, loop, point, pointIndex): + 'Insert a point into a loop, at the index at which the loop would be shortest.' + afterCenterComplex = loop[pointIndex] + if abs(afterCenterComplex - point) > close: + return False + afterEndComplex = loop[(pointIndex + 1) % len(loop)] + if not isInline( point, afterCenterComplex, afterEndComplex ): + return False + beforeCenterComplex = loop[(pointIndex + len(loop) - 1) % len(loop)] + if abs(beforeCenterComplex - point) > close: + return False + beforeEndComplex = loop[(pointIndex + len(loop) - 2) % len(loop)] + return isInline(point, beforeCenterComplex, beforeEndComplex) + +def getLoopsFromCorrectMesh( edges, faces, vertexes, z ): + 'Get loops from a carve of a correct mesh.' + remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z) + remainingValues = remainingEdgeTable.values() + for edge in remainingValues: + if len( edge.faceIndexes ) < 2: + print('This should never happen, there is a hole in the triangle mesh, each edge should have two faces.') + print(edge) + print('Something will still be printed, but there is no guarantee that it will be the correct shape.' ) + print('Once the gcode is saved, you should check over the layer with a z of:') + print(z) + return [] + loops = [] + while isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ): + pass + if euclidean.isLoopListIntersecting(loops): + print('Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.') + print('Something will still be printed, but there is no guarantee that it will be the correct shape.') + print('Once the gcode is saved, you should check over the layer with a z of:') + print(z) + return [] + return loops +# untouchables = [] +# for boundingLoop in boundingLoops: +# if not boundingLoop.isIntersectingList( untouchables ): +# untouchables.append( boundingLoop ) +# if len( untouchables ) < len( boundingLoops ): +# print('This should never happen, the carve layer intersects itself. Something will still be printed, but there is no guarantee that it will be the correct shape.') +# print('Once the gcode is saved, you should check over the layer with a z of:') +# print(z) +# remainingLoops = [] +# for untouchable in untouchables: +# remainingLoops.append( untouchable.loop ) +# return remainingLoops + +def getLoopsFromUnprovenMesh(edges, faces, importRadius, vertexes, z): + 'Get loops from a carve of an unproven mesh.' + edgePairTable = {} + corners = [] + remainingEdgeTable = getRemainingEdgeTable(edges, vertexes, z) + remainingEdgeTableKeys = remainingEdgeTable.keys() + for remainingEdgeIndexKey in remainingEdgeTable: + edge = remainingEdgeTable[remainingEdgeIndexKey] + carveIntersection = getCarveIntersectionFromEdge(edge, vertexes, z) + corners.append(carveIntersection) + for edgeFaceIndex in edge.faceIndexes: + face = faces[edgeFaceIndex] + for edgeIndex in face.edgeIndexes: + addEdgePair(edgePairTable, edges, edgeIndex, remainingEdgeIndexKey, remainingEdgeTable) + allPoints = corners[:] + for edgePairValue in edgePairTable.values(): + addPointsAtZ(edgePairValue, allPoints, importRadius, vertexes, z) + pointTable = {} + return getDescendingAreaLoops(allPoints, corners, importRadius) + +def getLoopLayerAppend(loopLayers, z): + 'Get next z and add extruder loops.' + settings.printProgress(len(loopLayers), 'slice') + loopLayer = euclidean.LoopLayer(z) + loopLayers.append(loopLayer) + return loopLayer + +def getLoopsWithCorners(corners, importRadius, loops, pointTable): + 'Add corners to the loops.' + for corner in corners: + if corner not in pointTable: + addWithLeastLength(importRadius, loops, corner) + pointTable[corner] = None + return euclidean.getSimplifiedLoops(loops, importRadius) + +def getMeldedPillarOutput(loops): + 'Get melded pillar output.' + faces = [] + vertexes = getUniqueVertexes(loops) + addMeldedPillarByLoops(faces, loops) + return getGeometryOutputByFacesVertexes(faces, vertexes) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return evaluate.EmptyObject(elementNode) + +def getNextEdgeIndexAroundZ(edge, faces, remainingEdgeTable): + 'Get the next edge index in the mesh carve.' + for faceIndex in edge.faceIndexes: + face = faces[faceIndex] + for edgeIndex in face.edgeIndexes: + if edgeIndex in remainingEdgeTable: + return edgeIndex + return -1 + +def getOrientedLoops(loops): + 'Orient the loops which must be in descending order.' + for loopIndex, loop in enumerate(loops): + leftPoint = euclidean.getLeftPoint(loop) + isInFilledRegion = euclidean.getIsInFilledRegion(loops[: loopIndex] + loops[loopIndex + 1 :], leftPoint) + if isInFilledRegion == euclidean.isWiddershins(loop): + loop.reverse() + return loops + +def getOverlapRatio( loop, pointTable ): + 'Get the overlap ratio between the loop and the point table.' + numberOfOverlaps = 0 + for point in loop: + if point in pointTable: + numberOfOverlaps += 1 + return float( numberOfOverlaps ) / float(len(loop)) + +def getPath( edges, pathIndexes, loop, z ): + 'Get the path from the edge intersections.' + path = [] + for pathIndexIndex in xrange( len( pathIndexes ) ): + pathIndex = pathIndexes[ pathIndexIndex ] + edge = edges[ pathIndex ] + carveIntersection = getCarveIntersectionFromEdge( edge, loop, z ) + path.append( carveIntersection ) + return path + +def getPillarOutput(loops): + 'Get pillar output.' + faces = [] + vertexes = getUniqueVertexes(loops) + addPillarByLoops(faces, loops) + return getGeometryOutputByFacesVertexes(faces, vertexes) + +def getPillarsOutput(loopLists): + 'Get pillars output.' + pillarsOutput = [] + for loopList in loopLists: + pillarsOutput.append(getPillarOutput(loopList)) + return getUnifiedOutput(pillarsOutput) + +def getRemainingEdgeTable(edges, vertexes, z): + 'Get the remaining edge hashtable.' + remainingEdgeTable = {} + if len(edges) > 0: + if edges[0].zMinimum == None: + for edge in edges: + setEdgeMaximumMinimum(edge, vertexes) + for edgeIndex in xrange(len(edges)): + edge = edges[edgeIndex] + if (edge.zMinimum < z) and (edge.zMaximum > z): + remainingEdgeTable[edgeIndex] = edge + return remainingEdgeTable + +def getRemainingLoopAddFace(faces, remainingLoop): + 'Get the remaining loop and add face.' + for indexedVertexIndex, indexedVertex in enumerate(remainingLoop): + nextIndex = (indexedVertexIndex + 1) % len(remainingLoop) + previousIndex = (indexedVertexIndex + len(remainingLoop) - 1) % len(remainingLoop) + nextVertex = remainingLoop[nextIndex] + previousVertex = remainingLoop[previousIndex] + remainingPath = euclidean.getAroundLoop((indexedVertexIndex + 2) % len(remainingLoop), previousIndex, remainingLoop) + if len(remainingLoop) < 4 or getIsPathEntirelyOutsideTriangle(previousVertex, indexedVertex, nextVertex, remainingPath): + faceConvex = face.Face() + faceConvex.index = len(faces) + faceConvex.vertexIndexes.append(indexedVertex.index) + faceConvex.vertexIndexes.append(nextVertex.index) + faceConvex.vertexIndexes.append(previousVertex.index) + faces.append(faceConvex) + return euclidean.getAroundLoop(nextIndex, indexedVertexIndex, remainingLoop) + print('Warning, could not decompose polygon in getRemainingLoopAddFace in trianglemesh for:') + print(remainingLoop) + return [] + +def getSharedFace( firstEdge, faces, secondEdge ): + 'Get the face which is shared by two edges.' + for firstEdgeFaceIndex in firstEdge.faceIndexes: + for secondEdgeFaceIndex in secondEdge.faceIndexes: + if firstEdgeFaceIndex == secondEdgeFaceIndex: + return faces[ firstEdgeFaceIndex ] + return None + +def getSymmetricXLoop(path, vertexes, x): + 'Get symmetrix x loop.' + loop = [] + for point in path: + vector3Index = Vector3Index(len(vertexes), x, point.real, point.imag) + loop.append(vector3Index) + vertexes.append(vector3Index) + return loop + +def getSymmetricYLoop(path, vertexes, y): + 'Get symmetrix y loop.' + loop = [] + for point in path: + vector3Index = Vector3Index(len(vertexes), point.real, y, point.imag) + loop.append(vector3Index) + vertexes.append(vector3Index) + return loop + +def getUnifiedOutput(outputs): + 'Get unified output.' + if len(outputs) < 1: + return {} + if len(outputs) == 1: + return outputs[0] + return {'union' : {'shapes' : outputs}} + +def getUniqueVertexes(loops): + 'Get unique vertexes.' + vertexDictionary = {} + uniqueVertexes = [] + for loop in loops: + for vertexIndex, vertex in enumerate(loop): + vertexTuple = (vertex.x, vertex.y, vertex.z) + if vertexTuple in vertexDictionary: + loop[vertexIndex] = vertexDictionary[vertexTuple] + else: + if vertex.__class__ == Vector3Index: + loop[vertexIndex].index = len(vertexDictionary) + else: + loop[vertexIndex] = Vector3Index(len(vertexDictionary), vertex.x, vertex.y, vertex.z) + vertexDictionary[vertexTuple] = loop[vertexIndex] + uniqueVertexes.append(loop[vertexIndex]) + return uniqueVertexes + +def getWideAnglePointIndex(loop): + 'Get a point index which has a wide enough angle, most point indexes have a wide enough angle, this is just to make sure.' + dotProductMinimum = 9999999.9 + widestPointIndex = 0 + for pointIndex in xrange(len(loop)): + point = loop[ pointIndex % len(loop) ] + afterPoint = loop[(pointIndex + 1) % len(loop)] + beforePoint = loop[ ( pointIndex - 1 ) % len(loop) ] + afterSegmentNormalized = euclidean.getNormalized( afterPoint - point ) + beforeSegmentNormalized = euclidean.getNormalized( beforePoint - point ) + dotProduct = euclidean.getDotProduct( afterSegmentNormalized, beforeSegmentNormalized ) + if dotProduct < .99: + return pointIndex + if dotProduct < dotProductMinimum: + dotProductMinimum = dotProduct + widestPointIndex = pointIndex + return widestPointIndex + +def isInline( beginComplex, centerComplex, endComplex ): + 'Determine if the three complex points form a line.' + centerBeginComplex = beginComplex - centerComplex + centerEndComplex = endComplex - centerComplex + centerBeginLength = abs( centerBeginComplex ) + centerEndLength = abs( centerEndComplex ) + if centerBeginLength <= 0.0 or centerEndLength <= 0.0: + return False + centerBeginComplex /= centerBeginLength + centerEndComplex /= centerEndLength + return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) < -0.999 + +def isPathAdded( edges, faces, loops, remainingEdgeTable, vertexes, z ): + 'Get the path indexes around a triangle mesh carve and add the path to the flat loops.' + if len( remainingEdgeTable ) < 1: + return False + pathIndexes = [] + remainingEdgeIndexKey = remainingEdgeTable.keys()[0] + pathIndexes.append( remainingEdgeIndexKey ) + del remainingEdgeTable[remainingEdgeIndexKey] + nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[remainingEdgeIndexKey], faces, remainingEdgeTable ) + while nextEdgeIndexAroundZ != - 1: + pathIndexes.append( nextEdgeIndexAroundZ ) + del remainingEdgeTable[ nextEdgeIndexAroundZ ] + nextEdgeIndexAroundZ = getNextEdgeIndexAroundZ( edges[ nextEdgeIndexAroundZ ], faces, remainingEdgeTable ) + if len( pathIndexes ) < 3: + print('Dangling edges, will use intersecting circles to get import layer at height %s' % z) + del loops[:] + return False + loops.append( getPath( edges, pathIndexes, vertexes, z ) ) + return True + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(TriangleMesh, elementNode) + +def setEdgeMaximumMinimum(edge, vertexes): + 'Set the edge maximum and minimum.' + beginIndex = edge.vertexIndexes[0] + endIndex = edge.vertexIndexes[1] + if beginIndex >= len(vertexes) or endIndex >= len(vertexes): + print('Warning, there are duplicate vertexes in setEdgeMaximumMinimum in triangle_mesh.') + print('Something might still be printed, but there is no guarantee that it will be the correct shape.' ) + edge.zMaximum = -987654321.0 + edge.zMinimum = -987654321.0 + return + beginZ = vertexes[beginIndex].z + endZ = vertexes[endIndex].z + edge.zMinimum = min(beginZ, endZ) + edge.zMaximum = max(beginZ, endZ) + +def sortLoopsInOrderOfArea(isDescending, loops): + 'Sort the loops in the order of area according isDescending.' + loops.sort(key=euclidean.getAreaLoopAbsolute, reverse=isDescending) + + +class EdgePair: + def __init__(self): + 'Pair of edges on a face.' + self.edgeIndexes = [] + self.edges = [] + + def __repr__(self): + 'Get the string representation of this EdgePair.' + return str( self.edgeIndexes ) + + def getFromIndexesEdges( self, edgeIndexes, edges ): + 'Initialize from edge indices.' + self.edgeIndexes = edgeIndexes[:] + self.edgeIndexes.sort() + for edgeIndex in self.edgeIndexes: + self.edges.append( edges[ edgeIndex ] ) + return self + + +class FaceGenerator: + 'A face generator.' + def __init__(self, faces, indexedLoopBottom, indexedLoopTop): + 'Initialize.' + self.startTop = 0 + if len(indexedLoopBottom) == 0 or len(indexedLoopTop) == 0: + return + smallestDistance = 987654321987654321.0 + for pointIndex, point in enumerate(indexedLoopBottom): + distanceIndex = getClosestDistanceIndexToPoint(point, indexedLoopTop) + if distanceIndex.distance < smallestDistance: + smallestDistance = distanceIndex.distance + offsetBottom = pointIndex + offsetTop = distanceIndex.index + self.indexedLoopBottom = indexedLoopBottom[offsetBottom :] + indexedLoopBottom[: offsetBottom] + self.indexedLoopTop = indexedLoopTop[offsetTop :] + indexedLoopTop[: offsetTop] + for bottomIndex in xrange(len(self.indexedLoopBottom)): + self.addFacesByBottomIndex(bottomIndex, faces) + subsetTop = self.indexedLoopTop[self.startTop :] + subsetTop.append(self.indexedLoopTop[0]) + addFacesByConvexBottomTopLoop(faces, [self.indexedLoopBottom[0]], subsetTop[: : -1]) + + def addFacesByBottomIndex(self, bottomIndex, faces): + 'Add faces from the bottom index to the next index.' + bottomPoint = self.indexedLoopBottom[bottomIndex % len(self.indexedLoopBottom)] + bottomPointNext = self.indexedLoopBottom[(bottomIndex + 1) % len(self.indexedLoopBottom)] + topIndex = self.startTop + getClosestDistanceIndexToPoint(bottomPointNext, self.indexedLoopTop[self.startTop :]).index + topIndexPlusOne = topIndex + 1 + betweenIndex = self.getBetweenIndex(bottomPoint, bottomPointNext, topIndexPlusOne) + betweenIndexPlusOne = betweenIndex + 1 + subsetStart = self.indexedLoopTop[self.startTop : betweenIndexPlusOne] + subsetEnd = self.indexedLoopTop[betweenIndex : topIndexPlusOne] + addFacesByConvexBottomTopLoop(faces, [bottomPoint], subsetStart[: : -1]) + addFacesByConvexBottomTopLoop(faces, [bottomPoint, bottomPointNext], [self.indexedLoopTop[betweenIndex]]) + addFacesByConvexBottomTopLoop(faces, [bottomPointNext], subsetEnd[: : -1]) + self.startTop = topIndex + + def getBetweenIndex(self, bottomPoint, bottomPointNext, topIndexPlusOne): + 'Get the index of the last point along the loop which is closer to the bottomPoint.' + betweenIndex = self.startTop + bottomPointComplex = bottomPoint.dropAxis() + bottomPointNextComplex = bottomPointNext.dropAxis() + for topPointIndex in xrange(self.startTop, topIndexPlusOne): + topPointComplex = self.indexedLoopTop[topPointIndex].dropAxis() + if abs(topPointComplex - bottomPointComplex) > abs(topPointComplex - bottomPointNextComplex): + return betweenIndex + betweenIndex = topPointIndex + return betweenIndex + + +class TriangleMesh( group.Group ): + 'A triangle mesh.' + def __init__(self): + 'Add empty lists.' + group.Group.__init__(self) + self.belowLoops = [] + self.edges = [] + self.faces = [] + self.importCoarseness = 1.0 + self.isCorrectMesh = True + self.loopLayers = [] + self.oldChainTetragrid = None + self.transformedVertexes = None + self.vertexes = [] + + def addXMLSection(self, depth, output): + 'Add the xml section for this object.' + xml_simple_writer.addXMLFromVertexes( depth, output, self.vertexes ) + xml_simple_writer.addXMLFromObjects( depth, self.faces, output ) + + def getCarveBoundaryLayers(self): + 'Get the boundary layers.' + if self.getMinimumZ() == None: + return [] + halfHeight = 0.5 * self.layerHeight + self.zoneArrangement = ZoneArrangement(self.layerHeight, self.getTransformedVertexes()) + layerTop = self.cornerMaximum.z - halfHeight * 0.5 + z = self.cornerMinimum.z + halfHeight + while z < layerTop: + getLoopLayerAppend(self.loopLayers, z).loops = self.getLoopsFromMesh(self.zoneArrangement.getEmptyZ(z)) + z += self.layerHeight + return self.loopLayers + + def getCarveCornerMaximum(self): + 'Get the corner maximum of the vertexes.' + return self.cornerMaximum + + def getCarveCornerMinimum(self): + 'Get the corner minimum of the vertexes.' + return self.cornerMinimum + + def getCarveLayerHeight(self): + 'Get the layer height.' + return self.layerHeight + + def getFabmetheusXML(self): + 'Return the fabmetheus XML.' + return None + + def getGeometryOutput(self): + 'Get geometry output dictionary.' + return getGeometryOutputByFacesVertexes(self.faces, self.vertexes) + + def getInterpretationSuffix(self): + 'Return the suffix for a triangle mesh.' + return 'xml' + + def getLoops(self, importRadius, z): + 'Get loops sliced through shape.' + self.importRadius = importRadius + return self.getLoopsFromMesh(z) + + def getLoopsFromMesh( self, z ): + 'Get loops from a carve of a mesh.' + originalLoops = [] + self.setEdgesForAllFaces() + if self.isCorrectMesh: + originalLoops = getLoopsFromCorrectMesh( self.edges, self.faces, self.getTransformedVertexes(), z ) + if len( originalLoops ) < 1: + originalLoops = getLoopsFromUnprovenMesh( self.edges, self.faces, self.importRadius, self.getTransformedVertexes(), z ) + loops = euclidean.getSimplifiedLoops(originalLoops, self.importRadius) + sortLoopsInOrderOfArea(True, loops) + return getOrientedLoops(loops) + + def getMinimumZ(self): + 'Get the minimum z.' + self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0) + self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0) + transformedVertexes = self.getTransformedVertexes() + if len(transformedVertexes) < 1: + return None + for point in transformedVertexes: + self.cornerMaximum.maximize(point) + self.cornerMinimum.minimize(point) + return self.cornerMinimum.z + + def getTransformedVertexes(self): + 'Get all transformed vertexes.' + if self.elementNode == None: + return self.vertexes + chainTetragrid = self.getMatrixChainTetragrid() + if self.oldChainTetragrid != chainTetragrid: + self.oldChainTetragrid = matrix.getTetragridCopy(chainTetragrid) + self.transformedVertexes = None + if self.transformedVertexes == None: + if len(self.edges) > 0: + self.edges[0].zMinimum = None + self.transformedVertexes = matrix.getTransformedVector3s(chainTetragrid, self.vertexes) + return self.transformedVertexes + + def getTriangleMeshes(self): + 'Get all triangleMeshes.' + return [self] + + def getVertexes(self): + 'Get all vertexes.' + self.transformedVertexes = None + return self.vertexes + + def setCarveImportRadius( self, importRadius ): + 'Set the import radius.' + self.importRadius = importRadius + + def setCarveIsCorrectMesh( self, isCorrectMesh ): + 'Set the is correct mesh flag.' + self.isCorrectMesh = isCorrectMesh + + def setCarveLayerHeight( self, layerHeight ): + 'Set the layer height.' + self.layerHeight = layerHeight + + def setEdgesForAllFaces(self): + 'Set the face edges of all the faces.' + edgeTable = {} + for face in self.faces: + face.setEdgeIndexesToVertexIndexes( self.edges, edgeTable ) + + +class ZoneArrangement: + 'A zone arrangement.' + def __init__(self, layerHeight, vertexes): + 'Initialize the zone interval and the zZone table.' + self.zoneInterval = layerHeight / math.sqrt(len(vertexes)) / 1000.0 + self.zZoneSet = set() + for point in vertexes: + zoneIndexFloat = point.z / self.zoneInterval + self.zZoneSet.add(math.floor(zoneIndexFloat)) + self.zZoneSet.add(math.ceil(zoneIndexFloat )) + + def getEmptyZ(self, z): + 'Get the first z which is not in the zone table.' + zoneIndex = round(z / self.zoneInterval) + if zoneIndex not in self.zZoneSet: + return z + zoneAround = 1 + while 1: + zoneDown = zoneIndex - zoneAround + if zoneDown not in self.zZoneSet: + return zoneDown * self.zoneInterval + zoneUp = zoneIndex + zoneAround + if zoneUp not in self.zZoneSet: + return zoneUp * self.zoneInterval + zoneAround += 1 diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/union.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/union.py new file mode 100644 index 0000000..7c30dea --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/solids/union.py @@ -0,0 +1,39 @@ +""" +Boolean geometry union of solids. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.solids import difference +from fabmetheus_utilities.geometry.solids import group + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def convertElementNode(elementNode, geometryOutput): + 'Convert the xml element to a union xml element.' + group.convertContainerElementNode(elementNode, geometryOutput, Union()) + +def getNewDerivation(elementNode): + 'Get new derivation.' + return evaluate.EmptyObject(elementNode) + +def processElementNode(elementNode): + 'Process the xml element.' + evaluate.processArchivable(Union, elementNode) + + +class Union(difference.Difference): + 'A difference object.' + def getLoopsFromObjectLoopsList(self, importRadius, visibleObjectLoopsList): + 'Get loops from visible object loops list.' + return self.getUnion(importRadius, visibleObjectLoopsList) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/_print.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/_print.py new file mode 100644 index 0000000..716dc95 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/_print.py @@ -0,0 +1,63 @@ +""" +Print statement. + +There is also the print attribute in geometry_utilities/evaluate_fundamentals/print.py + +The model is xml_models/geometry_utilities/evaluate_fundamentals/print.xml + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getLocalDictionary( attributesKey, elementNode): + "Get the local dictionary." + xmlProcessor = elementNode.getXMLProcessor() + if len( xmlProcessor.functions ) < 1: + return None + return xmlProcessor.functions[-1].localDictionary + +def printAttributesKey( attributesKey, elementNode): + "Print the attributesKey." + if attributesKey.lower() == '_localdictionary': + localDictionary = getLocalDictionary( attributesKey, elementNode) + if localDictionary != None: + localDictionaryKeys = localDictionary.keys() + attributeValue = elementNode.attributes[attributesKey] + if attributeValue != '': + attributeValue = ' - ' + attributeValue + print('Local Dictionary Variables' + attributeValue ) + localDictionaryKeys.sort() + for localDictionaryKey in localDictionaryKeys: + print('%s: %s' % ( localDictionaryKey, localDictionary[ localDictionaryKey ] ) ) + return + value = elementNode.attributes[attributesKey] + evaluatedValue = None + if value == '': + evaluatedValue = evaluate.getEvaluatedExpressionValue(elementNode, attributesKey) + else: + evaluatedValue = evaluate.getEvaluatedExpressionValue(elementNode, value) + print('%s: %s' % ( attributesKey, evaluatedValue ) ) + +def processElementNode(elementNode): + "Process the xml element." + if len(elementNode.getTextContent()) > 1: + print(elementNode.getTextContent()) + return + attributesKeys = elementNode.attributes.keys() + if len( attributesKeys ) < 1: + print('') + return + attributesKeys.sort() + for attributesKey in attributesKeys: + printAttributesKey( attributesKey, elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/class.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/class.py new file mode 100644 index 0000000..cb74053 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/class.py @@ -0,0 +1,19 @@ +""" +Class. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + 'Process the xml element.' + pass diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/elif.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/elif.py new file mode 100644 index 0000000..5da7d59 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/elif.py @@ -0,0 +1,25 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + pass + +def processElse(elementNode): + "Process the else statement." + evaluate.processCondition(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/else.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/else.py new file mode 100644 index 0000000..708c167 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/else.py @@ -0,0 +1,30 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + pass + +def processElse(elementNode): + "Process the else statement." + functions = elementNode.getXMLProcessor().functions + if len(functions) < 1: + print('Warning, "else" element is not in a function in processElse in else.py for:') + print(elementNode) + return + functions[-1].processChildNodes(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/for.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/for.py new file mode 100644 index 0000000..954e9b2 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/for.py @@ -0,0 +1,72 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processChildNodesByIndexValue( elementNode, function, index, indexValue, value ): + "Process childNodes by index value." + if indexValue.indexName != '': + function.localDictionary[ indexValue.indexName ] = index + if indexValue.valueName != '': + function.localDictionary[ indexValue.valueName ] = value + function.processChildNodes(elementNode) + +def processElementNode(elementNode): + "Process the xml element." + if elementNode.xmlObject == None: + elementNode.xmlObject = IndexValue(elementNode) + if elementNode.xmlObject.inSplitWords == None: + return + xmlProcessor = elementNode.getXMLProcessor() + if len( xmlProcessor.functions ) < 1: + print('Warning, "for" element is not in a function in processElementNode in for.py for:') + print(elementNode) + return + function = xmlProcessor.functions[-1] + inValue = evaluate.getEvaluatedExpressionValueBySplitLine(elementNode, elementNode.xmlObject.inSplitWords) + if inValue.__class__ == list or inValue.__class__ == str: + for index, value in enumerate( inValue ): + processChildNodesByIndexValue( elementNode, function, index, elementNode.xmlObject, value ) + return + if inValue.__class__ == dict: + inKeys = inValue.keys() + inKeys.sort() + for inKey in inKeys: + processChildNodesByIndexValue( elementNode, function, inKey, elementNode.xmlObject, inValue[ inKey ] ) + + +class IndexValue: + "Class to get the in attribute, the index name and the value name." + def __init__(self, elementNode): + "Initialize." + self.inSplitWords = None + self.indexName = '' + if 'index' in elementNode.attributes: + self.indexName = elementNode.attributes['index'] + self.valueName = '' + if 'value' in elementNode.attributes: + self.valueName = elementNode.attributes['value'] + if 'in' in elementNode.attributes: + self.inSplitWords = evaluate.getEvaluatorSplitWords( elementNode.attributes['in'] ) + else: + print('Warning, could not find the "in" attribute in IndexValue in for.py for:') + print(elementNode) + return + if len( self.inSplitWords ) < 1: + self.inSplitWords = None + print('Warning, could not get split words for the "in" attribute in IndexValue in for.py for:') + print(elementNode) + diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/function.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/function.py new file mode 100644 index 0000000..d758fa2 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/function.py @@ -0,0 +1,19 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + pass diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/if.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/if.py new file mode 100644 index 0000000..1519efd --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/if.py @@ -0,0 +1,21 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + evaluate.processCondition(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/return.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/return.py new file mode 100644 index 0000000..f8bb5c5 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/return.py @@ -0,0 +1,33 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + functions = elementNode.getXMLProcessor().functions + if len(functions) < 1: + return + function = functions[-1] + function.shouldReturn = True + if elementNode.xmlObject == None: + if 'return' in elementNode.attributes: + value = elementNode.attributes['return'] + elementNode.xmlObject = evaluate.getEvaluatorSplitWords(value) + else: + elementNode.xmlObject = [] + if len( elementNode.xmlObject ) > 0: + function.returnValue = evaluate.getEvaluatedExpressionValueBySplitLine(elementNode, elementNode.xmlObject) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/statement.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/statement.py new file mode 100644 index 0000000..805d70e --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/statement.py @@ -0,0 +1,50 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + functions = elementNode.getXMLProcessor().functions + if len(functions) < 1: + print('Warning, there are no functions in processElementNode in statement for:') + print(elementNode) + return + function = functions[-1] + evaluate.setLocalAttribute(elementNode) + if elementNode.xmlObject.value == None: + print('Warning, elementNode.xmlObject.value is None in processElementNode in statement for:') + print(elementNode) + return + localValue = evaluate.getEvaluatedExpressionValueBySplitLine(elementNode, elementNode.xmlObject.value) + keywords = elementNode.xmlObject.key.split('.') + if len(keywords) == 0: + print('Warning, there are no keywords in processElementNode in statement for:') + print(elementNode) + return + firstWord = keywords[0] + if len(keywords) == 1: + function.localDictionary[firstWord] = localValue + return + attributeName = keywords[-1] + object = None + if firstWord == 'self': + object = function.classObject + else: + object = function.localDictionary[firstWord] + for keywordIndex in xrange(1, len(keywords) - 1): + object = object._getAccessibleAttribute(keywords[keywordIndex]) + object._setAccessibleAttribute(attributeName, localValue) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/while.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/while.py new file mode 100644 index 0000000..314f0df --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry/statements/while.py @@ -0,0 +1,34 @@ +""" +Polygon path. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Art of Illusion ' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def processElementNode(elementNode): + "Process the xml element." + if elementNode.xmlObject == None: + if 'condition' in elementNode.attributes: + value = elementNode.attributes['condition'] + elementNode.xmlObject = evaluate.getEvaluatorSplitWords(value) + else: + elementNode.xmlObject = [] + if len( elementNode.xmlObject ) < 1: + return + xmlProcessor = elementNode.getXMLProcessor() + if len( xmlProcessor.functions ) < 1: + return + function = xmlProcessor.functions[-1] + while evaluate.getEvaluatedExpressionValueBySplitLine(elementNode, elementNode.xmlObject) > 0: + function.processChildNodes(elementNode) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/__init__.py new file mode 100644 index 0000000..2dc8ddc --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 2 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/creation/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/creation/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/creation/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_matrix/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_matrix/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_matrix/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_meta/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_meta/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_meta/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_paths/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_paths/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_paths/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_shapes/__init__.py b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_shapes/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/geometry_plugins/manipulation_shapes/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/display_line.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/display_line.ppm new file mode 100644 index 0000000..4738d03 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/display_line.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +27 27 +255 +ðôôðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóðóóîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòíññÙØZäâHîìMõóP÷õRôòRíìcåéééíííññîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòíññÜÞ«ÖÔCæäHïíM÷õRüúVÿÿ\ÿÿkÿÿ\õóTáâ¦äèèëïïíññîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòëïïÎÍ@àÞEëéKõóPüúVÿÿ~ÿÿÉÿÿãÿÿÉÿÿ~õóTÐÔÔãççëïïîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòíññÄÃRÖÕ@ãáFîìLøöSÿÿ\ÿÿÉÿÿþÿÿÛÿÿþÿÿÉÿÿ\åä\ÐÔÔäèèíññîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòéííÄÃ:×Ö@äâGïíMù÷TÿÿkÿÿãÿÿÛÿýYÿÿÛÿÿãÿÿkòðQº½½ÙÜÜéííîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòåééÇÆ:ÖÕ@ãáFîìLøöSÿÿ\ÿÿÉÿÿþÿÿÛÿÿþÿÿÉÿÿ\õóQ¢¥¥ËÏÏåééîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòàääÆÄ9ÔÒ?àÞEëéKõóPüúVÿÿ~ÿÿÉÿÿãÿÿÉÿÿ~üúVõóP”——ÃÆÆàääîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòÞâ⿾6ÏÍ=ÛÙBæäHïíM÷õRüúVÿÿ\ÿÿkÿÿ\üúV÷õRìêKŽ¾ÂÂÞââîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòÞâⱯ4ÈÇ:ÕÓ?ßÝDèæIïíMõóPøöSù÷TøöSõóPïíMàÞEŽ¾ÂÂÞââîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòÞâ⢢CÀ¿7ÌÊ<ÖÕ@ßÝDæäHëéKîìLïíMîìLëéKæäHÆÄGŽ¾ÂÂÞââîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòàääÃÆÆ©¨2ÂÀ8ÌÊ<ÕÓ?ÛÙBàÞEãáFäâGãáFàÞEÑÏ@rtt”——ÃÆÆàääîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòåééËÏÏœw¨¦2ÑÐ@óñRÿýYôòSæåWÿýYôòSÉÇ=ŠŠV}¢¥¥ËÏÏåééîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòéííÙÜܺ½½“––ÞÜCæäHðîNûùWòòtÿÿ×æåLfggwyy“––º½½ÙÜÜéííîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòíññäèèÐÔÔ¯²²ÞÜCëéJöôQÿýYÿÿÍÿÿãÿýYrttŽ¯²²ÐÔÔäèèíññîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòëïïãççÊÍÍÞÜCëéJöôQÿýYÿÿÑÿÿåÿýYƒƒ¨««ÊÍÍãççëïïîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòíññëïïÙÜÜÞÜCëéJöôQÿýYÿÿÑÿÿåÿýY‰ŒŒ·ººÙÜÜëïïíññîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòÞâ⃃ƒ›››±±±ÄÄÄÓÓÓüüüÓÓÓŽ½ÁÁÞââîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòÞâ⎎™šš¬¬¬­®®¹ººÙÚÚ½½½’’¿ÃÃÞââîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòßãã‹‹‹ššš¬¬¬½½½ËËËñññÌÌÌ“••ÀÃÃßããîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòàää‘’’š››¤¤¤¶¶¶£¤¤ÐÑÑÂÃÙœœÃÆÆàääîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòâæ涸¸›››®®®ÀÀÀÂÃÃää䧨¨ž  ÆÉÉáååîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòåééÉÍÍ›››±±±ÄÄÄÓÓÓüüü™œœ¦©©ËÏÏãççîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòèììÕØص¸¸“••€‚‚z||„††•˜˜²µµÓÖÖçêêîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòìððßããÈËË«®®›žž”——œžž¬¯¯ÅÈÈÜààêîîîòòîòòîòòîòòîòòîòòîòòðóóîòòîòòîòòîòòîòòîòòîòòîòòíññçëëÜààÍÑÑÃÆÆ¿ÃÃÃÆÆÌÐÐÚÞÞæééíññîòòîòòîòòîòòîòòîòòîòò \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/dive.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/dive.ppm new file mode 100644 index 0000000..39b6e11 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/dive.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +16 16 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿìôÿØéÿÉáÿ¾Ûÿ¾ÛÿÉáÿØéÿìôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöúÿÙêÿÇÝùÐÞïÕÛãÖØÚÕ×ÙÑØáÍÛìÆÜøÙêÿöúÿÿÿÿÿÿÿÿÿÿöúÿÓæÿÓáòÙÝâèìñóöúûýþûýþòõùåéîÏÔÚÎÜîÓæÿöúÿÿÿÿÿÿÿÙêÿÓáòÖÚà÷øûÿÿÿöùüîóúíóúôøüÿÿÿóöùÇÌÓÍÛìÙêÿÿÿÿìôÿÇÝùÙÝâ÷øûÿÿÿëñùãì÷èïùèïùæïøßéöÿÿÿóöùÄÉÏÃÙõìôÿØéÿÐÝîèìñÿÿÿëñùãì÷YŒÍèïùæïøßéöX‹Ëãì÷ÿÿÿØßçÀÎßØéÿÉáÿÔÚâóöúôøüáë÷S„ÃYŒÍåîøÝèöN{´X‹Ëáë÷ñõûêïö¶½ÅÉáÿ¾ÛÿÔÖØûýþéðùRƒÀRƒÁX‹ËßéöMz³Mz´WŠÊßéöãì÷ùûý«­¯¾Ûÿ¾ÛÿÓÕ×ûýþìòúZŽÑQ€½X‹ËéðùU†ÅP~¹VˆÈÝèöáë÷ñõú§©ª¾ÛÿÉáÿÐ×ßïóøòöûäí÷YÐYŒÏàêöáë÷X‹ËX‹ÍÜçõîóúÛäדּ»ÉáÿØéÿÌÙëáåìþþÿáë÷ãì÷\ÒÝèöÝèößéö[ÑÚåõúüþ¿ÉÖµÃÔØéÿìôÿÄÚ÷ÈÌÒíñ÷ÿÿÿÝèöàêöÜçõÛæõÚåõÛæõþþÿÜä±¾ÔðìôÿÿÿÿÙêÿÉ×è·¾Æèîôþþÿïôûáë÷àêöíóúúüþÛäî–¡¬»ÉÛÙêÿÿÿÿÿÿÿöúÿÓæÿÅÒ䱸¾ÈÑÜáèñòöûîóúÖàí¸ÃÒ¤­ºÈÚÓæÿöúÿÿÿÿÿÿÿÿÿÿöúÿÙêÿÀÖò·ÅÖ¨¯·œŸ˜šœ£ª³±¿Ð¼ÓïÙêÿöúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìôÿØéÿÉáÿ¾Ûÿ¾ÛÿÉáÿØéÿìôÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/soar.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/soar.ppm new file mode 100644 index 0000000..9fb815d --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/soar.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +16 16 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿìôÿØéÿÉáÿ¾Ûÿ¾ÛÿÉáÿØéÿìôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿöúÿÙêÿÇÝùÐÞïÕÛãÖØÚÕ×ÙÑØáÍÛìÆÜøÙêÿöúÿÿÿÿÿÿÿÿÿÿöúÿÓæÿÓáòÙÝâèìñóöúûýþûýþòõùåéîÏÔÚÎÜîÓæÿöúÿÿÿÿÿÿÿÙêÿÓáòÖÚà÷øûÿÿÿöùüîóúíóúôøüÿÿÿóöùÇÌÓÍÛìÙêÿÿÿÿìôÿÇÝùÙÝâ÷øûÿÿÿÜçõéðùèïùèïùÚåõåîøÿÿÿóöùÄÉÏÃÙõìôÿØéÿÐÝîèìñÿÿÿëñùT…ÃÚåõèïùæïøRƒÁÕãóãì÷ÿÿÿØßçÀÎßØéÿÉáÿÔÚâóöúôøüèïùS„ÃS„ÁØåôäí÷RƒÀRƒÀÔâóñõûêïö¶½ÅÉáÿ¾ÛÿÔÖØûýþìòúèïùRƒÁRƒÁVˆÈÛæõRÀR¾U†ÆÛæõùûý«­¯¾Ûÿ¾ÛÿÓÕ×ûýþëñùåîøRƒÁU‡ÆRƒÀìòúQ€¾T…ÃQ€½ìòúñõú§©ª¾ÛÿÉáÿÐ×ßïóøòöûãì÷U‡ÆRÀìòúßéöT…ÃQ€½ëñùîóúÛäדּ»ÉáÿØéÿÌÙëáåìþþÿáë÷R¾ìòúÝèöÝèöQ½éðùÚåõúüþ¿ÉÖµÃÔØéÿìôÿÄÚ÷ÈÌÒíñ÷ÿÿÿëñùÝèöÜçõÛæõéðùØåôþþÿÜä±¾ÔðìôÿÿÿÿÙêÿÉ×è·¾Æèîôþþÿïôûáë÷àêöíóúúüþÛäî–¡¬»ÉÛÙêÿÿÿÿÿÿÿöúÿÓæÿÅÒ䱸¾ÈÑÜáèñòöûîóúÖàí¸ÃÒ¤­ºÈÚÓæÿöúÿÿÿÿÿÿÿÿÿÿöúÿÙêÿÀÖò·ÅÖ¨¯·œŸ˜šœ£ª³±¿Ð¼ÓïÙêÿöúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìôÿØéÿÉáÿ¾Ûÿ¾ÛÿÉáÿØéÿìôÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/stop.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/stop.ppm new file mode 100644 index 0000000..d1b9813 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/stop.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +16 16 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿùâáï³¯íª¦íª¥í¨¤ë¤ží©¥÷ÙØþüüÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøáàò¹²üÓÂÿÖÁÿѽÿÏ»ÿιüȷퟘòÅÃÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿøáàð·±üÔÄÿ£ÿ¯‹ÿ«‰ÿ§„ÿ ~ÿ®‘ûÃ´í¢œöÖÕÿÿÿÿÿÿÿÿÿøáßñµ¯üÑÁÿ¿ ÿª†ÿ§…ÿ¢ÿ|ÿ˜xÿoÿ¡…ûº¬ê—öÓÒÿÿÿÿÿÿüͼÿ»œÿ¥ÿ¢ÿž|ÿ™xÿ“tÿŽoÿ‰iÿ€`ÿ“xû­žå…ÿÿÿÿÿÿê˜ÿ͸ÿ }ÿ|ÿ™xÿ“sÿŽnÿ‰iÿƒeÿ~`ÿxYÿpOÿª˜ârkÿÿÿÿÿÿéš•ÿıÿ˜vÿ“sÿŽnÿ‰iÿƒeÿ~_ÿyZÿsUÿnOÿgHÿ¡áohÿÿÿÿÿÿè”ÿ¾«ÿmÿ‰iÿƒeÿ~_ÿyZÿtUÿoPÿiKÿcFÿ\?ÿ›Šßibÿÿÿÿÿÿ抄ÿ»¦ÿ_ÿ~_ÿyZÿsUÿnPÿiKÿdFÿ^AÿY;ÿO1ÿ™‡Ý^Vÿÿÿÿÿÿ莈ü²¢ÿsÿpOÿnOÿiKÿdFÿ^AÿZ<ÿU7ÿK,ÿgNú“„Þf_ÿÿÿÿÿÿõÒÐ鎇û­Ÿÿgÿ^@ÿ^@ÿY<ÿU7ÿP3ýF(ýeJö–‰áneòÃÁÿÿÿÿÿÿÿÿÿôÌÊç†~ú¥–ÿtZÿP2ÿO0þI,úB%ù`Hô”†âpiñÀ½ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóÈÆç|tú—‰ÿ•„üúŽ}÷}ò‹}ÝYQꟛÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿóÆÄàkeÜYRÜ[SÚXPÚRIÜ`Yðº·þúúÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_move.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_move.ppm new file mode 100644 index 0000000..d566b1e Binary files /dev/null and b/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_move.ppm differ diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_rotate.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_rotate.ppm new file mode 100644 index 0000000..49ca6be Binary files /dev/null and b/SkeinPyPy_NewUI/fabmetheus_utilities/images/view_rotate.ppm differ diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_in.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_in.ppm new file mode 100644 index 0000000..6f4767f --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_in.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +16 16 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôìÜÝÑҮmÏ©eΧcШjÙ»ŒóèÚÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìÜ¿Ô°p×·}æѯ÷ðåöïääάҭwÌ¡gçÓºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôìÜÔ°påÏ©÷úýïõûêòúëóúñöûùûýßţɛcñæØÿÿÿÿÿÿÿÿÿÿÿÿÝ‘׶}÷úýëóúèñùéòùéòúêòúîõûùûýͤqÒ®„ÿÿÿÿÿÿÿÿÿÿÿÿÒ­læÑ®ðöûèñùéòùéòúêòúëóúëóúò÷üßħÒ[ÿÿÿÿÿÿÿÿÿÿÿÿϨc÷ïåëóúéòúêòúêòúëóúg¶\a°W\¨Rôëâ¿‹Pÿÿÿÿÿÿÿÿÿÿÿÿͦböïäìóúêòúêòúëóúëóú_­V¦ÕSžKôë⾇Mÿÿÿÿÿÿÿÿÿÿÿÿͧhä̬ñ÷üëóúëóúc²Y^«Tv·mžÑ•k¬cD‹=>ƒ7ÿÿÿÿÿÿÿÿÿÿÿÿع‹Ñªvùûýïõûìôú\¨RŸÒ—šÏ’†Å}ʈ‹Æƒ5y0ÿÿÿÿÿÿÿÿÿÿÿÿòçÙÊŸeÞÄ£ùûþóøüSžKM–Ee§^‹Ç…Z›S3v..o)ãÍ»ÿÿÿÿÿÿÿÿÿÿÿÿæѹȚ`Ê oÞçôëâôëâ>ƒ7‚Â}2t,ãÌ™ÞļƒOøñíÿÿÿÿÿÿÿÿÿÿÿÿñåØÑ«ƒÁZ¾‡M½…L5y00q+*k&èÕ£é×¥Ú»€¬e?÷ñíÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâ˹ÁŽXßÅçÓ àÇ“É›a—aFÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáɸ¼„Q×µ}ÖµÑV¶{Qÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáɸ®mH¹€H·yRúöôÿÿÿ \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_out.ppm b/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_out.ppm new file mode 100644 index 0000000..108f893 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/images/zoom_out.ppm @@ -0,0 +1,5 @@ +P6 +# CREATOR: The GIMP's PNM Filter Version 1.0 +16 16 +255 +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôìÜÝÑҮmÏ©eΧcШjÙ»ŒóèÚÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿìÜ¿Ô°p×·}æѯ÷ðåöïääάҭwÌ¡gçÓºÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôìÜÔ°påÏ©÷úýïõûêòúëóúñöûùûýßţɛcñæØÿÿÿÿÿÿÿÿÿÿÿÿÝ‘׶}÷úýëóúèñùéòùéòúêòúîõûùûýͤqÒ®„ÿÿÿÿÿÿÿÿÿÿÿÿÒ­læÑ®ðöûèñùéòùéòúêòúëóúëóúò÷üßħÒ[ÿÿÿÿÿÿÿÿÿÿÿÿϨc÷ïåëóúéòúêòúêòúëóúëóúìóúîõûôëâ¿‹Pÿÿÿÿÿÿÿÿÿÿÿÿͦböïäìóúêòúêòúëóúëóúìóúìôúïõûôë⾇Mÿÿÿÿÿÿÿÿÿÿÿÿͧhä̬ñ÷üëóúëóúþxtüomúcb÷TVôEHñ5:î&.ÿÿÿÿÿÿÿÿÿÿÿÿع‹ÑªvùûýïõûìôúüomøÈ¿÷À·ö¶®ô«¤ó ›ì#ÿÿÿÿÿÿÿÿÿÿÿÿòçÙÊŸeÞÄ£ùûþóøüúcb÷TVôEHñ5:î&.ì#êãÍ»ÿÿÿÿÿÿÿÿÿÿÿÿæѹȚ`Ê oÞçôëâôëâܾ¤ÁSÖ´yãÌ™ÞļƒOøñíÿÿÿÿÿÿÿÿÿÿÿÿñåØÑ«ƒÁZ¾‡M½…L»†R»LÛ¿‡èÕ£é×¥Ú»€¬e?÷ñíÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿâ˹ÁŽXßÅçÓ àÇ“É›a—aFÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿáɸ¼„Q×µ}ÖµÑV±rEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿàǶ¥\3¹€H³sKøòïÿÿÿ \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/intercircle.py b/SkeinPyPy_NewUI/fabmetheus_utilities/intercircle.py new file mode 100644 index 0000000..3c4e602 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/intercircle.py @@ -0,0 +1,725 @@ +""" +Intercircle is a collection of utilities for intersecting circles, used to get smooth loops around a collection of points and inset & outset loops. + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import euclidean +import math + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalDecreasingRadiusMultipliers = [1.0, 0.55, 0.35, 0.2] +globalIntercircleMultiplier = 1.04 # 1.02 is enough to stop known intersection + + +def addCircleIntersectionLoop(circleIntersectionLoop, circleIntersections): + 'Add a circle intersection loop.' + firstCircleIntersection = circleIntersectionLoop[0] + circleIntersectionAhead = firstCircleIntersection + for circleIntersectionIndex in xrange(len(circleIntersections) + 1): + circleIntersectionAhead = circleIntersectionAhead.getCircleIntersectionAhead() + if circleIntersectionAhead == firstCircleIntersection or circleIntersectionAhead == None: + firstCircleIntersection.steppedOn = True + return + circleIntersectionAhead.addToList(circleIntersectionLoop) + firstCircleIntersection.steppedOn = True + print('Warning, addCircleIntersectionLoop would have gone into an endless loop.') + print('circleIntersectionLoop') + for circleIntersection in circleIntersectionLoop: + print(circleIntersection) + print(circleIntersection.circleNodeAhead) + print(circleIntersection.circleNodeBehind) + print('firstCircleIntersection') + print(firstCircleIntersection) + print('circleIntersections') + for circleIntersection in circleIntersections: + print(circleIntersection) + +def addEndCap(begin, end, points, radius): + 'Get circular end cap.' + beginMinusEnd = begin - end + beginMinusEndLength = abs(beginMinusEnd) + if beginMinusEndLength <= 0.0: + points.append(begin) + return + beginMinusEnd *= radius / beginMinusEndLength + perpendicular = complex(-beginMinusEnd.imag, beginMinusEnd.real) + numberOfSides = 20 # to end up with close to unit length corners, 5 * 4 + numberOfPositiveSides = numberOfSides / 2 + totalAngle = 0.0 + angle = euclidean.globalTau / float(numberOfSides) + # dotProductMultiplier to compensate for the corner outset in addInsetPointFromClockwiseTriple + dotProductMultiplier = 2.0 - 1.0 / math.cos(0.5 * angle) + for sideIndex in xrange(numberOfPositiveSides + 1): + circumferentialPoint = math.sin(totalAngle) * beginMinusEnd + math.cos(totalAngle) * perpendicular + points.append(begin + circumferentialPoint * dotProductMultiplier) + totalAngle += angle + +def addHalfPath(path, points, radius, thresholdRatio=0.9): + 'Add the points from every point on a half path and between points.' + lessThanRadius = 0.75 * radius + for pointIndex in xrange(len(path) - 1): + begin = path[pointIndex] + center = path[pointIndex + 1] + centerBegin = getWiddershinsByLength(begin, center, radius) + if centerBegin != None: + addPointsFromSegment(begin + centerBegin, center + centerBegin, points, lessThanRadius, thresholdRatio) + endIndex = pointIndex + 2 + if endIndex < len(path): + end = path[endIndex] + centerEnd = getWiddershinsByLength(center, end, radius) + if centerBegin != None and centerEnd != None: + centerPerpendicular = 0.5 * (centerBegin + centerEnd) + points.append(center + centerPerpendicular) + if euclidean.getCrossProduct(centerBegin, centerEnd) < 0.0: + points.append(center + centerBegin) + points.append(center + centerEnd) + else: + points.append(center) + addEndCap(path[0], path[1], points, radius) + +def addInsetPointFromClockwiseTriple(begin, center, end, loop, radius): + 'Get inset point with possible intersection from clockwise triple, out from widdershins loop.' + centerMinusBegin = center - begin + centerMinusBeginLength = abs(centerMinusBegin) + centerMinusBeginClockwise = None + if centerMinusBeginLength > 0.0: + centerMinusBeginClockwise = complex(centerMinusBegin.imag, -centerMinusBegin.real) / centerMinusBeginLength + endMinusCenter = end - center + endMinusCenterLength = abs(endMinusCenter) + endMinusCenterClockwise = None + if endMinusCenterLength > 0.0: + endMinusCenterClockwise = complex(endMinusCenter.imag, -endMinusCenter.real) / endMinusCenterLength + if centerMinusBeginClockwise == None and endMinusCenterClockwise == None: + return + if centerMinusBeginClockwise == None: + loop.append(center + endMinusCenterClockwise * radius) + return + if endMinusCenterClockwise == None: + loop.append(center + centerMinusBeginClockwise * radius) + return + centerClockwise = 0.5 * (centerMinusBeginClockwise + endMinusCenterClockwise) + dotProduct = euclidean.getDotProduct(centerMinusBeginClockwise, centerClockwise) + loop.append(center + centerClockwise * radius / max(0.4, abs(dotProduct))) # 0.4 to avoid pointy corners + +def addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ): + 'Add orbits with the extruder off.' + timeInOrbit = 0.0 + while timeInOrbit < temperatureChangeTime: + for point in loop: + distanceFeedRate.addGcodeMovementZWithFeedRate( 60.0 * orbitalFeedRatePerSecond, point, z ) + timeInOrbit += euclidean.getLoopLength(loop) / orbitalFeedRatePerSecond + +def addOrbitsIfLarge( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ): + 'Add orbits with the extruder off if the orbits are large enough.' + if orbitsAreLarge( loop, temperatureChangeTime ): + addOrbits( distanceFeedRate, loop, orbitalFeedRatePerSecond, temperatureChangeTime, z ) + +def addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio=0.9 ): + 'Add point complexes between the endpoints of a segment.' + if radius <= 0.0: + print('This should never happen, radius should never be zero or less in addPointsFromSegment in intercircle.') + thresholdRadius = radius * thresholdRatio # a higher number would be faster but would leave bigger dangling loops and extra dangling loops. + thresholdDiameter = thresholdRadius + thresholdRadius + segment = pointEnd - pointBegin + segmentLength = abs(segment) + extraCircles = int( math.floor( segmentLength / thresholdDiameter ) ) + if extraCircles < 1: + return + if segmentLength == 0.0: + print('Warning, segmentLength = 0.0 in intercircle.') + print('pointBegin') + print(pointBegin) + print(pointEnd) + return + if extraCircles < 2: + lengthIncrement = segmentLength / ( float(extraCircles) + 1.0 ) + segment *= lengthIncrement / segmentLength + pointBegin += segment + else: + pointBegin += segment * thresholdDiameter / segmentLength + remainingLength = segmentLength - thresholdDiameter - thresholdDiameter + lengthIncrement = remainingLength / ( float(extraCircles) - 1.0 ) + segment *= lengthIncrement / segmentLength + for circleIndex in xrange(extraCircles): + points.append( pointBegin ) + pointBegin += segment + +def directLoop(isWiddershins, loop): + 'Direct the loop.' + if euclidean.isWiddershins(loop) != isWiddershins: + loop.reverse() + +def directLoopLists(isWiddershins, loopLists): + 'Direct the loop lists.' + for loopList in loopLists: + directLoops(isWiddershins, loopList) + +def directLoops(isWiddershins, loops): + 'Direct the loops.' + for loop in loops: + directLoop(isWiddershins, loop) + +def getAroundsFromLoop(loop, radius, thresholdRatio=0.9): + 'Get the arounds from the loop.' + return getAroundsFromPoints(getPointsFromLoop(loop, radius, thresholdRatio), radius) + +def getAroundsFromLoops( loops, radius, thresholdRatio=0.9 ): + 'Get the arounds from the loops.' + return getAroundsFromPoints(getPointsFromLoops(loops, radius, thresholdRatio), radius) + +def getAroundsFromPath(path, radius, thresholdRatio=0.9): + 'Get the arounds from the path.' + radius = abs(radius) + points = getPointsFromPath(path, radius, thresholdRatio) + return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9) + +def getAroundsFromPathPoints(points, radius, thresholdRatio=0.9): + 'Get the arounds from the path.' + centers = getCentersFromPoints(points, 0.8 * radius) + arounds = [] + for center in centers: + if euclidean.isWiddershins(center): + arounds.append(euclidean.getSimplifiedPath(center, radius)) + return arounds + +def getAroundsFromPaths(paths, radius, thresholdRatio=0.9): + 'Get the arounds from the path.' + radius = abs(radius) + points = [] + for path in paths: + points += getPointsFromPath(path, radius, thresholdRatio) + return getAroundsFromPathPoints(points, radius, thresholdRatio=0.9) + +def getAroundsFromPoints( points, radius ): + 'Get the arounds from the points.' + arounds = [] + radius = abs(radius) + centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius) + for center in centers: + inset = getSimplifiedInsetFromClockwiseLoop(center, radius) + if isLargeSameDirection(inset, center, radius): + arounds.append(inset) + return arounds + +def getCentersFromCircleNodes( circleNodes, radius ): + 'Get the complex centers of the circle intersection loops from circle nodes.' + if len( circleNodes ) < 2: + return [] + circleIntersections = getCircleIntersectionsFromCircleNodes( circleNodes ) + circleIntersectionLoops = getCircleIntersectionLoops( circleIntersections ) + return getCentersFromIntersectionLoops( circleIntersectionLoops, radius ) + +def getCentersFromIntersectionLoop(circleIntersectionLoop, radius): + 'Get the centers from the intersection loop.' + loop = [] + for circleIntersection in circleIntersectionLoop: + loop.append(circleIntersection.circleNodeAhead.actualPoint) + return loop + +def getCentersFromIntersectionLoops( circleIntersectionLoops, radius ): + 'Get the centers from the intersection loops.' + centers = [] + for circleIntersectionLoop in circleIntersectionLoops: + centers.append( getCentersFromIntersectionLoop( circleIntersectionLoop, radius ) ) + return centers + +def getCentersFromLoop( loop, radius ): + 'Get the centers of the loop.' + circleNodes = getCircleNodesFromLoop( loop, radius ) + return getCentersFromCircleNodes( circleNodes, radius ) + +def getCentersFromLoopDirection( isWiddershins, loop, radius ): + 'Get the centers of the loop which go around in the given direction.' + centers = getCentersFromLoop( loop, radius ) + return getLoopsFromLoopsDirection( isWiddershins, centers ) + +def getCentersFromPoints(points, radius): + 'Get the centers from the points.' + circleNodes = getCircleNodesFromPoints(points, abs(radius)) + return getCentersFromCircleNodes(circleNodes, abs(radius)) + +def getCircleIntersectionLoops( circleIntersections ): + 'Get all the loops going through the circle intersections.' + circleIntersectionLoops = [] + for circleIntersection in circleIntersections: + if not circleIntersection.steppedOn: + circleIntersectionLoop = [ circleIntersection ] + circleIntersectionLoops.append( circleIntersectionLoop ) + addCircleIntersectionLoop( circleIntersectionLoop, circleIntersections ) + return circleIntersectionLoops + +def getCircleIntersectionsFromCircleNodes(circleNodes): + 'Get all the circle intersections which exist between all the circle nodes.' + if len( circleNodes ) < 1: + return [] + circleIntersections = [] + index = 0 + pixelTable = {} + for circleNode in circleNodes: + euclidean.addElementToPixelListFromPoint(circleNode, pixelTable, circleNode.dividedPoint) + accumulatedCircleNodeTable = {} + for circleNodeIndex in xrange(len(circleNodes)): + circleNodeBehind = circleNodes[circleNodeIndex] + circleNodeIndexMinusOne = circleNodeIndex - 1 + if circleNodeIndexMinusOne >= 0: + circleNodeAdditional = circleNodes[circleNodeIndexMinusOne] + euclidean.addElementToPixelListFromPoint(circleNodeAdditional, accumulatedCircleNodeTable, 0.5 * circleNodeAdditional.dividedPoint) + withinNodes = circleNodeBehind.getWithinNodes(accumulatedCircleNodeTable) + for circleNodeAhead in withinNodes: + circleIntersectionForward = CircleIntersection(circleNodeAhead, index, circleNodeBehind) + if not circleIntersectionForward.isWithinCircles(pixelTable): + circleIntersections.append(circleIntersectionForward) + circleNodeBehind.circleIntersections.append(circleIntersectionForward) + index += 1 + circleIntersectionBackward = CircleIntersection(circleNodeBehind, index, circleNodeAhead) + if not circleIntersectionBackward.isWithinCircles(pixelTable): + circleIntersections.append(circleIntersectionBackward) + circleNodeAhead.circleIntersections.append(circleIntersectionBackward) + index += 1 + return circleIntersections + +def getCircleNodesFromLoop(loop, radius, thresholdRatio=0.9): + 'Get the circle nodes from every point on a loop and between points.' + radius = abs(radius) + points = getPointsFromLoop( loop, radius, thresholdRatio ) + return getCircleNodesFromPoints( points, radius ) + +def getCircleNodesFromPoints(points, radius): + 'Get the circle nodes from a path.' + if radius == 0.0: + print('Warning, radius is 0 in getCircleNodesFromPoints in intercircle.') + print(points) + return [] + circleNodes = [] + oneOverRadius = 1.000001 / radius # to avoid problem of accidentally integral radius + points = euclidean.getAwayPoints(points, radius) + for point in points: + circleNodes.append(CircleNode(oneOverRadius, point)) + return circleNodes + +def getInsetLoopsFromLoop(loop, radius, thresholdRatio=0.9): + 'Get the inset loops, which might overlap.' + if radius == 0.0: + return [loop] + isInset = radius > 0 + insetLoops = [] + isLoopWiddershins = euclidean.isWiddershins(loop) + arounds = getAroundsFromLoop(loop, radius, thresholdRatio) + for around in arounds: + leftPoint = euclidean.getLeftPoint(around) + shouldBeWithin = (isInset == isLoopWiddershins) + if euclidean.isPointInsideLoop(loop, leftPoint) == shouldBeWithin: + if isLoopWiddershins != euclidean.isWiddershins(around): + around.reverse() + insetLoops.append(around) + return insetLoops + +def getInsetLoopsFromLoops(loops, radius): + 'Get the inset loops, which might overlap.' + insetLoops = [] + for loop in loops: + insetLoops += getInsetLoopsFromLoop(loop, radius) + return insetLoops + +def getInsetLoopsFromVector3Loop(loop, radius, thresholdRatio=0.9): + 'Get the inset loops from vector3 loop, which might overlap.' + if len(loop) < 2: + return [loop] + loopComplex = euclidean.getComplexPath(loop) + loopComplexes = getInsetLoopsFromLoop(loopComplex, radius) + return euclidean.getVector3Paths(loopComplexes, loop[0].z) + +def getInsetSeparateLoopsFromLoops(loops, radius, thresholdRatio=0.9): + 'Get the separate inset loops.' + if radius == 0.0: + return loops + isInset = radius > 0 + insetSeparateLoops = [] + arounds = getAroundsFromLoops(loops, abs(radius), thresholdRatio) + for around in arounds: + if isInset == euclidean.getIsInFilledRegion(loops, around[0]): + if isInset: + around.reverse() + insetSeparateLoops.append(around) + return insetSeparateLoops + +def getInsetSeparateLoopsFromAroundLoops(loops, radius, radiusAround, thresholdRatio=0.9): + 'Get the separate inset loops.' + if radius == 0.0: + return loops + isInset = radius > 0 + insetSeparateLoops = [] + radius = abs(radius) + radiusAround = max(abs(radiusAround), radius) + points = getPointsFromLoops(loops, radiusAround, thresholdRatio) + centers = getCentersFromPoints(points, globalIntercircleMultiplier * radiusAround) + for center in centers: + inset = getSimplifiedInsetFromClockwiseLoop(center, radius) + if isLargeSameDirection(inset, center, radius): + if isInset == euclidean.getIsInFilledRegion(loops, inset[0]): + if isInset: + inset.reverse() + insetSeparateLoops.append(inset) + return insetSeparateLoops + +def getIsLarge(loop, radius): + 'Determine if the loop is large enough.' + return euclidean.getMaximumSpan(loop) > 2.01 * abs(radius) + +def getLargestCenterOutsetLoopFromLoop(loop, radius, thresholdRatio=0.9): + 'Get the largest circle outset loop from the loop.' + if radius == 0.0: + return loop + radius = abs(radius) + points = getPointsFromLoop(loop, radius, thresholdRatio) + centers = getCentersFromPoints(points, globalIntercircleMultiplier * radius) + largestCenterOutset = None + largestOutsetArea = -987654321.0 + for center in centers: + outset = getSimplifiedInsetFromClockwiseLoop(center, radius) + if isLargeSameDirection(outset, center, radius): + if euclidean.isPathInsideLoop(loop, outset) != euclidean.isWiddershins(loop): + centerOutset = CenterOutset(center, outset) + outsetArea = abs(euclidean.getAreaLoop(outset)) + if outsetArea > largestOutsetArea: + largestOutsetArea = outsetArea + largestCenterOutset = centerOutset + if largestCenterOutset == None: + return None + largestCenterOutset.center = euclidean.getSimplifiedLoop(largestCenterOutset.center, radius) + return largestCenterOutset + +def getLargestCenterOutsetLoopFromLoopRegardless(loop, radius): + 'Get the largest circle outset loop from the loop, even if the radius has to be shrunk and even if there is still no outset loop.' + global globalDecreasingRadiusMultipliers + for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers: + decreasingRadius = radius * decreasingRadiusMultiplier + largestCenterOutsetLoop = getLargestCenterOutsetLoopFromLoop(loop, decreasingRadius) + if largestCenterOutsetLoop != None: + return largestCenterOutsetLoop + return CenterOutset(loop, loop) + +def getLargestInsetLoopFromLoop(loop, radius): + 'Get the largest inset loop from the loop.' + loops = getInsetLoopsFromLoop(loop, radius) + return euclidean.getLargestLoop(loops) + +def getLargestInsetLoopFromLoopRegardless( loop, radius ): + 'Get the largest inset loop from the loop, even if the radius has to be shrunk and even if there is still no inset loop.' + global globalDecreasingRadiusMultipliers + for decreasingRadiusMultiplier in globalDecreasingRadiusMultipliers: + decreasingRadius = radius * decreasingRadiusMultiplier + largestInsetLoop = getLargestInsetLoopFromLoop( loop, decreasingRadius ) + if len( largestInsetLoop ) > 0: + return largestInsetLoop + print('This should never happen, there should always be a largestInsetLoop in getLargestInsetLoopFromLoopRegardless in intercircle.') + print(loop) + return loop + +def getLoopsFromLoopsDirection( isWiddershins, loops ): + 'Get the loops going round in a given direction.' + directionalLoops = [] + for loop in loops: + if euclidean.isWiddershins(loop) == isWiddershins: + directionalLoops.append(loop) + return directionalLoops + +def getPointsFromLoop(loop, radius, thresholdRatio=0.9): + 'Get the points from every point on a loop and between points.' + if radius == 0.0: + print('Warning, radius is 0 in getPointsFromLoop in intercircle.') + print(loop) + return loop + radius = abs(radius) + points = [] + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + points.append( pointBegin ) + addPointsFromSegment( pointBegin, pointEnd, points, radius, thresholdRatio ) + return points + +def getPointsFromLoops(loops, radius, thresholdRatio=0.9): + 'Get the points from every point on a loop and between points.' + points = [] + for loop in loops: + points += getPointsFromLoop(loop, radius, thresholdRatio) + return points + +def getPointsFromPath(path, radius, thresholdRatio=0.9): + 'Get the points from every point on a path and between points.' + if len(path) < 1: + return [] + if len(path) < 2: + return path + radius = abs(radius) + points = [] + addHalfPath(path, points, radius, thresholdRatio) + addHalfPath(path[: : -1], points, radius, thresholdRatio) + return points + +def getSimplifiedInsetFromClockwiseLoop(loop, radius): + 'Get loop inset from clockwise loop, out from widdershins loop.' + inset = [] + for pointIndex, begin in enumerate(loop): + center = loop[(pointIndex + 1) % len(loop)] + end = loop[(pointIndex + 2) % len(loop)] + addInsetPointFromClockwiseTriple(begin, center, end, inset, radius) + return getWithoutIntersections(euclidean.getSimplifiedLoop(inset, radius)) + +def getWiddershinsByLength(begin, end, length): + 'Get the widdershins by length.' + endMinusBegin = end - begin + endMinusBeginLength = abs(endMinusBegin) + if endMinusBeginLength <= 0.0: + return None + endMinusBegin *= length / endMinusBeginLength + return complex(-endMinusBegin.imag, endMinusBegin.real) + +def getWithoutIntersections( loop ): + 'Get loop without intersections.' + lastLoopLength = len( loop ) + while lastLoopLength > 3: + removeIntersection( loop ) + if len( loop ) == lastLoopLength: + return loop + lastLoopLength = len( loop ) + return loop + +def isLargeSameDirection(inset, loop, radius): + 'Determine if the inset is in the same direction as the loop and it is large enough.' + if euclidean.isWiddershins(inset) != euclidean.isWiddershins(loop): + return False + return getIsLarge(inset, radius) and len(inset) > 2 + +def isLoopIntersectingLoop( anotherLoop, loop ): + 'Determine if the a loop is intersecting another loop.' + for pointIndex in xrange(len(loop)): + pointFirst = loop[pointIndex] + pointSecond = loop[(pointIndex + 1) % len(loop)] + segment = pointFirst - pointSecond + normalizedSegment = euclidean.getNormalized(segment) + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + segmentFirstPoint = segmentYMirror * pointFirst + segmentSecondPoint = segmentYMirror * pointSecond + if euclidean.isLoopIntersectingInsideXSegment( anotherLoop, segmentFirstPoint.real, segmentSecondPoint.real, segmentYMirror, segmentFirstPoint.imag ): + return True + return False + +def orbitsAreLarge( loop, temperatureChangeTime ): + 'Determine if the orbits are large enough.' + if len(loop) < 1: + print('Zero length loop which was skipped over, this should never happen.') + return False + return temperatureChangeTime > 1.5 + +def removeIntersection( loop ): + 'Get loop without the first intersection.' + for pointIndex, ahead in enumerate(loop): + behind = loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] + behindEnd = loop[ ( pointIndex + len( loop ) - 2 ) % len( loop ) ] + behindMidpoint = 0.5 * ( behind + behindEnd ) + aheadEnd = loop[ (pointIndex + 1) % len( loop ) ] + aheadMidpoint = 0.5 * ( ahead + aheadEnd ) + normalizedSegment = behind - behindMidpoint + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength > 0.0: + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + behindRotated = segmentYMirror * behind + behindMidpointRotated = segmentYMirror * behindMidpoint + aheadRotated = segmentYMirror * ahead + aheadMidpointRotated = segmentYMirror * aheadMidpoint + y = behindRotated.imag + xIntersection = euclidean.getXIntersectionIfExists( aheadRotated, aheadMidpointRotated, y ) + if xIntersection != None: + if xIntersection > min( behindMidpointRotated.real, behindRotated.real ) and xIntersection < max( behindMidpointRotated.real, behindRotated.real ): + intersectionPoint = normalizedSegment * complex( xIntersection, y ) + loop[ ( pointIndex + len( loop ) - 1 ) % len( loop ) ] = intersectionPoint + del loop[pointIndex] + return + + +class BoundingLoop: + 'A class to hold a bounding loop composed of a minimum complex, a maximum complex and an outset loop.' + def __eq__(self, other): + 'Determine whether this bounding loop is identical to other one.' + if other == None: + return False + return self.minimum == other.minimum and self.maximum == other.maximum and self.loop == other.loop + + def __repr__(self): + 'Get the string representation of this bounding loop.' + return '%s, %s, %s' % ( self.minimum, self.maximum, self.loop ) + + def getFromLoop( self, loop ): + 'Get the bounding loop from a path.' + self.loop = loop + self.maximum = euclidean.getMaximumByComplexPath(loop) + self.minimum = euclidean.getMinimumByComplexPath(loop) + return self + + def getOutsetBoundingLoop( self, outsetDistance ): + 'Outset the bounding rectangle and loop by a distance.' + outsetBoundingLoop = BoundingLoop() + outsetBoundingLoop.maximum = self.maximum + complex( outsetDistance, outsetDistance ) + outsetBoundingLoop.minimum = self.minimum - complex( outsetDistance, outsetDistance ) + greaterThanOutsetDistance = 1.1 * outsetDistance + centers = getCentersFromLoopDirection( True, self.loop, greaterThanOutsetDistance ) + outsetBoundingLoop.loop = getSimplifiedInsetFromClockwiseLoop( centers[0], outsetDistance ) + return outsetBoundingLoop + + def isEntirelyInsideAnother( self, anotherBoundingLoop ): + 'Determine if this bounding loop is entirely inside another bounding loop.' + if self.minimum.imag < anotherBoundingLoop.minimum.imag or self.minimum.real < anotherBoundingLoop.minimum.real: + return False + if self.maximum.imag > anotherBoundingLoop.maximum.imag or self.maximum.real > anotherBoundingLoop.maximum.real: + return False + for point in self.loop: + if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 0: + return False + return not isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles + + def isOverlappingAnother( self, anotherBoundingLoop ): + 'Determine if this bounding loop is intersecting another bounding loop.' + if self.isRectangleMissingAnother( anotherBoundingLoop ): + return False + for point in self.loop: + if euclidean.getNumberOfIntersectionsToLeft( anotherBoundingLoop.loop, point ) % 2 == 1: + return True + for point in anotherBoundingLoop.loop: + if euclidean.getNumberOfIntersectionsToLeft( self.loop, point ) % 2 == 1: + return True + return isLoopIntersectingLoop( anotherBoundingLoop.loop, self.loop ) #later check for intersection on only acute angles + + def isOverlappingAnotherInList( self, boundingLoops ): + 'Determine if this bounding loop is intersecting another bounding loop in a list.' + for boundingLoop in boundingLoops: + if self.isOverlappingAnother( boundingLoop ): + return True + return False + + def isRectangleMissingAnother( self, anotherBoundingLoop ): + 'Determine if the rectangle of this bounding loop is missing the rectangle of another bounding loop.' + if self.maximum.imag < anotherBoundingLoop.minimum.imag or self.maximum.real < anotherBoundingLoop.minimum.real: + return True + return self.minimum.imag > anotherBoundingLoop.maximum.imag or self.minimum.real > anotherBoundingLoop.maximum.real + + +class CenterOutset: + 'A class to hold a center and an outset.' + def __init__(self, center, outset): + 'Set the center and outset.' + self.center = center + self.outset = outset + + def __repr__(self): + 'Get the string representation of this CenterOutset.' + return '%s\n%s' % (self.center, self.outset) + + +class CircleIntersection: + 'An intersection of two complex circles.' + def __init__( self, circleNodeAhead, index, circleNodeBehind ): + self.aheadMinusBehind = 0.5 * ( circleNodeAhead.dividedPoint - circleNodeBehind.dividedPoint ) + self.circleNodeAhead = circleNodeAhead + self.circleNodeBehind = circleNodeBehind + self.index = index + self.steppedOn = False + demichordWidth = math.sqrt( 1.0 - self.aheadMinusBehind.real * self.aheadMinusBehind.real - self.aheadMinusBehind.imag * self.aheadMinusBehind.imag ) + rotatedClockwiseQuarter = complex( self.aheadMinusBehind.imag, - self.aheadMinusBehind.real ) + rotatedClockwiseQuarterLength = abs( rotatedClockwiseQuarter ) + if rotatedClockwiseQuarterLength == 0: + print('Warning, rotatedClockwiseQuarter in getDemichord in intercircle is 0') + print(circleNodeAhead.dividedPoint) + print(circleNodeBehind.dividedPoint) + self.demichord = 0.0 + else: + self.demichord = rotatedClockwiseQuarter * demichordWidth / rotatedClockwiseQuarterLength + self.positionRelativeToBehind = self.aheadMinusBehind + self.demichord + + def __repr__(self): + 'Get the string representation of this CircleIntersection.' + return '%s, %s, %s, %s' % (self.index, self.getAbsolutePosition(), self.circleNodeBehind, self.circleNodeAhead) + + def addToList( self, circleIntersectionPath ): + 'Add this to the circle intersection path, setting stepped on to be true.' + self.steppedOn = True + circleIntersectionPath.append(self) + + def getAbsolutePosition(self): + 'Get the absolute position.' + return self.positionRelativeToBehind + self.circleNodeBehind.dividedPoint + + def getCircleIntersectionAhead(self): + 'Get the first circle intersection on the circle node ahead.' + circleIntersections = self.circleNodeAhead.circleIntersections + circleIntersectionAhead = None + largestDot = -912345678.0 + for circleIntersection in circleIntersections: + if not circleIntersection.steppedOn: + circleIntersectionRelativeToMidpoint = euclidean.getNormalized(circleIntersection.positionRelativeToBehind + self.aheadMinusBehind) + dot = euclidean.getDotProduct(self.demichord, circleIntersectionRelativeToMidpoint) + if dot > largestDot: + largestDot = dot + circleIntersectionAhead = circleIntersection + if circleIntersectionAhead == None: + print('Warning, circleIntersectionAhead in getCircleIntersectionAhead in intercircle is None for:') + print(self.circleNodeAhead.dividedPoint) + print('circleIntersectionsAhead') + for circleIntersection in circleIntersections: + print(circleIntersection.circleNodeAhead.dividedPoint) + print('circleIntersectionsBehind') + for circleIntersection in self.circleNodeBehind.circleIntersections: + print(circleIntersection.circleNodeAhead.dividedPoint) + print('This may lead to a loop not being sliced.') + print('If this is a problem, you may as well send a bug report, even though I probably can not fix this particular problem.') + return circleIntersectionAhead + + def isWithinCircles(self, pixelTable): + 'Determine if this circle intersection is within the circle node circles.' + absolutePosition = self.getAbsolutePosition() + squareValues = euclidean.getSquareValuesFromPoint(pixelTable, absolutePosition) + for squareValue in squareValues: + if abs(squareValue.dividedPoint - absolutePosition) < 1.0: + if squareValue != self.circleNodeAhead and squareValue != self.circleNodeBehind: + return True + return False + + +class CircleNode: + 'A complex node of complex circle intersections.' + def __init__(self, oneOverRadius, point): + self.actualPoint = point + self.circleIntersections = [] + self.dividedPoint = point * oneOverRadius +# self.index = index # when debugging bring back index + + def __repr__(self): + 'Get the string representation of this CircleNode.' +# return '%s, %s, %s' % (self.index, self.dividedPoint, len(self.circleIntersections)) # when debugging bring back index + return '%s, %s' % (self.dividedPoint, len(self.circleIntersections)) + + def getWithinNodes(self, pixelTable): + 'Get the nodes this circle node is within.' + withinNodes = [] + squareValues = euclidean.getSquareValuesFromPoint(pixelTable, 0.5 * self.dividedPoint) + for squareValue in squareValues: + if abs(self.dividedPoint - squareValue.dividedPoint) < 2.0: + withinNodes.append(squareValue) + return withinNodes diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py b/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py new file mode 100644 index 0000000..802994b --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/settings.py @@ -0,0 +1,216 @@ +""" +Settings is a collection of utilities to display, read & write the settings and position widgets. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +import ConfigParser +import os + +def loadGlobalConfig(filename): + "Read a configuration file as global config" + global globalConfigParser + globalConfigParser = ConfigParser.ConfigParser() + print globalConfigParser.read(filename) + +def getDefaultConfigPath(): + return os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../current_config.ini")) + +def safeConfigName(name): + return name.replace("=", "").replace(":", "").replace(" ", "_").replace("(", "").replace(")", "") + +def getReadRepository(repository): + "Read the configuration for this 'repository'" + + #Check if we have a configuration file loaded, else load the default. + if not globals().has_key('globalConfigParser'): + loadGlobalConfig(getDefaultConfigPath()) + + print('getReadRepository:', repository.name) + for p in repository.preferences: + try: + p.setValueToString(globalConfigParser.get(repository.name, safeConfigName(p.name))) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + #Value not in configuration, add it. + try: + globalConfigParser.add_section(repository.name) + except: + pass + globalConfigParser.set(repository.name, safeConfigName(p.name), str(p.value)) + globalConfigParser.write(open(getDefaultConfigPath(), 'w')) + #print "============" + str(p) + "|" + p.name + "|" + str(p.value) + "|" + str(type(p.value)) + return repository + +def printProgress(layerIndex, procedureName): + print("Progress: ", procedureName, layerIndex) +def printProgressByNumber(layerIndex, numberOfLayers, procedureName): + print("Progress: ", procedureName, layerIndex, numberOfLayers) + +def getAlterationFileLines(fileName): + 'Get the alteration file line and the text lines from the fileName in the alterations directories.' + print ('getAlterationFileLines:', fileName) + return [] +def getAlterationLines(fileName): + print ('getAlterationLines:', fileName) + return [] + +#################################### +## Configuration settings classes ## +#################################### + +class GeneralSetting: + "Just a basic setting subclass" + def getFromValue( self, name, repository, value ): + #print('GeneralSetting:', name, repository, value ) + self.name = name + self.value = value + repository.preferences.append(self) + return self + +class StringSetting(GeneralSetting): + "A class to display, read & write a string." + def setValueToString(self, value): + self.value = value + +class BooleanSetting( GeneralSetting ): + "A class to display, read & write a boolean." + def setValueToString(self, value): + self.value = value == "True" + +class LatentStringVar: + "This is actually used as 'group' object for Radio buttons. (Did I mention the code is a mess?)" + "This class doesn't have a name, and isn't really used for anything. It doesn't even know which repository it belongs to" + +class Radio( BooleanSetting ): + "A class to display, read & write a boolean with associated radio button." + def getFromRadio( self, latentStringVar, name, repository, value ): + "Initialize." + #print('Radio->getFromRadio:', latentStringVar, name, repository, value ) + self.name = name + self.value = value + repository.preferences.append(self) + return self + +class RadioCapitalized( Radio ): + "A class to display, read & write a boolean with associated radio button." + +class RadioCapitalizedButton( Radio ): + "A class to display, read & write a boolean with associated radio button. With an added configuration dialog button" + "Only used for the extra export options, which we are not using, so ignore the path for now" + def getFromPath( self, latentStringVar, name, path, repository, value ): + "Initialize." + print('RadioCapitalizedButton->getFromPath:', latentStringVar, name, path, repository, value ) + self.name = name + self.value = value + repository.preferences.append(self) + return self + +class FileNameInput(StringSetting ): + "A class to display, read & write a fileName." + def getFromFileName( self, fileTypes, name, repository, value ): + #print('FileNameInput:getFromFileName:', self, fileTypes, name, repository, value ) + repository.preferences.append(self) + self.name = name + self.value = value + return self + +class HelpPage: + "A class to open a help page." + def getOpenFromAbsolute( self, hypertextAddress ): + return self + +class MenuButtonDisplay: + "A class to add a combo box selection." + def getFromName( self, name, repository ): + #print('MenuButtonDisplay->getFromName:', name, repository ) + self.name = name + self.value = "ERROR" + self.radioList = [] + repository.preferences.append(self) + return self + + def addRadio(self, radio, default): + if default: + self.value = radio.name + self.radioList.append(radio) + + def setValueToString(self, value): + valueFound = False + for radio in self.radioList: + if radio.name == value: + valueFound = True; + if valueFound: + self.value = value + for radio in self.radioList: + radio.value = (radio.name == value) + +class MenuRadio( BooleanSetting ): + "A class to display, read & write a boolean with associated combo box selection." + def getFromMenuButtonDisplay( self, menuButtonDisplay, name, repository, value ): + "Initialize." + print('MenuRadio->getFromMenuButtonDisplay:', menuButtonDisplay, name, repository, value ) + self.name = name + self.value = value + menuButtonDisplay.addRadio(self, value) + return self + +class LabelDisplay: + "A class to add a label." + def getFromName( self, name, repository ): + "Initialize." + return self + +class FloatSetting(GeneralSetting): + "A class to display, read & write a float." + def setValueToString(self, value): + self.value = float(value) + +class FloatSpin( FloatSetting ): + "A class to display, read & write an float in a spin box." + def getFromValue(self, from_, name, repository, to, value): + "Initialize." + self.name = name + self.value = value + repository.preferences.append(self) + return self + +class LabelSeparator: + "A class to add a label and menu separator." + def getFromRepository( self, repository ): + "Initialize." + return self + +class IntSpin(FloatSpin): + "A class to display, read & write an int in a spin box." + def getSingleIncrementFromValue( self, from_, name, repository, to, value ): + "Initialize." + self.name = name + self.value = value + repository.preferences.append(self) + return self + + def setValueToString(self, value): + self.value = int(value) + +########################## +# Helper classes +########################## + +class LayerCount: + 'A class to handle the layerIndex.' + def __init__(self): + 'Initialize.' + self.layerIndex = -1 + + def __repr__(self): + 'Get the string representation of this LayerCount.' + return str(self.layerIndex) + + def printProgressIncrement(self, procedureName): + 'Print progress then increment layerIndex.' + self.layerIndex += 1 + printProgress(self.layerIndex, procedureName) + diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/svg_reader.py b/SkeinPyPy_NewUI/fabmetheus_utilities/svg_reader.py new file mode 100644 index 0000000..9633852 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/svg_reader.py @@ -0,0 +1,955 @@ +""" +Svg reader. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.xml_simple_reader import DocumentNode +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +import math +import os +import sys +import traceback + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalNumberOfCornerPoints = 11 +globalNumberOfBezierPoints = globalNumberOfCornerPoints + globalNumberOfCornerPoints +globalNumberOfCirclePoints = 4 * globalNumberOfCornerPoints + + +def addFunctionsToDictionary( dictionary, functions, prefix ): + "Add functions to dictionary." + for function in functions: + dictionary[ function.__name__[ len( prefix ) : ] ] = function + +def getArcComplexes(begin, end, largeArcFlag, radius, sweepFlag, xAxisRotation): + 'Get the arc complexes, procedure at http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes' + if begin == end: + print('Warning, begin equals end in getArcComplexes in svgReader') + print(begin) + print(end) + return [] + if radius.imag < 0.0: + print('Warning, radius.imag is less than zero in getArcComplexes in svgReader') + print(radius) + radius = complex(radius.real, abs(radius.imag)) + if radius.real < 0.0: + print('Warning, radius.real is less than zero in getArcComplexes in svgReader') + print(radius) + radius = complex(abs(radius.real), radius.imag) + if radius.imag <= 0.0: + print('Warning, radius.imag is too small for getArcComplexes in svgReader') + print(radius) + return [end] + if radius.real <= 0.0: + print('Warning, radius.real is too small for getArcComplexes in svgReader') + print(radius) + return [end] + xAxisRotationComplex = euclidean.getWiddershinsUnitPolar(xAxisRotation) + reverseXAxisRotationComplex = complex(xAxisRotationComplex.real, -xAxisRotationComplex.imag) + beginRotated = begin * reverseXAxisRotationComplex + endRotated = end * reverseXAxisRotationComplex + beginTransformed = complex(beginRotated.real / radius.real, beginRotated.imag / radius.imag) + endTransformed = complex(endRotated.real / radius.real, endRotated.imag / radius.imag) + midpointTransformed = 0.5 * (beginTransformed + endTransformed) + midMinusBeginTransformed = midpointTransformed - beginTransformed + midMinusBeginTransformedLength = abs(midMinusBeginTransformed) + if midMinusBeginTransformedLength > 1.0: + print('The ellipse radius is too small for getArcComplexes in svgReader.') + print('So the ellipse will be scaled to fit, according to the formulas in "Step 3: Ensure radii are large enough" of:') + print('http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii') + print('') + radius *= midMinusBeginTransformedLength + beginTransformed /= midMinusBeginTransformedLength + endTransformed /= midMinusBeginTransformedLength + midpointTransformed /= midMinusBeginTransformedLength + midMinusBeginTransformed /= midMinusBeginTransformedLength + midMinusBeginTransformedLength = 1.0 + midWiddershinsTransformed = complex(-midMinusBeginTransformed.imag, midMinusBeginTransformed.real) + midWiddershinsLengthSquared = 1.0 - midMinusBeginTransformedLength * midMinusBeginTransformedLength + if midWiddershinsLengthSquared < 0.0: + midWiddershinsLengthSquared = 0.0 + midWiddershinsLength = math.sqrt(midWiddershinsLengthSquared) + midWiddershinsTransformed *= midWiddershinsLength / abs(midWiddershinsTransformed) + centerTransformed = midpointTransformed + if largeArcFlag == sweepFlag: + centerTransformed -= midWiddershinsTransformed + else: + centerTransformed += midWiddershinsTransformed + beginMinusCenterTransformed = beginTransformed - centerTransformed + beginMinusCenterTransformedLength = abs(beginMinusCenterTransformed) + if beginMinusCenterTransformedLength <= 0.0: + return end + beginAngle = math.atan2(beginMinusCenterTransformed.imag, beginMinusCenterTransformed.real) + endMinusCenterTransformed = endTransformed - centerTransformed + angleDifference = euclidean.getAngleDifferenceByComplex(endMinusCenterTransformed, beginMinusCenterTransformed) + if sweepFlag: + if angleDifference < 0.0: + angleDifference += 2.0 * math.pi + else: + if angleDifference > 0.0: + angleDifference -= 2.0 * math.pi + global globalSideAngle + sides = int(math.ceil(abs(angleDifference) / globalSideAngle)) + sideAngle = angleDifference / float(sides) + arcComplexes = [] + center = complex(centerTransformed.real * radius.real, centerTransformed.imag * radius.imag) * xAxisRotationComplex + for side in xrange(1, sides): + unitPolar = euclidean.getWiddershinsUnitPolar(beginAngle + float(side) * sideAngle) + circumferential = complex(unitPolar.real * radius.real, unitPolar.imag * radius.imag) * beginMinusCenterTransformedLength + point = center + circumferential * xAxisRotationComplex + arcComplexes.append(point) + arcComplexes.append(end) + return arcComplexes + +def getChainMatrixSVG(elementNode, matrixSVG): + "Get chain matrixSVG by svgElement." + matrixSVG = matrixSVG.getOtherTimesSelf(getMatrixSVG(elementNode).tricomplex) + if elementNode.parentNode != None: + matrixSVG = getChainMatrixSVG(elementNode.parentNode, matrixSVG) + return matrixSVG + +def getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward): + "Get chain matrixSVG by svgElement and yAxisPointingUpward." + matrixSVG = MatrixSVG() + if yAxisPointingUpward: + return matrixSVG + return getChainMatrixSVG(elementNode, matrixSVG) + +def getCubicPoint( along, begin, controlPoints, end ): + 'Get the cubic point.' + segmentBegin = getQuadraticPoint( along, begin, controlPoints[0], controlPoints[1] ) + segmentEnd = getQuadraticPoint( along, controlPoints[0], controlPoints[1], end ) + return ( 1.0 - along ) * segmentBegin + along * segmentEnd + +def getCubicPoints( begin, controlPoints, end, numberOfBezierPoints=globalNumberOfBezierPoints): + 'Get the cubic points.' + bezierPortion = 1.0 / float(numberOfBezierPoints) + cubicPoints = [] + for bezierIndex in xrange( 1, numberOfBezierPoints + 1 ): + cubicPoints.append(getCubicPoint(bezierPortion * bezierIndex, begin, controlPoints, end)) + return cubicPoints + +def getFontReader(fontFamily): + 'Get the font reader for the fontFamily.' + fontLower = fontFamily.lower().replace(' ', '_') + global globalFontReaderDictionary + if fontLower in globalFontReaderDictionary: + return globalFontReaderDictionary[fontLower] + global globalFontFileNames + if globalFontFileNames == None: + globalFontFileNames = archive.getFileNamesByFilePaths(archive.getFilePathsByDirectory(getFontsDirectoryPath())) + if fontLower not in globalFontFileNames: + print('Warning, the %s font was not found in the fabmetheus_utilities/fonts folder, so Gentium Basic Regular will be substituted.' % fontFamily) + print('The available fonts are:') + globalFontFileNames.sort() + print(globalFontFileNames) + print('') + fontLower = 'gentium_basic_regular' + fontReader = FontReader(fontLower) + globalFontReaderDictionary[fontLower] = fontReader + return fontReader + +def getFontsDirectoryPath(): + "Get the fonts directory path." + return archive.getFabmetheusUtilitiesPath('fonts') + +def getLabelString(dictionary): + "Get the label string for the dictionary." + for key in dictionary: + labelIndex = key.find('label') + if labelIndex >= 0: + return dictionary[key] + return '' + +def getMatrixSVG(elementNode): + "Get matrixSVG by svgElement." + matrixSVG = MatrixSVG() + if 'transform' not in elementNode.attributes: + return matrixSVG + transformWords = [] + for transformWord in elementNode.attributes['transform'].replace(')', '(').split('('): + transformWordStrip = transformWord.strip() + if transformWordStrip != '': # workaround for split(character) bug which leaves an extra empty element + transformWords.append(transformWordStrip) + global globalGetTricomplexDictionary + getTricomplexDictionaryKeys = globalGetTricomplexDictionary.keys() + for transformWordIndex, transformWord in enumerate(transformWords): + if transformWord in getTricomplexDictionaryKeys: + transformString = transformWords[transformWordIndex + 1].replace(',', ' ') + matrixSVG = matrixSVG.getSelfTimesOther(globalGetTricomplexDictionary[ transformWord ](transformString.split())) + return matrixSVG + +def getQuadraticPoint( along, begin, controlPoint, end ): + 'Get the quadratic point.' + oneMinusAlong = 1.0 - along + segmentBegin = oneMinusAlong * begin + along * controlPoint + segmentEnd = oneMinusAlong * controlPoint + along * end + return oneMinusAlong * segmentBegin + along * segmentEnd + +def getQuadraticPoints(begin, controlPoint, end, numberOfBezierPoints=globalNumberOfBezierPoints): + 'Get the quadratic points.' + bezierPortion = 1.0 / float(numberOfBezierPoints) + quadraticPoints = [] + for bezierIndex in xrange(1, numberOfBezierPoints + 1): + quadraticPoints.append(getQuadraticPoint(bezierPortion * bezierIndex, begin, controlPoint, end)) + return quadraticPoints + +def getRightStripAlphabetPercent(word): + "Get word with alphabet characters and the percent sign stripped from the right." + word = word.strip() + for characterIndex in xrange(len(word) - 1, -1, -1): + character = word[characterIndex] + if not character.isalpha() and not character == '%': + return float(word[: characterIndex + 1]) + return None + +def getRightStripMinusSplit(lineString): + "Get string with spaces after the minus sign stripped." + oldLineStringLength = -1 + while oldLineStringLength < len(lineString): + oldLineStringLength = len(lineString) + lineString = lineString.replace('- ', '-') + return lineString.split() + +def getStrokeRadius(elementNode): + "Get the stroke radius." + return 0.5 * getRightStripAlphabetPercent(getStyleValue('1.0', elementNode, 'stroke-width')) + +def getStyleValue(defaultValue, elementNode, key): + "Get the stroke value string." + if 'style' in elementNode.attributes: + line = elementNode.attributes['style'] + strokeIndex = line.find(key) + if strokeIndex > -1: + words = line[strokeIndex :].replace(':', ' ').replace(';', ' ').split() + if len(words) > 1: + return words[1] + if key in elementNode.attributes: + return elementNode.attributes[key] + if elementNode.parentNode == None: + return defaultValue + return getStyleValue(defaultValue, elementNode.parentNode, key) + +def getTextComplexLoops(fontFamily, fontSize, text, yAxisPointingUpward=True): + "Get text as complex loops." + textComplexLoops = [] + fontReader = getFontReader(fontFamily) + horizontalAdvanceX = 0.0 + for character in text: + glyph = fontReader.getGlyph(character, yAxisPointingUpward) + textComplexLoops += glyph.getSizedAdvancedLoops(fontSize, horizontalAdvanceX, yAxisPointingUpward) + horizontalAdvanceX += glyph.horizontalAdvanceX + return textComplexLoops + +def getTransformedFillOutline(elementNode, loop, yAxisPointingUpward): + "Get the loops if fill is on, otherwise get the outlines." + fillOutlineLoops = None + if getStyleValue('none', elementNode, 'fill').lower() == 'none': + fillOutlineLoops = intercircle.getAroundsFromLoop(loop, getStrokeRadius(elementNode)) + else: + fillOutlineLoops = [loop] + return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(fillOutlineLoops) + +def getTransformedOutlineByPath(elementNode, path, yAxisPointingUpward): + "Get the outline from the path." + aroundsFromPath = intercircle.getAroundsFromPath(path, getStrokeRadius(elementNode)) + return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(aroundsFromPath) + +def getTransformedOutlineByPaths(elementNode, paths, yAxisPointingUpward): + "Get the outline from the paths." + aroundsFromPaths = intercircle.getAroundsFromPaths(paths, getStrokeRadius(elementNode)) + return getChainMatrixSVGIfNecessary(elementNode, yAxisPointingUpward).getTransformedPaths(aroundsFromPaths) + +def getTricomplexmatrix(transformWords): + "Get matrixSVG by transformWords." + tricomplex = [euclidean.getComplexByWords(transformWords)] + tricomplex.append(euclidean.getComplexByWords(transformWords, 2)) + tricomplex.append(euclidean.getComplexByWords(transformWords, 4)) + return tricomplex + +def getTricomplexrotate(transformWords): + "Get matrixSVG by transformWords." + rotate = euclidean.getWiddershinsUnitPolar(math.radians(float(transformWords[0]))) + return [rotate, complex(-rotate.imag,rotate.real), complex()] + +def getTricomplexscale(transformWords): + "Get matrixSVG by transformWords." + scale = euclidean.getComplexByWords(transformWords) + return [complex(scale.real,0.0), complex(0.0,scale.imag), complex()] + +def getTricomplexskewX(transformWords): + "Get matrixSVG by transformWords." + skewX = math.tan(math.radians(float(transformWords[0]))) + return [complex(1.0, 0.0), complex(skewX, 1.0), complex()] + +def getTricomplexskewY(transformWords): + "Get matrixSVG by transformWords." + skewY = math.tan(math.radians(float(transformWords[0]))) + return [complex(1.0, skewY), complex(0.0, 1.0), complex()] + +def getTricomplexTimesColumn(firstTricomplex, otherColumn): + "Get this matrix multiplied by the otherColumn." + dotProductX = firstTricomplex[0].real * otherColumn.real + firstTricomplex[1].real * otherColumn.imag + dotProductY = firstTricomplex[0].imag * otherColumn.real + firstTricomplex[1].imag * otherColumn.imag + return complex(dotProductX, dotProductY) + +def getTricomplexTimesOther(firstTricomplex, otherTricomplex): + "Get the first tricomplex multiplied by the other tricomplex." + #A down, B right from http://en.wikipedia.org/wiki/Matrix_multiplication + tricomplexTimesOther = [getTricomplexTimesColumn(firstTricomplex, otherTricomplex[0])] + tricomplexTimesOther.append(getTricomplexTimesColumn(firstTricomplex, otherTricomplex[1])) + tricomplexTimesOther.append(getTricomplexTimesColumn(firstTricomplex, otherTricomplex[2]) + firstTricomplex[2]) + return tricomplexTimesOther + +def getTricomplextranslate(transformWords): + "Get matrixSVG by transformWords." + translate = euclidean.getComplexByWords(transformWords) + return [complex(1.0, 0.0), complex(0.0, 1.0), translate] + +def processSVGElementcircle( elementNode, svgReader ): + "Process elementNode by svgReader." + attributes = elementNode.attributes + center = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'cx', 'cy') + radius = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'r') + if radius == 0.0: + print('Warning, in processSVGElementcircle in svgReader radius is zero in:') + print(attributes) + return + global globalNumberOfCirclePoints + global globalSideAngle + loop = [] + loopLayer = svgReader.getLoopLayer() + for side in xrange( globalNumberOfCirclePoints ): + unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle ) + loop.append( center + radius * unitPolar ) + loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward) + +def processSVGElementellipse( elementNode, svgReader ): + "Process elementNode by svgReader." + attributes = elementNode.attributes + center = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'cx', 'cy') + radius = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'rx', 'ry') + if radius.real == 0.0 or radius.imag == 0.0: + print('Warning, in processSVGElementellipse in svgReader radius is zero in:') + print(attributes) + return + global globalNumberOfCirclePoints + global globalSideAngle + loop = [] + loopLayer = svgReader.getLoopLayer() + for side in xrange( globalNumberOfCirclePoints ): + unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle ) + loop.append( center + complex( unitPolar.real * radius.real, unitPolar.imag * radius.imag ) ) + loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward) + +def processSVGElementg(elementNode, svgReader): + 'Process elementNode by svgReader.' + if 'id' not in elementNode.attributes: + return + idString = elementNode.attributes['id'] + if 'beginningOfControlSection' in elementNode.attributes: + if elementNode.attributes['beginningOfControlSection'].lower()[: 1] == 't': + svgReader.stopProcessing = True + return + idStringLower = idString.lower() + zIndex = idStringLower.find('z:') + if zIndex < 0: + idStringLower = getLabelString(elementNode.attributes) + zIndex = idStringLower.find('z:') + if zIndex < 0: + return + floatFromValue = euclidean.getFloatFromValue(idStringLower[zIndex + len('z:') :].strip()) + if floatFromValue != None: + svgReader.z = floatFromValue + +def processSVGElementline(elementNode, svgReader): + "Process elementNode by svgReader." + begin = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x1', 'y1') + end = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x2', 'y2') + loopLayer = svgReader.getLoopLayer() + loopLayer.loops += getTransformedOutlineByPath(elementNode, [begin, end], svgReader.yAxisPointingUpward) + +def processSVGElementpath( elementNode, svgReader ): + "Process elementNode by svgReader." + if 'd' not in elementNode.attributes: + print('Warning, in processSVGElementpath in svgReader can not get a value for d in:') + print(elementNode.attributes) + return + loopLayer = svgReader.getLoopLayer() + PathReader(elementNode, loopLayer.loops, svgReader.yAxisPointingUpward) + +def processSVGElementpolygon( elementNode, svgReader ): + "Process elementNode by svgReader." + if 'points' not in elementNode.attributes: + print('Warning, in processSVGElementpolygon in svgReader can not get a value for d in:') + print(elementNode.attributes) + return + loopLayer = svgReader.getLoopLayer() + words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' ')) + loop = [] + for wordIndex in xrange( 0, len(words), 2 ): + loop.append(euclidean.getComplexByWords(words[wordIndex :])) + loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward) + +def processSVGElementpolyline(elementNode, svgReader): + "Process elementNode by svgReader." + if 'points' not in elementNode.attributes: + print('Warning, in processSVGElementpolyline in svgReader can not get a value for d in:') + print(elementNode.attributes) + return + loopLayer = svgReader.getLoopLayer() + words = getRightStripMinusSplit(elementNode.attributes['points'].replace(',', ' ')) + path = [] + for wordIndex in xrange(0, len(words), 2): + path.append(euclidean.getComplexByWords(words[wordIndex :])) + loopLayer.loops += getTransformedOutlineByPath(elementNode, path, svgReader.yAxisPointingUpward) + +def processSVGElementrect( elementNode, svgReader ): + "Process elementNode by svgReader." + attributes = elementNode.attributes + height = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'height') + if height == 0.0: + print('Warning, in processSVGElementrect in svgReader height is zero in:') + print(attributes) + return + width = euclidean.getFloatDefaultByDictionary( 0.0, attributes, 'width') + if width == 0.0: + print('Warning, in processSVGElementrect in svgReader width is zero in:') + print(attributes) + return + center = euclidean.getComplexDefaultByDictionaryKeys(complex(), attributes, 'x', 'y') + inradius = 0.5 * complex( width, height ) + cornerRadius = euclidean.getComplexDefaultByDictionaryKeys( complex(), attributes, 'rx', 'ry') + loopLayer = svgReader.getLoopLayer() + if cornerRadius.real == 0.0 and cornerRadius.imag == 0.0: + inradiusMinusX = complex( - inradius.real, inradius.imag ) + loop = [center + inradius, center + inradiusMinusX, center - inradius, center - inradiusMinusX] + loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward) + return + if cornerRadius.real == 0.0: + cornerRadius = complex( cornerRadius.imag, cornerRadius.imag ) + elif cornerRadius.imag == 0.0: + cornerRadius = complex( cornerRadius.real, cornerRadius.real ) + cornerRadius = complex( min( cornerRadius.real, inradius.real ), min( cornerRadius.imag, inradius.imag ) ) + ellipsePath = [ complex( cornerRadius.real, 0.0 ) ] + inradiusMinusCorner = inradius - cornerRadius + loop = [] + global globalNumberOfCornerPoints + global globalSideAngle + for side in xrange( 1, globalNumberOfCornerPoints ): + unitPolar = euclidean.getWiddershinsUnitPolar( float(side) * globalSideAngle ) + ellipsePath.append( complex( unitPolar.real * cornerRadius.real, unitPolar.imag * cornerRadius.imag ) ) + ellipsePath.append( complex( 0.0, cornerRadius.imag ) ) + cornerPoints = [] + for point in ellipsePath: + cornerPoints.append( point + inradiusMinusCorner ) + cornerPointsReversed = cornerPoints[: : -1] + for cornerPoint in cornerPoints: + loop.append( center + cornerPoint ) + for cornerPoint in cornerPointsReversed: + loop.append( center + complex( - cornerPoint.real, cornerPoint.imag ) ) + for cornerPoint in cornerPoints: + loop.append( center - cornerPoint ) + for cornerPoint in cornerPointsReversed: + loop.append( center + complex( cornerPoint.real, - cornerPoint.imag ) ) + loop = euclidean.getLoopWithoutCloseSequentialPoints( 0.0001 * abs(inradius), loop ) + loopLayer.loops += getTransformedFillOutline(elementNode, loop, svgReader.yAxisPointingUpward) + +def processSVGElementtext(elementNode, svgReader): + "Process elementNode by svgReader." + if svgReader.yAxisPointingUpward: + return + fontFamily = getStyleValue('Gentium Basic Regular', elementNode, 'font-family') + fontSize = getRightStripAlphabetPercent(getStyleValue('12.0', elementNode, 'font-size')) + matrixSVG = getChainMatrixSVGIfNecessary(elementNode, svgReader.yAxisPointingUpward) + loopLayer = svgReader.getLoopLayer() + translate = euclidean.getComplexDefaultByDictionaryKeys(complex(), elementNode.attributes, 'x', 'y') + for textComplexLoop in getTextComplexLoops(fontFamily, fontSize, elementNode.getTextContent(), svgReader.yAxisPointingUpward): + translatedLoop = [] + for textComplexPoint in textComplexLoop: + translatedLoop.append(textComplexPoint + translate ) + loopLayer.loops.append(matrixSVG.getTransformedPath(translatedLoop)) + + +class FontReader: + "Class to read a font in the fonts folder." + def __init__(self, fontFamily): + "Initialize." + self.fontFamily = fontFamily + self.glyphDictionary = {} + self.glyphElementNodeDictionary = {} + self.missingGlyph = None + fileName = os.path.join(getFontsDirectoryPath(), fontFamily + '.svg') + documentElement = DocumentNode(fileName, archive.getFileText(fileName)).getDocumentElement() + self.fontElementNode = documentElement.getFirstChildByLocalName('defs').getFirstChildByLocalName('font') + self.fontFaceElementNode = self.fontElementNode.getFirstChildByLocalName('font-face') + self.unitsPerEM = float(self.fontFaceElementNode.attributes['units-per-em']) + glyphElementNodes = self.fontElementNode.getChildElementsByLocalName('glyph') + for glyphElementNode in glyphElementNodes: + self.glyphElementNodeDictionary[glyphElementNode.attributes['unicode']] = glyphElementNode + + def getGlyph(self, character, yAxisPointingUpward): + "Get the glyph for the character." + if character not in self.glyphElementNodeDictionary: + if self.missingGlyph == None: + missingGlyphElementNode = self.fontElementNode.getFirstChildByLocalName('missing-glyph') + self.missingGlyph = Glyph(missingGlyphElementNode, self.unitsPerEM, yAxisPointingUpward) + return self.missingGlyph + if character not in self.glyphDictionary: + self.glyphDictionary[character] = Glyph(self.glyphElementNodeDictionary[character], self.unitsPerEM, yAxisPointingUpward) + return self.glyphDictionary[character] + + +class Glyph: + "Class to handle a glyph." + def __init__(self, elementNode, unitsPerEM, yAxisPointingUpward): + "Initialize." + self.horizontalAdvanceX = float(elementNode.attributes['horiz-adv-x']) + self.loops = [] + self.unitsPerEM = unitsPerEM + elementNode.attributes['fill'] = '' + if 'd' not in elementNode.attributes: + return + PathReader(elementNode, self.loops, yAxisPointingUpward) + + def getSizedAdvancedLoops(self, fontSize, horizontalAdvanceX, yAxisPointingUpward=True): + "Get loops for font size, advanced horizontally." + multiplierX = fontSize / self.unitsPerEM + multiplierY = multiplierX + if not yAxisPointingUpward: + multiplierY = -multiplierY + sizedLoops = [] + for loop in self.loops: + sizedLoop = [] + sizedLoops.append(sizedLoop) + for point in loop: + sizedLoop.append( complex(multiplierX * (point.real + horizontalAdvanceX), multiplierY * point.imag)) + return sizedLoops + + +class MatrixSVG: + "Two by three svg matrix." + def __init__(self, tricomplex=None): + "Initialize." + self.tricomplex = tricomplex + + def __repr__(self): + "Get the string representation of this two by three svg matrix." + return str(self.tricomplex) + + def getOtherTimesSelf(self, otherTricomplex): + "Get the other matrix multiplied by this matrix." + if otherTricomplex == None: + return MatrixSVG(self.tricomplex) + if self.tricomplex == None: + return MatrixSVG(otherTricomplex) + return MatrixSVG(getTricomplexTimesOther(otherTricomplex, self.tricomplex)) + + def getSelfTimesOther(self, otherTricomplex): + "Get this matrix multiplied by the other matrix." + if otherTricomplex == None: + return MatrixSVG(self.tricomplex) + if self.tricomplex == None: + return MatrixSVG(otherTricomplex) + return MatrixSVG(getTricomplexTimesOther(self.tricomplex, otherTricomplex)) + + def getTransformedPath(self, path): + "Get transformed path." + if self.tricomplex == None: + return path + complexX = self.tricomplex[0] + complexY = self.tricomplex[1] + complexTranslation = self.tricomplex[2] + transformedPath = [] + for point in path: + x = complexX.real * point.real + complexY.real * point.imag + y = complexX.imag * point.real + complexY.imag * point.imag + transformedPath.append(complex(x, y) + complexTranslation) + return transformedPath + + def getTransformedPaths(self, paths): + "Get transformed paths." + if self.tricomplex == None: + return paths + transformedPaths = [] + for path in paths: + transformedPaths.append(self.getTransformedPath(path)) + return transformedPaths + + +class PathReader: + "Class to read svg path." + def __init__(self, elementNode, loops, yAxisPointingUpward): + "Add to path string to loops." + self.controlPoints = None + self.elementNode = elementNode + self.loops = loops + self.oldPoint = None + self.outlinePaths = [] + self.path = [] + self.yAxisPointingUpward = yAxisPointingUpward + pathString = elementNode.attributes['d'].replace(',', ' ') + global globalProcessPathWordDictionary + processPathWordDictionaryKeys = globalProcessPathWordDictionary.keys() + for processPathWordDictionaryKey in processPathWordDictionaryKeys: + pathString = pathString.replace( processPathWordDictionaryKey, ' %s ' % processPathWordDictionaryKey ) + self.words = getRightStripMinusSplit(pathString) + for self.wordIndex in xrange( len( self.words ) ): + word = self.words[ self.wordIndex ] + if word in processPathWordDictionaryKeys: + globalProcessPathWordDictionary[word](self) + if len(self.path) > 0: + self.outlinePaths.append(self.path) + self.loops += getTransformedOutlineByPaths(elementNode, self.outlinePaths, yAxisPointingUpward) + + def addPathArc( self, end ): + "Add an arc to the path." + begin = self.getOldPoint() + self.controlPoints = None + radius = self.getComplexByExtraIndex(1) + xAxisRotation = math.radians(float(self.words[self.wordIndex + 3])) + largeArcFlag = euclidean.getBooleanFromValue(self.words[ self.wordIndex + 4 ]) + sweepFlag = euclidean.getBooleanFromValue(self.words[ self.wordIndex + 5 ]) + self.path += getArcComplexes(begin, end, largeArcFlag, radius, sweepFlag, xAxisRotation) + self.wordIndex += 8 + + def addPathCubic( self, controlPoints, end ): + "Add a cubic curve to the path." + begin = self.getOldPoint() + self.controlPoints = controlPoints + self.path += getCubicPoints( begin, controlPoints, end ) + self.wordIndex += 7 + + def addPathCubicReflected( self, controlPoint, end ): + "Add a cubic curve to the path from a reflected control point." + begin = self.getOldPoint() + controlPointBegin = begin + if self.controlPoints != None: + if len(self.controlPoints) == 2: + controlPointBegin = begin + begin - self.controlPoints[-1] + self.controlPoints = [controlPointBegin, controlPoint] + self.path += getCubicPoints(begin, self.controlPoints, end) + self.wordIndex += 5 + + def addPathLine(self, lineFunction, point): + "Add a line to the path." + self.controlPoints = None + self.path.append(point) + self.wordIndex += 3 + self.addPathLineByFunction(lineFunction) + + def addPathLineAxis(self, point): + "Add an axis line to the path." + self.controlPoints = None + self.path.append(point) + self.wordIndex += 2 + + def addPathLineByFunction( self, lineFunction ): + "Add a line to the path by line function." + while 1: + if self.getFloatByExtraIndex() == None: + return + self.path.append(lineFunction()) + self.wordIndex += 2 + + def addPathMove( self, lineFunction, point ): + "Add an axis line to the path." + self.controlPoints = None + if len(self.path) > 0: + self.outlinePaths.append(self.path) + self.oldPoint = self.path[-1] + self.path = [point] + self.wordIndex += 3 + self.addPathLineByFunction(lineFunction) + + def addPathQuadratic( self, controlPoint, end ): + "Add a quadratic curve to the path." + begin = self.getOldPoint() + self.controlPoints = [controlPoint] + self.path += getQuadraticPoints(begin, controlPoint, end) + self.wordIndex += 5 + + def addPathQuadraticReflected( self, end ): + "Add a quadratic curve to the path from a reflected control point." + begin = self.getOldPoint() + controlPoint = begin + if self.controlPoints != None: + if len( self.controlPoints ) == 1: + controlPoint = begin + begin - self.controlPoints[-1] + self.controlPoints = [ controlPoint ] + self.path += getQuadraticPoints(begin, controlPoint, end) + self.wordIndex += 3 + + def getComplexByExtraIndex( self, extraIndex=0 ): + 'Get complex from the extraIndex.' + return euclidean.getComplexByWords(self.words, self.wordIndex + extraIndex) + + def getComplexRelative(self): + "Get relative complex." + return self.getComplexByExtraIndex() + self.getOldPoint() + + def getFloatByExtraIndex( self, extraIndex=0 ): + 'Get float from the extraIndex.' + totalIndex = self.wordIndex + extraIndex + if totalIndex >= len(self.words): + return None + word = self.words[totalIndex] + if word[: 1].isalpha(): + return None + return euclidean.getFloatFromValue(word) + + def getOldPoint(self): + 'Get the old point.' + if len(self.path) > 0: + return self.path[-1] + return self.oldPoint + + def processPathWordA(self): + 'Process path word A.' + self.addPathArc( self.getComplexByExtraIndex( 6 ) ) + + def processPathWorda(self): + 'Process path word a.' + self.addPathArc(self.getComplexByExtraIndex(6) + self.getOldPoint()) + + def processPathWordC(self): + 'Process path word C.' + end = self.getComplexByExtraIndex( 5 ) + self.addPathCubic( [ self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ], end ) + + def processPathWordc(self): + 'Process path word C.' + begin = self.getOldPoint() + end = self.getComplexByExtraIndex( 5 ) + self.addPathCubic( [ self.getComplexByExtraIndex( 1 ) + begin, self.getComplexByExtraIndex(3) + begin ], end + begin ) + + def processPathWordH(self): + "Process path word H." + beginY = self.getOldPoint().imag + self.addPathLineAxis(complex(float(self.words[self.wordIndex + 1]), beginY)) + while 1: + floatByExtraIndex = self.getFloatByExtraIndex() + if floatByExtraIndex == None: + return + self.path.append(complex(floatByExtraIndex, beginY)) + self.wordIndex += 1 + + def processPathWordh(self): + "Process path word h." + begin = self.getOldPoint() + self.addPathLineAxis(complex(float(self.words[self.wordIndex + 1]) + begin.real, begin.imag)) + while 1: + floatByExtraIndex = self.getFloatByExtraIndex() + if floatByExtraIndex == None: + return + self.path.append(complex(floatByExtraIndex + self.getOldPoint().real, begin.imag)) + self.wordIndex += 1 + + def processPathWordL(self): + "Process path word L." + self.addPathLine(self.getComplexByExtraIndex, self.getComplexByExtraIndex( 1 )) + + def processPathWordl(self): + "Process path word l." + self.addPathLine(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint()) + + def processPathWordM(self): + "Process path word M." + self.addPathMove(self.getComplexByExtraIndex, self.getComplexByExtraIndex(1)) + + def processPathWordm(self): + "Process path word m." + self.addPathMove(self.getComplexRelative, self.getComplexByExtraIndex(1) + self.getOldPoint()) + + def processPathWordQ(self): + 'Process path word Q.' + self.addPathQuadratic( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ) + + def processPathWordq(self): + 'Process path word q.' + begin = self.getOldPoint() + self.addPathQuadratic(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin) + + def processPathWordS(self): + 'Process path word S.' + self.addPathCubicReflected( self.getComplexByExtraIndex( 1 ), self.getComplexByExtraIndex(3) ) + + def processPathWords(self): + 'Process path word s.' + begin = self.getOldPoint() + self.addPathCubicReflected(self.getComplexByExtraIndex(1) + begin, self.getComplexByExtraIndex(3) + begin) + + def processPathWordT(self): + 'Process path word T.' + self.addPathQuadraticReflected( self.getComplexByExtraIndex( 1 ) ) + + def processPathWordt(self): + 'Process path word t.' + self.addPathQuadraticReflected(self.getComplexByExtraIndex(1) + self.getOldPoint()) + + def processPathWordV(self): + "Process path word V." + beginX = self.getOldPoint().real + self.addPathLineAxis(complex(beginX, float(self.words[self.wordIndex + 1]))) + while 1: + floatByExtraIndex = self.getFloatByExtraIndex() + if floatByExtraIndex == None: + return + self.path.append(complex(beginX, floatByExtraIndex)) + self.wordIndex += 1 + + def processPathWordv(self): + "Process path word v." + begin = self.getOldPoint() + self.addPathLineAxis(complex(begin.real, float(self.words[self.wordIndex + 1]) + begin.imag)) + while 1: + floatByExtraIndex = self.getFloatByExtraIndex() + if floatByExtraIndex == None: + return + self.path.append(complex(begin.real, floatByExtraIndex + self.getOldPoint().imag)) + self.wordIndex += 1 + + def processPathWordZ(self): + "Process path word Z." + self.controlPoints = None + if len(self.path) < 1: + return + self.loops.append(getChainMatrixSVGIfNecessary(self.elementNode, self.yAxisPointingUpward).getTransformedPath(self.path)) + self.oldPoint = self.path[0] + self.path = [] + + def processPathWordz(self): + "Process path word z." + self.processPathWordZ() + + +class SVGReader: + "An svg carving." + def __init__(self): + "Add empty lists." + self.loopLayers = [] + self.sliceDictionary = None + self.stopProcessing = False + self.z = 0.0 + + def flipDirectLayer(self, loopLayer): + "Flip the y coordinate of the layer and direct the loops." + for loop in loopLayer.loops: + for pointIndex, point in enumerate(loop): + loop[pointIndex] = complex(point.real, -point.imag) + triangle_mesh.sortLoopsInOrderOfArea(True, loopLayer.loops) + for loopIndex, loop in enumerate(loopLayer.loops): + isInsideLoops = euclidean.getIsInFilledRegion(loopLayer.loops[: loopIndex], euclidean.getLeftPoint(loop)) + intercircle.directLoop((not isInsideLoops), loop) + + def getLoopLayer(self): + "Return the rotated loop layer." + if self.z != None: + loopLayer = euclidean.LoopLayer(self.z) + self.loopLayers.append(loopLayer) + self.z = None + return self.loopLayers[-1] + + def parseSVG(self, fileName, svgText): + "Parse SVG text and store the layers." + self.fileName = fileName + xmlParser = DocumentNode(fileName, svgText) + self.documentElement = xmlParser.getDocumentElement() + if self.documentElement == None: + print('Warning, documentElement was None in parseSVG in SVGReader, so nothing will be done for:') + print(fileName) + return + self.parseSVGByElementNode(self.documentElement) + + def parseSVGByElementNode(self, elementNode): + "Parse SVG by elementNode." + self.sliceDictionary = svg_writer.getSliceDictionary(elementNode) + self.yAxisPointingUpward = euclidean.getBooleanFromDictionary(False, self.sliceDictionary, 'yAxisPointingUpward') + self.processElementNode(elementNode) + if not self.yAxisPointingUpward: + for loopLayer in self.loopLayers: + self.flipDirectLayer(loopLayer) + + def processElementNode(self, elementNode): + 'Process the xml element.' + if self.stopProcessing: + return + lowerLocalName = elementNode.getNodeName().lower() + global globalProcessSVGElementDictionary + if lowerLocalName in globalProcessSVGElementDictionary: + try: + globalProcessSVGElementDictionary[lowerLocalName](elementNode, self) + except: + print('Warning, in processElementNode in svg_reader, could not process:') + print(elementNode) + traceback.print_exc(file=sys.stdout) + for childNode in elementNode.childNodes: + self.processElementNode(childNode) + + +globalFontFileNames = None +globalFontReaderDictionary = {} +globalGetTricomplexDictionary = {} +globalGetTricomplexFunctions = [ + getTricomplexmatrix, + getTricomplexrotate, + getTricomplexscale, + getTricomplexskewX, + getTricomplexskewY, + getTricomplextranslate ] +globalProcessPathWordFunctions = [ + PathReader.processPathWordA, + PathReader.processPathWorda, + PathReader.processPathWordC, + PathReader.processPathWordc, + PathReader.processPathWordH, + PathReader.processPathWordh, + PathReader.processPathWordL, + PathReader.processPathWordl, + PathReader.processPathWordM, + PathReader.processPathWordm, + PathReader.processPathWordQ, + PathReader.processPathWordq, + PathReader.processPathWordS, + PathReader.processPathWords, + PathReader.processPathWordT, + PathReader.processPathWordt, + PathReader.processPathWordV, + PathReader.processPathWordv, + PathReader.processPathWordZ, + PathReader.processPathWordz ] +globalProcessPathWordDictionary = {} +globalProcessSVGElementDictionary = {} +globalProcessSVGElementFunctions = [ + processSVGElementcircle, + processSVGElementellipse, + processSVGElementg, + processSVGElementline, + processSVGElementpath, + processSVGElementpolygon, + processSVGElementpolyline, + processSVGElementrect, + processSVGElementtext ] +globalSideAngle = 0.5 * math.pi / float( globalNumberOfCornerPoints ) + + +addFunctionsToDictionary( globalGetTricomplexDictionary, globalGetTricomplexFunctions, 'getTricomplex') +addFunctionsToDictionary( globalProcessPathWordDictionary, globalProcessPathWordFunctions, 'processPathWord') +addFunctionsToDictionary( globalProcessSVGElementDictionary, globalProcessSVGElementFunctions, 'processSVGElement') diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/svg_writer.py b/SkeinPyPy_NewUI/fabmetheus_utilities/svg_writer.py new file mode 100644 index 0000000..ae836df --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/svg_writer.py @@ -0,0 +1,281 @@ +""" +Svg_writer is a class and collection of utilities to read from and write to an svg file. + +Svg_writer uses the layer_template.svg file in the templates folder in the same folder as svg_writer, to output an svg file. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities.xml_simple_reader import DocumentNode +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import xml_simple_reader +from fabmetheus_utilities import xml_simple_writer +import cStringIO +import math +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalOriginalTextString = '' in lineStripped: + isComment = False + xml_simple_reader.CommentNode(self.svgElement, '%s%s-->\n' % (globalOriginalTextString, commentNodeOutput.getvalue())).appendSelfToParent() + + def getReplacedSVGTemplate(self, fileName, loopLayers, procedureName, elementNode=None): + 'Get the lines of text from the layer_template.svg file.' + self.extent = self.cornerMaximum - self.cornerMinimum + svgTemplateText = archive.getFileText(archive.getTemplatesPath('layer_template.svg')) + documentNode = DocumentNode(fileName, svgTemplateText) + self.svgElement = documentNode.getDocumentElement() + svgElementDictionary = self.svgElement.attributes + self.sliceDictionary = getSliceDictionary(self.svgElement) + self.controlBoxHeight = float(self.sliceDictionary['controlBoxHeight']) + self.controlBoxWidth = float(self.sliceDictionary['controlBoxWidth']) + self.margin = float(self.sliceDictionary['margin']) + self.marginTop = float(self.sliceDictionary['marginTop']) + self.textHeight = float(self.sliceDictionary['textHeight']) + self.unitScale = float(self.sliceDictionary['unitScale']) + svgMinWidth = float(self.sliceDictionary['svgMinWidth']) + self.controlBoxHeightMargin = self.controlBoxHeight + self.marginTop + if not self.addLayerTemplateToSVG: + self.svgElement.getElementNodeByID('layerTextTemplate').removeFromIDNameParent() + del self.svgElement.getElementNodeByID('sliceElementTemplate').attributes['transform'] + self.graphicsElementNode = self.svgElement.getElementNodeByID('sliceElementTemplate') + self.graphicsElementNode.attributes['id'] = 'z:' + self.addLoopLayersToOutput(loopLayers) + self.setMetadataNoscriptElement('layerHeight', 'Layer Height: ', self.layerHeight) + self.setMetadataNoscriptElement('maxX', 'X: ', self.cornerMaximum.x) + self.setMetadataNoscriptElement('minX', 'X: ', self.cornerMinimum.x) + self.setMetadataNoscriptElement('maxY', 'Y: ', self.cornerMaximum.y) + self.setMetadataNoscriptElement('minY', 'Y: ', self.cornerMinimum.y) + self.setMetadataNoscriptElement('maxZ', 'Z: ', self.cornerMaximum.z) + self.setMetadataNoscriptElement('minZ', 'Z: ', self.cornerMinimum.z) + self.textHeight = float( self.sliceDictionary['textHeight'] ) + controlTop = len(loopLayers) * (self.margin + self.extent.y * self.unitScale + self.textHeight) + self.marginTop + self.textHeight + self.svgElement.getFirstChildByLocalName('title').setTextContent(os.path.basename(fileName) + ' - Slice Layers') + svgElementDictionary['height'] = '%spx' % self.getRounded(max(controlTop, self.controlBoxHeightMargin)) + width = max(self.extent.x * self.unitScale, svgMinWidth) + svgElementDictionary['width'] = '%spx' % self.getRounded( width ) + self.sliceDictionary['decimalPlacesCarried'] = str( self.decimalPlacesCarried ) + if self.edgeWidth != None: + self.sliceDictionary['edgeWidth'] = self.getRounded( self.edgeWidth ) + self.sliceDictionary['yAxisPointingUpward'] = 'true' + self.sliceDictionary['procedureName'] = procedureName + self.setDimensionTexts('dimX', 'X: ' + self.getRounded(self.extent.x)) + self.setDimensionTexts('dimY', 'Y: ' + self.getRounded(self.extent.y)) + self.setDimensionTexts('dimZ', 'Z: ' + self.getRounded(self.extent.z)) + self.setTexts('numberOfLayers', 'Number of Layers: %s' % len(loopLayers)) + volume = 0.0 + for loopLayer in loopLayers: + volume += euclidean.getAreaLoops(loopLayer.loops) + volume *= 0.001 * self.layerHeight + self.setTexts('volume', 'Volume: %s cm3' % self.getRounded(volume)) + if not self.addLayerTemplateToSVG: + self.svgElement.getFirstChildByLocalName('script').removeFromIDNameParent() + self.svgElement.getElementNodeByID('isoControlBox').removeFromIDNameParent() + self.svgElement.getElementNodeByID('layerControlBox').removeFromIDNameParent() + self.svgElement.getElementNodeByID('scrollControlBox').removeFromIDNameParent() + self.graphicsElementNode.removeFromIDNameParent() + self.addOriginalAsComment(elementNode) + return documentNode.__repr__() + + def getRounded(self, number): + 'Get number rounded to the number of carried decimal places as a string.' + return euclidean.getRoundedToPlacesString(self.decimalPlacesCarried, number) + + def getRoundedComplexString(self, point): + 'Get the rounded complex string.' + return self.getRounded( point.real ) + ' ' + self.getRounded( point.imag ) + + def getSVGStringForLoop( self, loop ): + 'Get the svg loop string.' + if len(loop) < 1: + return '' + return self.getSVGStringForPath(loop) + ' z' + + def getSVGStringForLoops( self, loops ): + 'Get the svg loops string.' + loopString = '' + if len(loops) > 0: + loopString += self.getSVGStringForLoop( loops[0] ) + for loop in loops[1 :]: + loopString += ' ' + self.getSVGStringForLoop(loop) + return loopString + + def getSVGStringForPath( self, path ): + 'Get the svg path string.' + svgLoopString = '' + for point in path: + stringBeginning = 'M ' + if len( svgLoopString ) > 0: + stringBeginning = ' L ' + svgLoopString += stringBeginning + self.getRoundedComplexString(point) + return svgLoopString + + def getTransformString(self): + 'Get the svg transform string.' + cornerMinimumXString = self.getRounded(-self.cornerMinimum.x) + cornerMinimumYString = self.getRounded(-self.cornerMinimum.y) + return 'scale(%s, %s) translate(%s, %s)' % (self.unitScale, - self.unitScale, cornerMinimumXString, cornerMinimumYString) + + def setDimensionTexts(self, key, valueString): + 'Set the texts to the valueString followed by mm.' + self.setTexts(key, valueString + ' mm') + + def setMetadataNoscriptElement(self, key, prefix, value): + 'Set the metadata value and the text.' + valueString = self.getRounded(value) + self.sliceDictionary[key] = valueString + self.setDimensionTexts(key, prefix + valueString) + + def setTexts(self, key, valueString): + 'Set the texts to the valueString.' + self.svgElement.getElementNodeByID(key + 'Iso').setTextContent(valueString) + self.svgElement.getElementNodeByID(key + 'Layer').setTextContent(valueString) + self.svgElement.getElementNodeByID(key + 'Scroll').setTextContent(valueString) diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/templates/canvas_template.svg b/SkeinPyPy_NewUI/fabmetheus_utilities/templates/canvas_template.svg new file mode 100644 index 0000000..b146e60 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/templates/canvas_template.svg @@ -0,0 +1,16 @@ + + + + + +replaceLineWithTitle + + + diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/templates/layer_template.svg b/SkeinPyPy_NewUI/fabmetheus_utilities/templates/layer_template.svg new file mode 100644 index 0000000..e410f9a --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/templates/layer_template.svg @@ -0,0 +1,519 @@ + + + + + + + + + replaceWith_Title + + + + + + + Layer 1, z:0.1 + + + + + + + + + + + + + Latitude + < + > + Longitude + < + > + Scale + 1 + < + > + + Min + + + + + + Max + + + + + + Dimension + + + + + + Statistics + + + + + + + + + + + Y + X + 0 + + + 1 + Layer + < + > + Scale + 1 + < + > + + Min + + + + + + Max + + + + + + Dimension + + + + + + Statistics + + + + + + + + + + Y + X + Scale + : 1 + < + > + + Min + + + + + + Max + + + + + + Dimension + + + + + + Statistics + + + + + + [Iso View] + Iso View + [Layer View] + Layer View + [Scroll View] + Scroll View + + diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/vector3.py b/SkeinPyPy_NewUI/fabmetheus_utilities/vector3.py new file mode 100644 index 0000000..e577bb8 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/vector3.py @@ -0,0 +1,336 @@ +""" +Vector3 is a three dimensional vector class. + +Below are examples of Vector3 use. + +>>> from vector3 import Vector3 +>>> origin = Vector3() +>>> origin +0.0, 0.0, 0.0 +>>> pythagoras = Vector3( 3, 4, 0 ) +>>> pythagoras +3.0, 4.0, 0.0 +>>> pythagoras.magnitude() +5.0 +>>> pythagoras.magnitudeSquared() +25 +>>> triplePythagoras = pythagoras * 3.0 +>>> triplePythagoras +9.0, 12.0, 0.0 +>>> plane = pythagoras.dropAxis() +>>> plane +(3+4j) +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import xml_simple_writer +import math +import operator + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +class Vector3: + 'A three dimensional vector class.' + __slots__ = ['x', 'y', 'z'] + + def __init__(self, x=0.0, y=0.0, z=0.0): + self.x = x + self.y = y + self.z = z + + def __abs__(self): + 'Get the magnitude of the Vector3.' + return math.sqrt( self.x * self.x + self.y * self.y + self.z * self.z ) + + magnitude = __abs__ + + def __add__(self, other): + 'Get the sum of this Vector3 and other one.' + return Vector3( self.x + other.x, self.y + other.y, self.z + other.z ) + + def __copy__(self): + 'Get the copy of this Vector3.' + return Vector3( self.x, self.y, self.z ) + + __pos__ = __copy__ + + copy = __copy__ + + def __div__(self, other): + 'Get a new Vector3 by dividing each component of this one.' + return Vector3( self.x / other, self.y / other, self.z / other ) + + def __eq__(self, other): + 'Determine whether this vector is identical to other one.' + if other == None: + return False + if other.__class__ != self.__class__: + return False + return self.x == other.x and self.y == other.y and self.z == other.z + + def __floordiv__(self, other): + 'Get a new Vector3 by floor dividing each component of this one.' + return Vector3( self.x // other, self.y // other, self.z // other ) + + def __hash__(self): + 'Determine whether this vector is identical to other one.' + return self.__repr__().__hash__() + + def __iadd__(self, other): + 'Add other Vector3 to this one.' + self.x += other.x + self.y += other.y + self.z += other.z + return self + + def __idiv__(self, other): + 'Divide each component of this Vector3.' + self.x /= other + self.y /= other + self.z /= other + return self + + def __ifloordiv__(self, other): + 'Floor divide each component of this Vector3.' + self.x //= other + self.y //= other + self.z //= other + return self + + def __imul__(self, other): + 'Multiply each component of this Vector3.' + self.x *= other + self.y *= other + self.z *= other + return self + + def __isub__(self, other): + 'Subtract other Vector3 from this one.' + self.x -= other.x + self.y -= other.y + self.z -= other.z + return self + + def __itruediv__(self, other): + 'True divide each component of this Vector3.' + self.x = operator.truediv( self.x, other ) + self.y = operator.truediv( self.y, other ) + self.z = operator.truediv( self.z, other ) + return self + + def __mul__(self, other): + 'Get a new Vector3 by multiplying each component of this one.' + return Vector3( self.x * other, self.y * other, self.z * other ) + + def __ne__(self, other): + 'Determine whether this vector is not identical to other one.' + return not self.__eq__(other) + + def __neg__(self): + return Vector3( - self.x, - self.y, - self.z ) + + def __nonzero__(self): + return self.x != 0 or self.y != 0 or self.z != 0 + + def __rdiv__(self, other): + 'Get a new Vector3 by dividing each component of this one.' + return Vector3( other / self.x, other / self.y, other / self.z ) + + def __repr__(self): + 'Get the string representation of this Vector3.' + return '(%s, %s, %s)' % ( self.x, self.y, self.z ) + + def __rfloordiv__(self, other): + 'Get a new Vector3 by floor dividing each component of this one.' + return Vector3( other // self.x, other // self.y, other // self.z ) + + def __rmul__(self, other): + 'Get a new Vector3 by multiplying each component of this one.' + return Vector3( self.x * other, self.y * other, self.z * other ) + + def __rtruediv__(self, other): + 'Get a new Vector3 by true dividing each component of this one.' + return Vector3( operator.truediv( other , self.x ), operator.truediv( other, self.y ), operator.truediv( other, self.z ) ) + + def __sub__(self, other): + 'Get the difference between the Vector3 and other one.' + return Vector3( self.x - other.x, self.y - other.y, self.z - other.z ) + + def __truediv__(self, other): + 'Get a new Vector3 by true dividing each component of this one.' + return Vector3( operator.truediv( self.x, other ), operator.truediv( self.y, other ), operator.truediv( self.z, other ) ) + + def _getAccessibleAttribute(self, attributeName): + 'Get the accessible attribute.' + if attributeName in globalGetAccessibleAttributeSet: + return getattr(self, attributeName, None) + return None + + def _setAccessibleAttribute(self, attributeName, value): + 'Set the accessible attribute.' + if attributeName in globalSetAccessibleAttributeSet: + setattr(self, attributeName, value) + + def cross(self, other): + 'Calculate the cross product of this vector with other one.' + return Vector3(self.y * other.z - self.z * other.y, -self.x * other.z + self.z * other.x, self.x * other.y - self.y * other.x) + + def distance(self, other): + 'Get the Euclidean distance between this vector and other one.' + return math.sqrt( self.distanceSquared(other) ) + + def distanceSquared(self, other): + 'Get the square of the Euclidean distance between this vector and other one.' + separationX = self.x - other.x + separationY = self.y - other.y + separationZ = self.z - other.z + return separationX * separationX + separationY * separationY + separationZ * separationZ + + def dot(self, other): + 'Calculate the dot product of this vector with other one.' + return self.x * other.x + self.y * other.y + self.z * other.z + + def dropAxis( self, which = 2 ): + 'Get a complex by removing one axis of the vector3.' + if which == 0: + return complex( self.y, self.z ) + if which == 1: + return complex( self.x, self.z ) + if which == 2: + return complex( self.x, self.y ) + + def getFloatList(self): + 'Get the vector as a list of floats.' + return [ float( self.x ), float( self.y ), float( self.z ) ] + + def getIsDefault(self): + 'Determine if this is the zero vector.' + if self.x != 0.0: + return False + if self.y != 0.0: + return False + return self.z == 0.0 + + def getNormalized(self): + 'Get the normalized Vector3.' + magnitude = abs(self) + if magnitude == 0.0: + return self.copy() + return self / magnitude + + def magnitudeSquared(self): + 'Get the square of the magnitude of the Vector3.' + return self.x * self.x + self.y * self.y + self.z * self.z + + def maximize(self, other): + 'Maximize the Vector3.' + self.x = max(other.x, self.x) + self.y = max(other.y, self.y) + self.z = max(other.z, self.z) + + def minimize(self, other): + 'Minimize the Vector3.' + self.x = min(other.x, self.x) + self.y = min(other.y, self.y) + self.z = min(other.z, self.z) + + def normalize(self): + 'Scale each component of this Vector3 so that it has a magnitude of 1. If this Vector3 has a magnitude of 0, this method has no effect.' + magnitude = abs(self) + if magnitude != 0.0: + self /= magnitude + + def reflect( self, normal ): + 'Reflect the Vector3 across the normal, which is assumed to be normalized.' + distance = 2 * ( self.x * normal.x + self.y * normal.y + self.z * normal.z ) + return Vector3( self.x - distance * normal.x, self.y - distance * normal.y, self.z - distance * normal.z ) + + def setToVector3(self, other): + 'Set this Vector3 to be identical to other one.' + self.x = other.x + self.y = other.y + self.z = other.z + + def setToXYZ( self, x, y, z ): + 'Set the x, y, and z components of this Vector3.' + self.x = x + self.y = y + self.z = z + + +globalGetAccessibleAttributeSet = 'x y z'.split() +globalSetAccessibleAttributeSet = globalGetAccessibleAttributeSet + +""" +class Vector3: + __slots__ = ['x', 'y', 'z'] + + + copy = __copy__ + + def __eq__(self, other): + if isinstance(other, Vector3): + return self.x == other.x and \ + self.y == other.y and \ + self.z == other.z + else: + assert hasattr(other, '__len__') and len(other) == 3 + return self.x == other[0] and \ + self.y == other[1] and \ + self.z == other[2] + + def __getattr__(self, name): + try: + return tuple([(self.x, self.y, self.z)['xyz'.index(c)] \ + for c in name]) + except ValueError: + raise AttributeError, name + + def __getitem__(self, key): + return (self.x, self.y, self.z)[key] + + def __iter__(self): + return iter((self.x, self.y, self.z)) + + def __len__(self): + return 3 + + def __repr__(self): + return 'Vector3(%.2f, %.2f, %.2f)' % (self.x, + self.y, + self.z) + + if _enable_swizzle_set: + # This has detrimental performance on ordinary setattr as well + # if enabled + def __setattr__(self, name, value): + if len(name) == 1: + object.__setattr__(self, name, value) + else: + try: + l = [self.x, self.y, self.z] + for c, v in map(None, name, value): + l['xyz'.index(c)] = v + self.x, self.y, self.z = l + except ValueError: + raise AttributeError, name + + def __setitem__(self, key, value): + l = [self.x, self.y, self.z] + l[key] = value + self.x, self.y, self.z = l + +""" diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/vector3index.py b/SkeinPyPy_NewUI/fabmetheus_utilities/vector3index.py new file mode 100644 index 0000000..7d46075 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/vector3index.py @@ -0,0 +1,277 @@ +""" +Vector3 is a three dimensional vector class. + +Below are examples of Vector3 use. + +>>> from vector3 import Vector3 +>>> origin = Vector3() +>>> origin +0.0, 0.0, 0.0 +>>> pythagoras = Vector3( 3, 4, 0 ) +>>> pythagoras +3.0, 4.0, 0.0 +>>> pythagoras.magnitude() +5.0 +>>> pythagoras.magnitudeSquared() +25 +>>> triplePythagoras = pythagoras * 3.0 +>>> triplePythagoras +9.0, 12.0, 0.0 +>>> plane = pythagoras.dropAxis() +>>> plane +(3+4j) +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import xml_simple_writer +import math +import operator + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +class Vector3Index: + 'A three dimensional vector index class.' + __slots__ = ['index', 'x', 'y', 'z'] + + def __init__( self, index, x = 0.0, y = 0.0, z = 0.0 ): + self.index = index + self.x = x + self.y = y + self.z = z + + def __abs__(self): + 'Get the magnitude of the Vector3.' + return math.sqrt( self.x * self.x + self.y * self.y + self.z * self.z ) + + magnitude = __abs__ + + def __add__(self, other): + 'Get the sum of this Vector3 and other one.' + return Vector3Index( self.index, self.x + other.x, self.y + other.y, self.z + other.z ) + + def __copy__(self): + 'Get the copy of this Vector3.' + return Vector3Index( self.index, self.x, self.y, self.z ) + + __pos__ = __copy__ + + copy = __copy__ + + def __div__(self, other): + 'Get a new Vector3 by dividing each component of this one.' + return Vector3Index( self.index, self.x / other, self.y / other, self.z / other ) + + def __eq__(self, other): + 'Determine whether this vector is identical to other one.' + if other == None: + return False + if other.__class__ != self.__class__: + return False + return self.x == other.x and self.y == other.y and self.z == other.z + + def __floordiv__(self, other): + 'Get a new Vector3 by floor dividing each component of this one.' + return Vector3Index( self.index, self.x // other, self.y // other, self.z // other ) + + def __hash__(self): + 'Determine whether this vector is identical to other one.' + return self.__repr__().__hash__() + + def __iadd__(self, other): + 'Add other Vector3 to this one.' + self.x += other.x + self.y += other.y + self.z += other.z + return self + + def __idiv__(self, other): + 'Divide each component of this Vector3.' + self.x /= other + self.y /= other + self.z /= other + return self + + def __ifloordiv__(self, other): + 'Floor divide each component of this Vector3.' + self.x //= other + self.y //= other + self.z //= other + return self + + def __imul__(self, other): + 'Multiply each component of this Vector3.' + self.x *= other + self.y *= other + self.z *= other + return self + + def __isub__(self, other): + 'Subtract other Vector3 from this one.' + self.x -= other.x + self.y -= other.y + self.z -= other.z + return self + + def __itruediv__(self, other): + 'True divide each component of this Vector3.' + self.x = operator.truediv( self.x, other ) + self.y = operator.truediv( self.y, other ) + self.z = operator.truediv( self.z, other ) + return self + + def __mul__(self, other): + 'Get a new Vector3 by multiplying each component of this one.' + return Vector3Index( self.index, self.x * other, self.y * other, self.z * other ) + + def __ne__(self, other): + 'Determine whether this vector is not identical to other one.' + return not self.__eq__(other) + + def __neg__(self): + return Vector3Index( self.index, - self.x, - self.y, - self.z ) + + def __nonzero__(self): + return self.x != 0 or self.y != 0 or self.z != 0 + + def __rdiv__(self, other): + 'Get a new Vector3 by dividing each component of this one.' + return Vector3Index( self.index, other / self.x, other / self.y, other / self.z ) + + def __repr__(self): + 'Get the string representation of this Vector3 index.' + return '(%s, %s, %s, %s)' % (self.index, self.x, self.y, self.z) + + def __rfloordiv__(self, other): + 'Get a new Vector3 by floor dividing each component of this one.' + return Vector3Index( self.index, other // self.x, other // self.y, other // self.z ) + + def __rmul__(self, other): + 'Get a new Vector3 by multiplying each component of this one.' + return Vector3Index( self.index, self.x * other, self.y * other, self.z * other ) + + def __rtruediv__(self, other): + 'Get a new Vector3 by true dividing each component of this one.' + return Vector3Index( self.index, operator.truediv( other , self.x ), operator.truediv( other, self.y ), operator.truediv( other, self.z ) ) + + def __sub__(self, other): + 'Get the difference between the Vector3 and other one.' + return Vector3Index( self.index, self.x - other.x, self.y - other.y, self.z - other.z ) + + def __truediv__(self, other): + 'Get a new Vector3 by true dividing each component of this one.' + return Vector3Index( self.index, operator.truediv( self.x, other ), operator.truediv( self.y, other ), operator.truediv( self.z, other ) ) + + def _getAccessibleAttribute(self, attributeName): + 'Get the accessible attribute.' + global globalGetAccessibleAttributeSet + if attributeName in globalGetAccessibleAttributeSet: + return getattr(self, attributeName, None) + return None + + def _setAccessibleAttribute(self, attributeName, value): + 'Set the accessible attribute.' + if attributeName in globalSetAccessibleAttributeSet: + setattr(self, attributeName, value) + + def cross(self, other): + 'Calculate the cross product of this vector with other one.' + return Vector3Index( self.index, self.y * other.z - self.z * other.y, - self.x * other.z + self.z * other.x, self.x * other.y - self.y * other.x ) + + def distance(self, other): + 'Get the Euclidean distance between this vector and other one.' + return math.sqrt( self.distanceSquared(other) ) + + def distanceSquared(self, other): + 'Get the square of the Euclidean distance between this vector and other one.' + separationX = self.x - other.x + separationY = self.y - other.y + separationZ = self.z - other.z + return separationX * separationX + separationY * separationY + separationZ * separationZ + + def dot(self, other): + 'Calculate the dot product of this vector with other one.' + return self.x * other.x + self.y * other.y + self.z * other.z + + def dropAxis( self, which = 2 ): + 'Get a complex by removing one axis of the vector3.' + if which == 0: + return complex( self.y, self.z ) + if which == 1: + return complex( self.x, self.z ) + if which == 2: + return complex( self.x, self.y ) + + def getFloatList(self): + 'Get the vector as a list of floats.' + return [ float( self.x ), float( self.y ), float( self.z ) ] + + def getIsDefault(self): + 'Determine if this is the zero vector.' + if self.x != 0.0: + return False + if self.y != 0.0: + return False + return self.z == 0.0 + + def getNormalized(self): + 'Get the normalized Vector3.' + magnitude = abs(self) + if magnitude == 0.0: + return self.copy() + return self / magnitude + + def magnitudeSquared(self): + 'Get the square of the magnitude of the Vector3.' + return self.x * self.x + self.y * self.y + self.z * self.z + + def maximize(self, other): + 'Maximize the Vector3.' + self.x = max(other.x, self.x) + self.y = max(other.y, self.y) + self.z = max(other.z, self.z) + + def minimize(self, other): + 'Minimize the Vector3.' + self.x = min(other.x, self.x) + self.y = min(other.y, self.y) + self.z = min(other.z, self.z) + + def normalize(self): + 'Scale each component of this Vector3 so that it has a magnitude of 1. If this Vector3 has a magnitude of 0, this method has no effect.' + magnitude = abs(self) + if magnitude != 0.0: + self /= magnitude + + def reflect( self, normal ): + 'Reflect the Vector3 across the normal, which is assumed to be normalized.' + distance = 2 * ( self.x * normal.x + self.y * normal.y + self.z * normal.z ) + return Vector3Index( self.index, self.x - distance * normal.x, self.y - distance * normal.y, self.z - distance * normal.z ) + + def setToVector3(self, other): + 'Set this Vector3 to be identical to other one.' + self.x = other.x + self.y = other.y + self.z = other.z + + def setToXYZ( self, x, y, z ): + 'Set the x, y, and z components of this Vector3.' + self.x = x + self.y = y + self.z = z + + +globalGetAccessibleAttributeSet = 'x y z'.split() +globalSetAccessibleAttributeSet = globalGetAccessibleAttributeSet diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/version.txt b/SkeinPyPy_NewUI/fabmetheus_utilities/version.txt new file mode 100644 index 0000000..0d73a0c --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/version.txt @@ -0,0 +1 @@ +12.02.10 \ No newline at end of file diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_reader.py b/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_reader.py new file mode 100644 index 0000000..87a8a08 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_reader.py @@ -0,0 +1,849 @@ +""" +The xml_simple_reader.py script is an xml parser that can parse a line separated xml text. + +This xml parser will read a line seperated xml text and produce a tree of the xml with a document element. Each element can have an attribute table, childNodes, a class name, parentNode, text and a link to the document element. + +This example gets an xml tree for the xml file boolean.xml. This example is run in a terminal in the folder which contains boolean.xml and xml_simple_reader.py. + + +> python +Python 2.5.1 (r251:54863, Sep 22 2007, 01:43:31) +[GCC 4.2.1 (SUSE Linux)] on linux2 +Type "help", "copyright", "credits" or "license" for more information. +>>> fileName = 'boolean.xml' +>>> file = open(fileName, 'r') +>>> xmlText = file.read() +>>> file.close() +>>> from xml_simple_reader import DocumentNode +>>> xmlParser = DocumentNode(fileName, xmlText) +>>> print(xmlParser) + ?xml, {'version': '1.0'} + ArtOfIllusion, {'xmlns:bf': '//babelfiche/codec', 'version': '2.0', 'fileversion': '3'} + Scene, {'bf:id': 'theScene'} + materials, {'bf:elem-type': 'java.lang.Object', 'bf:list': 'collection', 'bf:id': '1', 'bf:type': 'java.util.Vector'} +.. +many more lines of the xml tree +.. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.geometry.geometry_utilities import evaluate +from fabmetheus_utilities.geometry.geometry_utilities import matrix +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import xml_simple_writer +import cStringIO + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +globalGetAccessibleAttributeSet = set('getPaths getPreviousVertex getPreviousElementNode getVertexes parentNode'.split()) + + +def createAppendByText(parentNode, xmlText): + 'Create and append the child nodes from the xmlText.' + monad = OpenMonad(parentNode) + for character in xmlText: + monad = monad.getNextMonad(character) + +def createAppendByTextb(parentNode, xmlText): + 'Create and append the child nodes from the xmlText.' + monad = OpenMonad(parentNode) + for character in xmlText: + monad = monad.getNextMonad(character) + +def getChildElementsByLocalName(childNodes, localName): + 'Get the childNodes which have the given local name.' + childElementsByLocalName = [] + for childNode in childNodes: + if localName.lower() == childNode.getNodeName(): + childElementsByLocalName.append(childNode) + return childElementsByLocalName + +def getDocumentNode(fileName): + 'Get the document from the file name.' + xmlText = getFileText('test.xml') + return DocumentNode(fileName, xmlText) + +def getElementsByLocalName(childNodes, localName): + 'Get the descendents which have the given local name.' + elementsByLocalName = getChildElementsByLocalName(childNodes, localName) + for childNode in childNodes: + if childNode.getNodeType() == 1: + elementsByLocalName += childNode.getElementsByLocalName(localName) + return elementsByLocalName + +def getFileText(fileName, printWarning=True, readMode='r'): + 'Get the entire text of a file.' + try: + file = open(fileName, readMode) + fileText = file.read() + file.close() + return fileText + except IOError: + if printWarning: + print('The file ' + fileName + ' does not exist.') + return '' + + +class CDATASectionMonad: + 'A monad to handle a CDATASection node.' + def __init__(self, input, parentNode): + 'Initialize.' + self.input = input + self.parentNode = parentNode + + def getNextMonad(self, character): + 'Get the next monad.' + self.input.write(character) + if character == '>': + inputString = self.input.getvalue() + if inputString.endswith(']]>'): + textContent = '<%s\n' % inputString + self.parentNode.childNodes.append(CDATASectionNode(self.parentNode, textContent)) + return OpenMonad(self.parentNode) + return self + + +class CDATASectionNode: + 'A CDATASection node.' + def __init__(self, parentNode, textContent=''): + 'Initialize.' + self.parentNode = parentNode + self.textContent = textContent + + def __repr__(self): + 'Get the string representation of this CDATASection node.' + return self.textContent + + def addToIdentifierDictionaries(self): + 'Add the element to the owner document identifier dictionaries.' + pass + + def addXML(self, depth, output): + 'Add xml for this CDATASection node.' + output.write(self.textContent) + + def appendSelfToParent(self): + 'Append self to the parentNode.' + self.parentNode.appendChild(self) + + def copyXMLChildNodes(self, idSuffix, parentNode): + 'Copy the xml childNodes.' + pass + + def getAttributes(self): + 'Get the attributes.' + return {} + + def getChildNodes(self): + 'Get the empty set.' + return [] + + def getCopy(self, idSuffix, parentNode): + 'Copy the xml element, set its dictionary and add it to the parentNode.' + copy = self.getCopyShallow() + copy.parentNode = parentNode + copy.appendSelfToParent() + return copy + + def getCopyShallow(self, attributes=None): + 'Copy the node and set its parentNode.' + return CDATASectionNode(self.parentNode, self.textContent) + + def getNodeName(self): + 'Get the node name.' + return '#cdata-section' + + def getNodeType(self): + 'Get the node type.' + return 4 + + def getOwnerDocument(self): + 'Get the owner document.' + return self.parentNode.getOwnerDocument() + + def getTextContent(self): + 'Get the text content.' + return self.textContent + + def removeChildNodesFromIDNameParent(self): + 'Remove the childNodes from the id and name dictionaries and the childNodes.' + pass + + def removeFromIDNameParent(self): + 'Remove this from the id and name dictionaries and the childNodes of the parentNode.' + if self.parentNode != None: + self.parentNode.childNodes.remove(self) + + def setParentAddToChildNodes(self, parentNode): + 'Set the parentNode and add this to its childNodes.' + self.parentNode = parentNode + if self.parentNode != None: + self.parentNode.childNodes.append(self) + + attributes = property(getAttributes) + childNodes = property(getChildNodes) + nodeName = property(getNodeName) + nodeType = property(getNodeType) + ownerDocument = property(getOwnerDocument) + + +class CommentMonad(CDATASectionMonad): + 'A monad to handle a comment node.' + def getNextMonad(self, character): + 'Get the next monad.' + self.input.write(character) + if character == '>': + inputString = self.input.getvalue() + if inputString.endswith('-->'): + textContent = '<%s\n' % inputString + self.parentNode.childNodes.append(CommentNode(self.parentNode, textContent)) + return OpenMonad(self.parentNode) + return self + + +class CommentNode(CDATASectionNode): + 'A comment node.' + def getCopyShallow(self, attributes=None): + 'Copy the node and set its parentNode.' + return CommentNode(self.parentNode, self.textContent) + + def getNodeName(self): + 'Get the node name.' + return '#comment' + + def getNodeType(self): + 'Get the node type.' + return 8 + + nodeName = property(getNodeName) + nodeType = property(getNodeType) + + +class DocumentNode: + 'A class to parse an xml text and store the elements.' + def __init__(self, fileName, xmlText): + 'Initialize.' + self.childNodes = [] + self.fileName = fileName + self.idDictionary = {} + self.nameDictionary = {} + self.parentNode = None + self.tagDictionary = {} + self.xmlText = xmlText + createAppendByText(self, xmlText) + + def __repr__(self): + 'Get the string representation of this xml document.' + output = cStringIO.StringIO() + for childNode in self.childNodes: + childNode.addXML(0, output) + return output.getvalue() + + def appendChild(self, elementNode): + 'Append child elementNode to the child nodes.' + self.childNodes.append(elementNode) + elementNode.addToIdentifierDictionaries() + return elementNode + + def getAttributes(self): + 'Get the attributes.' + return {} + + def getCascadeBoolean(self, defaultBoolean, key): + 'Get the cascade boolean.' + return defaultBoolean + + def getCascadeFloat(self, defaultFloat, key): + 'Get the cascade float.' + return defaultFloat + + def getDocumentElement(self): + 'Get the document element.' + if len(self.childNodes) == 0: + return None + return self.childNodes[-1] + + def getElementsByLocalName(self, localName): + 'Get the descendents which have the given local name.' + return getElementsByLocalName(self.childNodes, localName) + + def getImportNameChain(self, suffix=''): + 'Get the import name chain with the suffix at the end.' + return suffix + + def getNodeName(self): + 'Get the node name.' + return '#document' + + def getNodeType(self): + 'Get the node type.' + return 9 + + def getOriginalRoot(self): + 'Get the original reparsed document element.' + if evaluate.getEvaluatedBoolean(True, self.documentElement, 'getOriginalRoot'): + return DocumentNode(self.fileName, self.xmlText).documentElement + return None + + def getOwnerDocument(self): + 'Get the owner document.' + return self + + attributes = property(getAttributes) + documentElement = property(getDocumentElement) + nodeName = property(getNodeName) + nodeType = property(getNodeType) + ownerDocument = property(getOwnerDocument) + + +class DocumentTypeMonad(CDATASectionMonad): + 'A monad to handle a document type node.' + def getNextMonad(self, character): + 'Get the next monad.' + self.input.write(character) + if character == '>': + inputString = self.input.getvalue() + if inputString.endswith('?>'): + textContent = '%s\n' % inputString + self.parentNode.childNodes.append(DocumentTypeNode(self.parentNode, textContent)) + return OpenMonad(self.parentNode) + return self + + +class DocumentTypeNode(CDATASectionNode): + 'A document type node.' + def getCopyShallow(self, attributes=None): + 'Copy the node and set its parentNode.' + return DocumentTypeNode(self.parentNode, self.textContent) + + def getNodeName(self): + 'Get the node name.' + return '#forNowDocumentType' + + def getNodeType(self): + 'Get the node type.' + return 10 + + nodeName = property(getNodeName) + nodeType = property(getNodeType) + + +class ElementEndMonad: + 'A monad to look for the end of an ElementNode tag.' + def __init__(self, parentNode): + 'Initialize.' + self.parentNode = parentNode + + def getNextMonad(self, character): + 'Get the next monad.' + if character == '>': + return TextMonad(self.parentNode) + return self + + +class ElementLocalNameMonad: + 'A monad to set the local name of an ElementNode.' + def __init__(self, character, parentNode): + 'Initialize.' + self.input = cStringIO.StringIO() + self.input.write(character) + self.parentNode = parentNode + + def getNextMonad(self, character): + 'Get the next monad.' + if character == '[': + if (self.input.getvalue() + character).startswith('![CDATA['): + self.input.write(character) + return CDATASectionMonad(self.input, self.parentNode) + if character == '-': + if (self.input.getvalue() + character).startswith('!--'): + self.input.write(character) + return CommentMonad(self.input, self.parentNode) + if character.isspace(): + self.setLocalName() + return ElementReadMonad(self.elementNode) + if character == '/': + self.setLocalName() + self.elementNode.appendSelfToParent() + return ElementEndMonad(self.elementNode.parentNode) + if character == '>': + self.setLocalName() + self.elementNode.appendSelfToParent() + return TextMonad(self.elementNode) + self.input.write(character) + return self + + def setLocalName(self): + 'Set the class name.' + self.elementNode = ElementNode(self.parentNode) + self.elementNode.localName = self.input.getvalue().lower().strip() + + +class ElementNode: + 'An xml element.' + def __init__(self, parentNode=None): + 'Initialize.' + self.attributes = {} + self.childNodes = [] + self.localName = '' + self.parentNode = parentNode + self.xmlObject = None + + def __repr__(self): + 'Get the string representation of this xml document.' + return '%s\n%s\n%s' % (self.localName, self.attributes, self.getTextContent()) + + def _getAccessibleAttribute(self, attributeName): + 'Get the accessible attribute.' + global globalGetAccessibleAttributeSet + if attributeName in globalGetAccessibleAttributeSet: + return getattr(self, attributeName, None) + return None + + def addSuffixToID(self, idSuffix): + 'Add the suffix to the id.' + if 'id' in self.attributes: + self.attributes['id'] += idSuffix + + def addToIdentifierDictionaries(self): + 'Add the element to the owner document identifier dictionaries.' + ownerDocument = self.getOwnerDocument() + importNameChain = self.getImportNameChain() + idKey = self.getStrippedAttributesValue('id') + if idKey != None: + ownerDocument.idDictionary[importNameChain + idKey] = self + nameKey = self.getStrippedAttributesValue('name') + if nameKey != None: + euclidean.addElementToListDictionaryIfNotThere(self, importNameChain + nameKey, ownerDocument.nameDictionary) + for tagKey in self.getTagKeys(): + euclidean.addElementToListDictionaryIfNotThere(self, tagKey, ownerDocument.tagDictionary) + + def addXML(self, depth, output): + 'Add xml for this elementNode.' + innerOutput = cStringIO.StringIO() + xml_simple_writer.addXMLFromObjects(depth + 1, self.childNodes, innerOutput) + innerText = innerOutput.getvalue() + xml_simple_writer.addBeginEndInnerXMLTag(self.attributes, depth, innerText, self.localName, output, self.getTextContent()) + + def appendChild(self, elementNode): + 'Append child elementNode to the child nodes.' + self.childNodes.append(elementNode) + elementNode.addToIdentifierDictionaries() + return elementNode + + def appendSelfToParent(self): + 'Append self to the parentNode.' + self.parentNode.appendChild(self) + + def copyXMLChildNodes(self, idSuffix, parentNode): + 'Copy the xml childNodes.' + for childNode in self.childNodes: + childNode.getCopy(idSuffix, parentNode) + + def getCascadeBoolean(self, defaultBoolean, key): + 'Get the cascade boolean.' + if key in self.attributes: + value = evaluate.getEvaluatedBoolean(None, self, key) + if value != None: + return value + return self.parentNode.getCascadeBoolean(defaultBoolean, key) + + def getCascadeFloat(self, defaultFloat, key): + 'Get the cascade float.' + if key in self.attributes: + value = evaluate.getEvaluatedFloat(None, self, key) + if value != None: + return value + return self.parentNode.getCascadeFloat(defaultFloat, key) + + def getChildElementsByLocalName(self, localName): + 'Get the childNodes which have the given local name.' + return getChildElementsByLocalName(self.childNodes, localName) + + def getCopy(self, idSuffix, parentNode): + 'Copy the xml element, set its dictionary and add it to the parentNode.' + matrix4X4 = matrix.getBranchMatrixSetElementNode(self) + attributesCopy = self.attributes.copy() + attributesCopy.update(matrix4X4.getAttributes('matrix.')) + copy = self.getCopyShallow(attributesCopy) + copy.setParentAddToChildNodes(parentNode) + copy.addSuffixToID(idSuffix) + copy.addToIdentifierDictionaries() + self.copyXMLChildNodes(idSuffix, copy) + return copy + + def getCopyShallow(self, attributes=None): + 'Copy the xml element and set its dictionary and parentNode.' + if attributes == None: # to evade default initialization bug where a dictionary is initialized to the last dictionary + attributes = {} + copyShallow = ElementNode(self.parentNode) + copyShallow.attributes = attributes + copyShallow.localName = self.localName + return copyShallow + + def getDocumentElement(self): + 'Get the document element.' + return self.getOwnerDocument().getDocumentElement() + + def getElementNodeByID(self, idKey): + 'Get the xml element by id.' + idDictionary = self.getOwnerDocument().idDictionary + idKey = self.getImportNameChain() + idKey + if idKey in idDictionary: + return idDictionary[idKey] + return None + + def getElementNodesByName(self, nameKey): + 'Get the xml elements by name.' + nameDictionary = self.getOwnerDocument().nameDictionary + nameKey = self.getImportNameChain() + nameKey + if nameKey in nameDictionary: + return nameDictionary[nameKey] + return None + + def getElementNodesByTag(self, tagKey): + 'Get the xml elements by tag.' + tagDictionary = self.getOwnerDocument().tagDictionary + if tagKey in tagDictionary: + return tagDictionary[tagKey] + return None + + def getElementsByLocalName(self, localName): + 'Get the descendents which have the given local name.' + return getElementsByLocalName(self.childNodes, localName) + + def getFirstChildByLocalName(self, localName): + 'Get the first childNode which has the given class name.' + for childNode in self.childNodes: + if localName.lower() == childNode.getNodeName(): + return childNode + return None + + def getIDSuffix(self, elementIndex=None): + 'Get the id suffix from the dictionary.' + suffix = self.localName + if 'id' in self.attributes: + suffix = self.attributes['id'] + if elementIndex == None: + return '_%s' % suffix + return '_%s_%s' % (suffix, elementIndex) + + def getImportNameChain(self, suffix=''): + 'Get the import name chain with the suffix at the end.' + importName = self.getStrippedAttributesValue('_importName') + if importName != None: + suffix = '%s.%s' % (importName, suffix) + return self.parentNode.getImportNameChain(suffix) + + def getNodeName(self): + 'Get the node name.' + return self.localName + + def getNodeType(self): + 'Get the node type.' + return 1 + + def getOwnerDocument(self): + 'Get the owner document.' + return self.parentNode.getOwnerDocument() + + def getParser(self): + 'Get the parser.' + return self.getOwnerDocument() + + def getPaths(self): + 'Get all paths.' + if self.xmlObject == None: + return [] + return self.xmlObject.getPaths() + + def getPreviousElementNode(self): + 'Get previous ElementNode if it exists.' + if self.parentNode == None: + return None + previousElementNodeIndex = self.parentNode.childNodes.index(self) - 1 + if previousElementNodeIndex < 0: + return None + return self.parentNode.childNodes[previousElementNodeIndex] + + def getPreviousVertex(self, defaultVector3=None): + 'Get previous vertex if it exists.' + if self.parentNode == None: + return defaultVector3 + if self.parentNode.xmlObject == None: + return defaultVector3 + if len(self.parentNode.xmlObject.vertexes) < 1: + return defaultVector3 + return self.parentNode.xmlObject.vertexes[-1] + + def getStrippedAttributesValue(self, keyString): + 'Get the stripped attribute value if the length is at least one, otherwise return None.' + if keyString in self.attributes: + strippedAttributesValue = self.attributes[keyString].strip() + if len(strippedAttributesValue) > 0: + return strippedAttributesValue + return None + + def getSubChildWithID( self, idReference ): + 'Get the childNode which has the idReference.' + for childNode in self.childNodes: + if 'bf:id' in childNode.attributes: + if childNode.attributes['bf:id'] == idReference: + return childNode + subChildWithID = childNode.getSubChildWithID( idReference ) + if subChildWithID != None: + return subChildWithID + return None + + def getTagKeys(self): + 'Get stripped tag keys.' + if 'tags' not in self.attributes: + return [] + tagKeys = [] + tagString = self.attributes['tags'] + if tagString.startswith('='): + tagString = tagString[1 :] + if tagString.startswith('['): + tagString = tagString[1 :] + if tagString.endswith(']'): + tagString = tagString[: -1] + for tagWord in tagString.split(','): + tagKey = tagWord.strip() + if tagKey != '': + tagKeys.append(tagKey) + return tagKeys + + def getTextContent(self): + 'Get the text from the child nodes.' + if len(self.childNodes) == 0: + return '' + firstNode = self.childNodes[0] + if firstNode.nodeType == 3: + return firstNode.textContent + return '' + + def getValueByKey( self, key ): + 'Get value by the key.' + if key in evaluate.globalElementValueDictionary: + return evaluate.globalElementValueDictionary[key](self) + if key in self.attributes: + return evaluate.getEvaluatedLinkValue(self, self.attributes[key]) + return None + + def getVertexes(self): + 'Get the vertexes.' + if self.xmlObject == None: + return [] + return self.xmlObject.getVertexes() + + def getXMLProcessor(self): + 'Get the xmlProcessor.' + return self.getDocumentElement().xmlProcessor + + def linkObject(self, xmlObject): + 'Link self to xmlObject and add xmlObject to archivableObjects.' + self.xmlObject = xmlObject + self.xmlObject.elementNode = self + self.parentNode.xmlObject.archivableObjects.append(self.xmlObject) + + def printAllVariables(self): + 'Print all variables.' + print('attributes') + print(self.attributes) + print('childNodes') + print(self.childNodes) + print('localName') + print(self.localName) + print('parentNode') + print(self.parentNode.getNodeName()) + print('text') + print(self.getTextContent()) + print('xmlObject') + print(self.xmlObject) + print('') + + def printAllVariablesRoot(self): + 'Print all variables and the document element variables.' + self.printAllVariables() + documentElement = self.getDocumentElement() + if documentElement != None: + print('') + print('Root variables:') + documentElement.printAllVariables() + + def removeChildNodesFromIDNameParent(self): + 'Remove the childNodes from the id and name dictionaries and the childNodes.' + childNodesCopy = self.childNodes[:] + for childNode in childNodesCopy: + childNode.removeFromIDNameParent() + + def removeFromIDNameParent(self): + 'Remove this from the id and name dictionaries and the childNodes of the parentNode.' + self.removeChildNodesFromIDNameParent() + idKey = self.getStrippedAttributesValue('id') + if idKey != None: + idDictionary = self.getOwnerDocument().idDictionary + idKey = self.getImportNameChain() + idKey + if idKey in idDictionary: + del idDictionary[idKey] + nameKey = self.getStrippedAttributesValue('name') + if nameKey != None: + euclidean.removeElementFromListTable(self, self.getImportNameChain() + nameKey, self.getOwnerDocument().nameDictionary) + for tagKey in self.getTagKeys(): + euclidean.removeElementFromListTable(self, tagKey, self.getOwnerDocument().tagDictionary) + if self.parentNode != None: + self.parentNode.childNodes.remove(self) + + def setParentAddToChildNodes(self, parentNode): + 'Set the parentNode and add this to its childNodes.' + self.parentNode = parentNode + if self.parentNode != None: + self.parentNode.childNodes.append(self) + + def setTextContent(self, textContent=''): + 'Get the text from the child nodes.' + if len(self.childNodes) == 0: + self.childNodes.append(TextNode(self, textContent)) + return + firstNode = self.childNodes[0] + if firstNode.nodeType == 3: + firstNode.textContent = textContent + self.childNodes.append(TextNode(self, textContent)) + + nodeName = property(getNodeName) + nodeType = property(getNodeType) + ownerDocument = property(getOwnerDocument) + textContent = property(getTextContent) + + +class ElementReadMonad: + 'A monad to read the attributes of the ElementNode tag.' + def __init__(self, elementNode): + 'Initialize.' + self.elementNode = elementNode + + def getNextMonad(self, character): + 'Get the next monad.' + if character.isspace(): + return self + if character == '/': + self.elementNode.appendSelfToParent() + return ElementEndMonad(self.elementNode.parentNode) + if character == '>': + self.elementNode.appendSelfToParent() + return TextMonad(self.elementNode) + return KeyMonad(character, self.elementNode) + + +class KeyMonad: + 'A monad to set the key of an attribute of an ElementNode.' + def __init__(self, character, elementNode): + 'Initialize.' + self.input = cStringIO.StringIO() + self.input.write(character) + self.elementNode = elementNode + + def getNextMonad(self, character): + 'Get the next monad.' + if character == '=': + return ValueMonad(self.elementNode, self.input.getvalue().strip()) + self.input.write(character) + return self + + +class OpenChooseMonad(ElementEndMonad): + 'A monad to choose the next monad.' + def getNextMonad(self, character): + 'Get the next monad.' + if character.isspace(): + return self + if character == '?': + input = cStringIO.StringIO() + input.write(' 0: + self.parentNode.childNodes.append(TextNode(self.parentNode, inputString)) + return OpenChooseMonad(self.parentNode) + self.input.write(character) + return self + + +class TextNode(CDATASectionNode): + 'A text node.' + def addXML(self, depth, output): + 'Add xml for this text node.' + pass + + def getCopyShallow(self, attributes=None): + 'Copy the node and set its parentNode.' + return TextNode(self.parentNode, self.textContent) + + def getNodeName(self): + 'Get the node name.' + return '#text' + + def getNodeType(self): + 'Get the node type.' + return 3 + + nodeName = property(getNodeName) + nodeType = property(getNodeType) + + +class ValueMonad: + 'A monad to set the value of an attribute of an ElementNode.' + def __init__(self, elementNode, key): + 'Initialize.' + self.elementNode = elementNode + self.input = cStringIO.StringIO() + self.key = key + self.quoteCharacter = None + + def getNextMonad(self, character): + 'Get the next monad.' + if self.quoteCharacter == None: + if character == '"' or character == "'": + self.quoteCharacter = character + return self + if self.quoteCharacter == character: + self.elementNode.attributes[self.key] = self.input.getvalue() + return ElementReadMonad(self.elementNode) + self.input.write(character) + return self + diff --git a/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_writer.py b/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_writer.py new file mode 100644 index 0000000..99e5ca6 --- /dev/null +++ b/SkeinPyPy_NewUI/fabmetheus_utilities/xml_simple_writer.py @@ -0,0 +1,132 @@ +""" +XML tag writer utilities. + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +import cStringIO + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead \nArt of Illusion ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addBeginEndInnerXMLTag(attributes, depth, innerText, localName, output, text=''): + 'Add the begin and end xml tag and the inner text if any.' + if len( innerText ) > 0: + addBeginXMLTag(attributes, depth, localName, output, text) + output.write( innerText ) + addEndXMLTag(depth, localName, output) + else: + addClosedXMLTag(attributes, depth, localName, output, text) + +def addBeginXMLTag(attributes, depth, localName, output, text=''): + 'Add the begin xml tag.' + depthStart = '\t' * depth + output.write('%s<%s%s>%s\n' % (depthStart, localName, getAttributesString(attributes), text)) + +def addClosedXMLTag(attributes, depth, localName, output, text=''): + 'Add the closed xml tag.' + depthStart = '\t' * depth + attributesString = getAttributesString(attributes) + if len(text) > 0: + output.write('%s<%s%s >%s\n' % (depthStart, localName, attributesString, text, localName)) + else: + output.write('%s<%s%s />\n' % (depthStart, localName, attributesString)) + +def addEndXMLTag(depth, localName, output): + 'Add the end xml tag.' + depthStart = '\t' * depth + output.write('%s\n' % (depthStart, localName)) + +def addXMLFromLoopComplexZ(attributes, depth, loop, output, z): + 'Add xml from loop.' + addBeginXMLTag(attributes, depth, 'path', output) + for pointComplexIndex in xrange(len(loop)): + pointComplex = loop[pointComplexIndex] + addXMLFromXYZ(depth + 1, pointComplexIndex, output, pointComplex.real, pointComplex.imag, z) + addEndXMLTag(depth, 'path', output) + +def addXMLFromObjects(depth, objects, output): + 'Add xml from objects.' + for object in objects: + object.addXML(depth, output) + +def addXMLFromVertexes(depth, output, vertexes): + 'Add xml from loop.' + for vertexIndex in xrange(len(vertexes)): + vertex = vertexes[vertexIndex] + addXMLFromXYZ(depth + 1, vertexIndex, output, vertex.x, vertex.y, vertex.z) + +def addXMLFromXYZ(depth, index, output, x, y, z): + 'Add xml from x, y & z.' + attributes = {'index' : str(index)} + if x != 0.0: + attributes['x'] = str(x) + if y != 0.0: + attributes['y'] = str(y) + if z != 0.0: + attributes['z'] = str(z) + addClosedXMLTag(attributes, depth, 'vertex', output) + +def compareAttributeKeyAscending(key, otherKey): + 'Get comparison in order to sort attribute keys in ascending order, with the id key first and name second.' + if key == 'id': + return - 1 + if otherKey == 'id': + return 1 + if key == 'name': + return - 1 + if otherKey == 'name': + return 1 + if key < otherKey: + return - 1 + return int(key > otherKey) + +def getAttributesString(attributes): + 'Add the closed xml tag.' + attributesString = '' + attributesKeys = attributes.keys() + attributesKeys.sort(compareAttributeKeyAscending) + for attributesKey in attributesKeys: + valueString = str(attributes[attributesKey]) + if "'" in valueString: + attributesString += ' %s="%s"' % (attributesKey, valueString) + else: + attributesString += " %s='%s'" % (attributesKey, valueString) + return attributesString + +def getBeginGeometryXMLOutput(elementNode=None): + 'Get the beginning of the string representation of this boolean geometry object info.' + output = getBeginXMLOutput() + attributes = {} + if elementNode != None: + documentElement = elementNode.getDocumentElement() + attributes = documentElement.attributes + addBeginXMLTag(attributes, 0, 'fabmetheus', output) + return output + +def getBeginXMLOutput(): + 'Get the beginning of the string representation of this object info.' + output = cStringIO.StringIO() + output.write("\n") + return output + +def getDictionaryWithoutList(dictionary, withoutList): + 'Get the dictionary without the keys in the list.' + dictionaryWithoutList = {} + for key in dictionary: + if key not in withoutList: + dictionaryWithoutList[key] = dictionary[key] + return dictionaryWithoutList + +def getEndGeometryXMLString(output): + 'Get the string representation of this object info.' + addEndXMLTag(0, 'fabmetheus', output) + return output.getvalue() diff --git a/SkeinPyPy_NewUI/models/Screw Holder Bottom.stl b/SkeinPyPy_NewUI/models/Screw Holder Bottom.stl new file mode 100644 index 0000000..f372a18 --- /dev/null +++ b/SkeinPyPy_NewUI/models/Screw Holder Bottom.stl @@ -0,0 +1,2886 @@ +solid "Screw_Holder_Bottom"; Produced by Art of Illusion 2.4, Fri Oct 16 16:42:04 PDT 2009 +facet normal 0 -0.831469612304 0.555570233017 + outer loop + vertex 54.002309837942 24.804173569059 0.110046151702 + vertex 48.011309837942 24.804173569059 0.110046151702 + vertex 54.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 24.730643081307 0 + vertex 14.883210818105 25.830303993361 0 + vertex 15.262563132924 25.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 60 23 0 + vertex 54.002309837942 24.730643081307 0 + vertex 60 40 0 + endloop +endfacet +facet normal 0 0.831469612301 0.555570233022 + outer loop + vertex 54.002309837942 15.269356918694 0 + vertex 48.011309837942 15.269356918694 0 + vertex 54.002309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.464705848856 10 0 + vertex 15.830303993361 11.883210818105 0 + vertex 16.5 11.75 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 30 0 + vertex 43.737436867076 27.737436867076 0 + vertex 43.169696006639 28.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 43.169696006639 15.116789181895 0 + vertex 42.5 15.25 0 + vertex 41.422110817733 17 0 + endloop +endfacet +facet normal 0 -0.831469612304 0.555570233017 + outer loop + vertex 48.011309837942 24.730643081307 0 + vertex 54.002309837942 24.730643081307 0 + vertex 48.011309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 30 0 + vertex 43.169696006639 28.116789181895 0 + vertex 42.5 28.25 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 60 40 0 + vertex 52 30 0 + vertex 52 40 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.883210818105 14.169696006639 0 + vertex 14.75 13.5 0 + vertex 12.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.883210818105 27.169696006639 0 + vertex 14.042553191489 30 0 + vertex 15.262563132924 27.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.464705848856 10 0 + vertex 16.5 11.75 0 + vertex 17.169696006639 11.883210818105 0 + endloop +endfacet +facet normal 0 0.831469612301 0.555570233022 + outer loop + vertex 48.011309837942 15.195826430941 0.110046151702 + vertex 54.002309837942 15.195826430941 0.110046151702 + vertex 48.011309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.042553191489 30 0 + vertex 17.169696006639 28.116789181895 0 + vertex 16.5 28.25 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 15.269356918694 0 + vertex 14.75 13.5 0 + vertex 14.883210818105 12.830303993361 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 54.002309837942 17 0 + vertex 60 17 0 + vertex 54.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 10 0 + vertex 42.5 11.75 0 + vertex 43.169696006639 11.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 15.269356918694 0 + vertex 15.262563132924 14.737436867076 0 + vertex 14.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 10 0 + vertex 14.464705848856 10 0 + vertex 42.5 11.75 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.042553191489 30 0 + vertex 15.830303993361 28.116789181895 0 + vertex 15.262563132924 27.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.464705848856 10 0 + vertex 15.262563132924 12.262563132924 0 + vertex 15.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.042553191489 30 0 + vertex 16.5 28.25 0 + vertex 15.830303993361 28.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 54.002309837942 24.730643081307 0 + vertex 60 23 0 + vertex 54.002309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 23 0 + vertex 16.5 24.75 0 + vertex 17.169696006639 24.883210818105 0 + endloop +endfacet +facet normal 0 0.831469612301 0.555570233022 + outer loop + vertex 6.011309837942 15.269356918694 0 + vertex 6.011309837942 15.195826430941 0.110046151702 + vertex 12.002309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.75 26.5 0 + vertex 12.002309837942 24.730643081307 0 + vertex 14.883210818105 27.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 30 0 + vertex 60 40 0 + vertex 54.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.422110817733 17 0 + vertex 48.011309837942 17 0 + vertex 43.169696006639 15.116789181895 0 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 14.75 13.5 0 + vertex 14.883210818105 12.830303993361 0.77775 + vertex 14.883210818105 12.830303993361 0 + endloop +endfacet +facet normal -0 -0 -1 + outer loop + vertex 6.011309837942 17 0 + vertex 6.011309837942 15.269356918694 0 + vertex 0 17 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.422110817733 17 0 + vertex 41.830303993361 15.116789181895 0 + vertex 41.262563132924 14.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.169696006639 11.883210818105 0 + vertex 17.737436867076 12.262563132924 0 + vertex 41.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 6.011309837942 15.269356918694 0 + vertex 0 10 0 + vertex 0 17 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.883210818105 25.830303993361 0 + vertex 12.002309837942 24.730643081307 0 + vertex 14.75 26.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 24.730643081307 0 + vertex 52 30 0 + vertex 54.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 17 0 + vertex 43.737436867076 14.737436867076 0 + vertex 43.169696006639 15.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 42.5 15.25 0 + vertex 41.830303993361 15.116789181895 0 + vertex 41.422110817733 17 0 + endloop +endfacet +facet normal -0 -0 -1 + outer loop + vertex 12.002309837942 15.269356918694 0 + vertex 14.883210818105 12.830303993361 0 + vertex 6.011309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 0 23 0 + vertex 0 30 0 + vertex 6.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0.831469612301 0.555570233022 + outer loop + vertex 12.002309837942 15.269356918694 0 + vertex 6.011309837942 15.269356918694 0 + vertex 12.002309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 54.002309837942 15.269356918694 0 + vertex 60 17 0 + vertex 52 10 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 6.011309837942 23 0 + vertex 0 23 0 + vertex 6.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 14.169696006639 0 + vertex 48.011309837942 15.269356918694 0 + vertex 44.25 13.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.116789181895 27.169696006639 0 + vertex 41.262563132924 25.262563132924 0 + vertex 18.25 26.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.116789181895 25.830303993361 0 + vertex 18.25 26.5 0 + vertex 41.830303993361 24.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.737436867076 14.737436867076 0 + vertex 41.262563132924 14.737436867076 0 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal -0 -0 -1 + outer loop + vertex 41.262563132924 25.262563132924 0 + vertex 41.830303993361 24.883210818105 0 + vertex 18.25 26.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.737436867076 25.262563132924 0 + vertex 18.116789181895 25.830303993361 0 + vertex 41.830303993361 24.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.737436867076 12.262563132924 0 + vertex 18.116789181895 12.830303993361 0 + vertex 40.883210818105 12.830303993361 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.737436867076 12.262563132924 0 + vertex 41.262563132924 12.262563132924 0 + vertex 41.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.262563132924 27.737436867076 0 + vertex 17.169696006639 28.116789181895 0 + vertex 41.830303993361 28.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 42.5 11.75 0 + vertex 14.464705848856 10 0 + vertex 17.169696006639 11.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.25 26.5 0 + vertex 44.116789181895 27.169696006639 0 + vertex 48.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 40.883210818105 12.830303993361 0 + vertex 41.262563132924 12.262563132924 0 + vertex 17.737436867076 12.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.169696006639 24.883210818105 0 + vertex 17.737436867076 25.262563132924 0 + vertex 41.830303993361 24.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 12.830303993361 0 + vertex 44.25 13.5 0 + vertex 48.011309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.116789181895 12.830303993361 0 + vertex 40.75 13.5 0 + vertex 40.883210818105 12.830303993361 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.25 13.5 0 + vertex 18.116789181895 14.169696006639 0 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.262563132924 14.737436867076 0 + vertex 17.737436867076 14.737436867076 0 + vertex 17.169696006639 15.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.116789181895 14.169696006639 0 + vertex 17.737436867076 14.737436867076 0 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 25.830303993361 0 + vertex 44.25 26.5 0 + vertex 48.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 14.883210818105 14.169696006639 0.77775 + vertex 14.75 13.5 0.77775 + vertex 14.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 14.75 13.5 0.77775 + vertex 14.75 13.5 0 + vertex 14.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 41.830303993361 28.116789181895 0 + vertex 42.5 28.25 0.77775 + vertex 41.830303993361 28.116789181895 0.77775 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 15.830303993361 24.883210818105 0.77775 + vertex 15.830303993361 24.883210818105 0 + vertex 15.262563132924 25.262563132924 0 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 16.5 28.25 0 + vertex 17.169696006639 28.116789181895 0.77775 + vertex 16.5 28.25 0.77775 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 43.169696006639 24.883210818105 0 + vertex 42.5 24.75 0.77775 + vertex 43.169696006639 24.883210818105 0.77775 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 16.5 28.25 0.77775 + vertex 15.830303993361 28.116789181895 0.77775 + vertex 16.5 28.25 0 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 15.830303993361 28.116789181895 0.77775 + vertex 15.830303993361 28.116789181895 0 + vertex 16.5 28.25 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 0 23 0 + vertex 6.011309837942 23 0.77775 + vertex 0 23 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 60 0 0.77775 + vertex 52 10 0.77775 + vertex 52 0 0.77775 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 40.883210818105 27.169696006639 0 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 40.75 26.5 0 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 17.169696006639 24.883210818105 0.77775 + vertex 17.169696006639 24.883210818105 0 + vertex 16.5 24.75 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 52 0 0 + vertex 52 0 0.77775 + vertex 52 10 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 34.416922535211 17 0.77775 + vertex 40.883210818105 12.830303993361 0.77775 + vertex 40.75 13.5 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 12.002309837942 24.936988122232 0.77775 + vertex 12.002309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 42.5 24.75 0 + vertex 41.830303993361 24.883210818105 0.77775 + vertex 42.5 24.75 0.77775 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 60 0 0 + vertex 60 0 0.77775 + vertex 52 0 0 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 15.830303993361 28.116789181895 0.77775 + vertex 15.262563132924 27.737436867076 0.77775 + vertex 15.830303993361 28.116789181895 0 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 15.262563132924 27.737436867076 0.77775 + vertex 15.262563132924 27.737436867076 0 + vertex 15.830303993361 28.116789181895 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 6.011309837942 17 0.77775 + vertex 6.011309837942 17 0 + vertex 0 17 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 17 0 + vertex 48.011309837942 17 0.77775 + vertex 48.011309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 0 + outer loop + vertex 18.25 26.5 0 + vertex 18.25 26.5 0.77775 + vertex 18.116789181895 27.169696006639 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.262563132924 14.737436867076 0.77775 + vertex 15.830303993361 15.116789181895 0.77775 + vertex 15.455907969142 17 0.77775 + endloop +endfacet +facet normal 0 0.980785280403 0.195090322015 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 54.002309837942 15.195826430941 0.110046151702 + vertex 48.011309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 0 + outer loop + vertex 44.25 26.5 0 + vertex 44.25 26.5 0.77775 + vertex 44.116789181895 27.169696006639 0.77775 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 52 10 0 + vertex 15.306548743796 10 0.77775 + vertex 14.464705848856 10 0 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 40.75 26.5 0.77775 + vertex 40.883210818105 25.830303993361 0 + vertex 40.75 26.5 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 15.063011877768 0.77775 + vertex 48.011309837942 15.195826430941 0.110046151702 + vertex 48.011309837942 15.269356918694 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 44.116789181895 27.169696006639 0.77775 + vertex 43.737436867076 27.737436867076 0.77775 + vertex 44.116789181895 27.169696006639 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 43.737436867076 27.737436867076 0.77775 + vertex 43.737436867076 27.737436867076 0 + vertex 44.116789181895 27.169696006639 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 52 10 0 + vertex 52 10 0.77775 + vertex 15.306548743796 10 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 23 0.77775 + vertex 6.011309837942 23 0 + vertex 6.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 34.416922535211 17 0.77775 + vertex 40.883210818105 14.169696006639 0.77775 + vertex 41.262563132924 14.737436867076 0.77775 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 14.75 26.5 0 + vertex 14.883210818105 25.830303993361 0.77775 + vertex 14.883210818105 25.830303993361 0 + endloop +endfacet +facet normal 0 -0.980785280403 0.195090322015 + outer loop + vertex 6.011309837942 24.936988122232 0.77775 + vertex 6.011309837942 24.804173569059 0.110046151702 + vertex 12.002309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 18.116789181895 14.169696006639 0.77775 + vertex 17.737436867076 14.737436867076 0.77775 + vertex 18.116789181895 14.169696006639 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 17.737436867076 14.737436867076 0.77775 + vertex 17.737436867076 14.737436867076 0 + vertex 18.116789181895 14.169696006639 0 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 42.5 15.25 0.77775 + vertex 41.830303993361 15.116789181895 0.77775 + vertex 42.5 15.25 0 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 41.830303993361 15.116789181895 0.77775 + vertex 41.830303993361 15.116789181895 0 + vertex 42.5 15.25 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 15.262563132924 14.737436867076 0.77775 + vertex 14.883210818105 14.169696006639 0.77775 + vertex 15.262563132924 14.737436867076 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 14.883210818105 14.169696006639 0.77775 + vertex 14.883210818105 14.169696006639 0 + vertex 15.262563132924 14.737436867076 0 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 0 + outer loop + vertex 43.169696006639 15.116789181895 0 + vertex 43.737436867076 14.737436867076 0.77775 + vertex 43.169696006639 15.116789181895 0.77775 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 14.75 13.5 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + vertex 14.75 13.5 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 41.262563132924 14.737436867076 0.77775 + vertex 40.883210818105 14.169696006639 0.77775 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 42.5 11.75 0.77775 + vertex 43.169696006639 11.883210818105 0.77775 + vertex 43.169696006639 11.883210818105 0 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 -0 + outer loop + vertex 17.169696006639 15.116789181895 0.77775 + vertex 17.169696006639 15.116789181895 0 + vertex 17.737436867076 14.737436867076 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 12.002309837942 24.730643081307 0 + vertex 12.002309837942 23 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 6.011309837942 23 0.77775 + vertex 0 23 0 + vertex 6.011309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 25.830303993361 0 + vertex 48.011309837942 23 0 + vertex 43.737436867076 25.262563132924 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 15.195826430941 0.110046151702 + vertex 12.002309837942 17 0.77775 + vertex 12.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 0 10 0 + vertex 6.011309837942 15.269356918694 0 + vertex 14.883210818105 12.830303993361 0 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 -0 + outer loop + vertex 17.169696006639 28.116789181895 0.77775 + vertex 16.5 28.25 0 + vertex 17.169696006639 28.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 24.730643081307 0 + vertex 44.116789181895 27.169696006639 0 + vertex 43.737436867076 27.737436867076 0 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 44.25 26.5 0.77775 + vertex 44.25 26.5 0 + vertex 44.116789181895 25.830303993361 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 40.75 26.5 0.77775 + vertex 37.474084379442 23 0.77775 + vertex 40.883210818105 25.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 34.416922535211 17 0.77775 + vertex 40.75 13.5 0.77775 + vertex 40.883210818105 14.169696006639 0.77775 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 17.169696006639 11.883210818105 0.77775 + vertex 17.169696006639 11.883210818105 0 + vertex 16.5 11.75 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 43.737436867076 27.737436867076 0.77775 + vertex 52 30 0.77775 + vertex 41.866228156844 30 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 12.002309837942 15.063011877768 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + vertex 14.75 13.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 48.011309837942 15.063011877768 0.77775 + vertex 52 10 0.77775 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 41.830303993361 24.883210818105 0.77775 + vertex 42.5 24.75 0 + vertex 41.830303993361 24.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 6.011309837942 24.730643081307 0 + vertex 14.883210818105 27.169696006639 0 + vertex 12.002309837942 24.730643081307 0 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 -0 + outer loop + vertex 43.737436867076 14.737436867076 0.77775 + vertex 43.169696006639 15.116789181895 0 + vertex 43.737436867076 14.737436867076 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 43.737436867076 27.737436867076 0.77775 + vertex 41.866228156844 30 0.77775 + vertex 43.169696006639 28.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 34.416922535211 17 0.77775 + vertex 41.830303993361 15.116789181895 0.77775 + vertex 42.5 15.25 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 16.5 11.75 0.77775 + vertex 15.830303993361 11.883210818105 0.77775 + vertex 15.306548743796 10 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 0 23 0.77775 + vertex 0 30 0 + vertex 0 23 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 0 30 0 + vertex 0 23 0.77775 + vertex 0 30 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 52 10 0 + vertex 52 0 0.77775 + vertex 52 10 0.77775 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 17.169696006639 11.883210818105 0 + vertex 17.169696006639 11.883210818105 0.77775 + vertex 17.737436867076 12.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 43.169696006639 28.116789181895 0.77775 + vertex 41.866228156844 30 0.77775 + vertex 42.5 28.25 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 14.042553191489 30 0 + vertex 41.866228156844 30 0.77775 + vertex 52 30 0 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 41.830303993361 11.883210818105 0.77775 + vertex 42.5 11.75 0 + vertex 41.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 15.262563132924 12.262563132924 0.77775 + vertex 15.830303993361 11.883210818105 0 + vertex 15.262563132924 12.262563132924 0 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 40.883210818105 25.830303993361 0 + vertex 40.75 26.5 0.77775 + vertex 40.883210818105 25.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 18.116789181895 27.169696006639 0 + vertex 40.883210818105 25.830303993361 0 + vertex 41.262563132924 25.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 42.5 28.25 0 + vertex 17.169696006639 28.116789181895 0 + vertex 14.042553191489 30 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 15.262563132924 27.737436867076 0.77775 + vertex 15.830303993361 28.116789181895 0.77775 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 14.883210818105 25.830303993361 0.77775 + vertex 15.262563132924 25.262563132924 0.77775 + vertex 14.883210818105 25.830303993361 0 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 15.262563132924 25.262563132924 0.77775 + vertex 15.262563132924 25.262563132924 0 + vertex 14.883210818105 25.830303993361 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 24.804173569059 0.110046151702 + vertex 48.011309837942 23 0.77775 + vertex 48.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 24.730643081307 0 + vertex 43.737436867076 27.737436867076 0 + vertex 52 30 0 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 18.116789181895 25.830303993361 0 + vertex 18.116789181895 25.830303993361 0.77775 + vertex 18.25 26.5 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 15.195826430941 0.110046151702 + vertex 54.002309837942 15.063011877768 0.77775 + vertex 54.002309837942 17 0.77775 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 41.830303993361 15.116789181895 0 + vertex 41.830303993361 15.116789181895 0.77775 + vertex 41.262563132924 14.737436867076 0.77775 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 -0 + outer loop + vertex 43.737436867076 27.737436867076 0.77775 + vertex 43.169696006639 28.116789181895 0.77775 + vertex 43.737436867076 27.737436867076 0 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 -0 + outer loop + vertex 43.169696006639 28.116789181895 0.77775 + vertex 43.169696006639 28.116789181895 0 + vertex 43.737436867076 27.737436867076 0 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 41.830303993361 11.883210818105 0.77775 + vertex 42.5 11.75 0.77775 + vertex 42.5 11.75 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 15.830303993361 24.883210818105 0.77775 + vertex 15.262563132924 25.262563132924 0.77775 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 41.262563132924 14.737436867076 0.77775 + vertex 41.262563132924 14.737436867076 0 + vertex 41.830303993361 15.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 60 17 0 + vertex 60 0 0 + vertex 52 0 0 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 15.262563132924 25.262563132924 0 + vertex 15.262563132924 25.262563132924 0.77775 + vertex 15.830303993361 24.883210818105 0.77775 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 12.002309837942 23 0 + vertex 17.379510917074 23 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 24.936988122232 0.77775 + vertex 6.011309837942 23 0.77775 + vertex 6.011309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.169696006639 11.883210818105 0 + vertex 41.830303993361 11.883210818105 0 + vertex 42.5 11.75 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 42.5 28.25 0 + vertex 14.042553191489 30 0 + vertex 52 30 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 17 0.77775 + vertex 43.169696006639 15.116789181895 0.77775 + vertex 43.737436867076 14.737436867076 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 15.269356918694 0 + vertex 6.011309837942 17 0.77775 + vertex 6.011309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 -0 + outer loop + vertex 18.116789181895 27.169696006639 0.77775 + vertex 18.116789181895 27.169696006639 0 + vertex 18.25 26.5 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 40.75 26.5 0 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 40.75 26.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 0 30 0.77775 + vertex 14.883210818105 27.169696006639 0.77775 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 42.5 11.75 0.77775 + vertex 43.169696006639 11.883210818105 0 + vertex 42.5 11.75 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 15.830303993361 28.116789181895 0.77775 + vertex 16.5 28.25 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 52 30 0.77775 + vertex 52 30 0 + vertex 41.866228156844 30 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 60 17 0 + vertex 52 0 0 + vertex 52 10 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 0 + outer loop + vertex 44.116789181895 14.169696006639 0.77775 + vertex 43.737436867076 14.737436867076 0.77775 + vertex 43.737436867076 14.737436867076 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 41.866228156844 30 0.77775 + vertex 14.042553191489 30 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.169696006639 11.883210818105 0.77775 + vertex 15.306548743796 10 0.77775 + vertex 17.737436867076 12.262563132924 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 15.269356918694 0 + vertex 12.002309837942 17 0.77775 + vertex 12.002309837942 17 0 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 17.169696006639 24.883210818105 0.77775 + vertex 16.5 24.75 0 + vertex 16.5 24.75 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 23 0.77775 + vertex 43.169696006639 24.883210818105 0.77775 + vertex 42.5 24.75 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 6.011309837942 17 0.77775 + vertex 0 17 0 + vertex 0 17 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 16.5 28.25 0.77775 + vertex 17.169696006639 28.116789181895 0.77775 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 18.116789181895 25.830303993361 0.77775 + vertex 17.737436867076 25.262563132924 0 + vertex 17.737436867076 25.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 16.5 24.75 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 17.169696006639 24.883210818105 0.77775 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 -0 + outer loop + vertex 44.116789181895 27.169696006639 0.77775 + vertex 44.116789181895 27.169696006639 0 + vertex 44.25 26.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 40.75 13.5 0 + vertex 18.25 13.5 0 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 25.830303993361 0 + vertex 48.011309837942 24.730643081307 0 + vertex 48.011309837942 23 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 0 10 0.77775 + vertex 0 10 0 + vertex 14.464705848856 10 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.306548743796 10 0.77775 + vertex 15.830303993361 11.883210818105 0.77775 + vertex 15.262563132924 12.262563132924 0.77775 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 43.169696006639 28.116789181895 0 + vertex 43.169696006639 28.116789181895 0.77775 + vertex 42.5 28.25 0.77775 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 17.169696006639 11.883210818105 0.77775 + vertex 16.5 11.75 0 + vertex 16.5 11.75 0.77775 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 0 + outer loop + vertex 18.25 13.5 0.77775 + vertex 18.116789181895 14.169696006639 0.77775 + vertex 18.116789181895 14.169696006639 0 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 17.169696006639 24.883210818105 0.77775 + vertex 17.737436867076 25.262563132924 0 + vertex 17.169696006639 24.883210818105 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 0 10 0.77775 + vertex 14.464705848856 10 0 + vertex 15.306548743796 10 0.77775 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 0 + outer loop + vertex 17.169696006639 15.116789181895 0.77775 + vertex 17.737436867076 14.737436867076 0 + vertex 17.737436867076 14.737436867076 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 23 0.77775 + vertex 48.011309837942 24.804173569059 0.110046151702 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.830303993361 28.116789181895 0.77775 + vertex 41.866228156844 30 0.77775 + vertex 41.262563132924 27.737436867076 0.77775 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 16.5 11.75 0.77775 + vertex 15.830303993361 11.883210818105 0 + vertex 15.830303993361 11.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.75 26.5 0.77775 + vertex 12.002309837942 24.936988122232 0.77775 + vertex 14.883210818105 25.830303993361 0.77775 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 44.116789181895 14.169696006639 0.77775 + vertex 43.737436867076 14.737436867076 0 + vertex 44.116789181895 14.169696006639 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 12.002309837942 24.804173569059 0.110046151702 + vertex 12.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 60 17 0.77775 + vertex 54.002309837942 17 0.77775 + endloop +endfacet +facet normal 0 0.980785280403 0.195090322015 + outer loop + vertex 6.011309837942 15.063011877768 0.77775 + vertex 12.002309837942 15.195826430941 0.110046151702 + vertex 6.011309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 12.002309837942 23 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 15.830303993361 24.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.830303993361 24.883210818105 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 16.5 24.75 0.77775 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 18.25 26.5 0.77775 + vertex 18.25 26.5 0 + vertex 18.116789181895 25.830303993361 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.262563132924 25.262563132924 0.77775 + vertex 37.474084379442 23 0.77775 + vertex 41.830303993361 24.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.607403236621 30 0.77775 + vertex 14.883210818105 27.169696006639 0.77775 + vertex 15.262563132924 27.737436867076 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 14.737436867076 0.77775 + vertex 15.455907969142 17 0.77775 + vertex 17.169696006639 15.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 14.737436867076 0.77775 + vertex 34.416922535211 17 0.77775 + vertex 15.455907969142 17 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.306548743796 10 0.77775 + vertex 15.262563132924 12.262563132924 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 17.169696006639 24.883210818105 0.77775 + vertex 17.737436867076 25.262563132924 0.77775 + vertex 17.737436867076 25.262563132924 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 16.5 15.25 0.77775 + vertex 15.455907969142 17 0.77775 + vertex 15.830303993361 15.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.830303993361 28.116789181895 0.77775 + vertex 42.5 28.25 0.77775 + vertex 41.866228156844 30 0.77775 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 18.116789181895 12.830303993361 0.77775 + vertex 18.25 13.5 0.77775 + vertex 18.25 13.5 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 60 17 0.77775 + vertex 60 17 0 + vertex 54.002309837942 17 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 15.455907969142 17 0.77775 + vertex 12.002309837942 17 0 + vertex 12.002309837942 17 0.77775 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 43.169696006639 11.883210818105 0 + vertex 43.737436867076 12.262563132924 0.77775 + vertex 43.737436867076 12.262563132924 0 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 43.169696006639 11.883210818105 0 + vertex 43.169696006639 11.883210818105 0.77775 + vertex 43.737436867076 12.262563132924 0.77775 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 -0 + outer loop + vertex 18.25 13.5 0.77775 + vertex 18.116789181895 14.169696006639 0 + vertex 18.25 13.5 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 42.5 28.25 0 + vertex 41.830303993361 28.116789181895 0 + vertex 17.169696006639 28.116789181895 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 23 0.77775 + vertex 48.011309837942 23 0 + vertex 48.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.169696006639 15.116789181895 0.77775 + vertex 15.455907969142 17 0.77775 + vertex 16.5 15.25 0.77775 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 17.737436867076 12.262563132924 0.77775 + vertex 17.737436867076 12.262563132924 0 + vertex 17.169696006639 11.883210818105 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 52 40 0.77775 + vertex 60 23 0.77775 + vertex 60 40 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 40.75 13.5 0 + vertex 18.116789181895 12.830303993361 0 + vertex 18.25 13.5 0 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 43.169696006639 28.116789181895 0 + vertex 42.5 28.25 0.77775 + vertex 42.5 28.25 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 23 0 + vertex 12.002309837942 23 0 + vertex 42.5 24.75 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 23 0.77775 + vertex 60 23 0.77775 + vertex 54.002309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 23 0.77775 + vertex 6.011309837942 24.730643081307 0 + vertex 6.011309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 17.169696006639 15.116789181895 0 + vertex 16.5 15.25 0.77775 + vertex 16.5 15.25 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 17 0.77775 + vertex 6.011309837942 15.063011877768 0.77775 + vertex 6.011309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 60 17 0.77775 + vertex 60 0 0 + vertex 60 17 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 60 0 0.77775 + vertex 60 0 0 + vertex 60 17 0.77775 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 15.262563132924 12.262563132924 0.77775 + vertex 15.830303993361 11.883210818105 0.77775 + vertex 15.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 52 10 0.77775 + vertex 44.116789181895 12.830303993361 0.77775 + vertex 43.737436867076 12.262563132924 0.77775 + endloop +endfacet +facet normal 0 0.980785280403 0.195090322015 + outer loop + vertex 6.011309837942 15.063011877768 0.77775 + vertex 12.002309837942 15.063011877768 0.77775 + vertex 12.002309837942 15.195826430941 0.110046151702 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.169696006639 24.883210818105 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 17.737436867076 25.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 52 10 0.77775 + vertex 43.737436867076 12.262563132924 0.77775 + vertex 43.169696006639 11.883210818105 0.77775 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 42.5 28.25 0.77775 + vertex 41.830303993361 28.116789181895 0 + vertex 42.5 28.25 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 6.011309837942 15.269356918694 0 + vertex 6.011309837942 17 0 + vertex 6.011309837942 17 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 40.883210818105 25.830303993361 0.77775 + vertex 37.474084379442 23 0.77775 + vertex 41.262563132924 25.262563132924 0.77775 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 18.116789181895 12.830303993361 0.77775 + vertex 18.25 13.5 0 + vertex 18.116789181895 12.830303993361 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 25.262563132924 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 18.116789181895 25.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 34.416922535211 17 0.77775 + vertex 41.262563132924 14.737436867076 0.77775 + vertex 41.830303993361 15.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 0 30 0.77775 + vertex 6.011309837942 24.936988122232 0.77775 + vertex 12.002309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 25.830303993361 0.77775 + vertex 17.379510917074 23 0.77775 + vertex 37.474084379442 23 0.77775 + endloop +endfacet +facet normal 0 -0.980785280403 0.195090322015 + outer loop + vertex 6.011309837942 24.936988122232 0.77775 + vertex 12.002309837942 24.804173569059 0.110046151702 + vertex 12.002309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 52 10 0.77775 + vertex 43.169696006639 11.883210818105 0.77775 + vertex 42.5 11.75 0.77775 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 41.262563132924 14.737436867076 0.77775 + vertex 40.883210818105 14.169696006639 0 + vertex 41.262563132924 14.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 23 0 + vertex 43.169696006639 24.883210818105 0 + vertex 43.737436867076 25.262563132924 0 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 44.25 26.5 0.77775 + vertex 44.116789181895 25.830303993361 0 + vertex 44.116789181895 25.830303993361 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 60 17 0.77775 + vertex 54.002309837942 17 0 + vertex 54.002309837942 17 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 48.011309837942 23 0 + vertex 42.5 24.75 0 + vertex 43.169696006639 24.883210818105 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 43.737436867076 27.737436867076 0.77775 + vertex 44.116789181895 27.169696006639 0.77775 + vertex 52 30 0.77775 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 18.116789181895 25.830303993361 0.77775 + vertex 18.116789181895 25.830303993361 0 + vertex 17.737436867076 25.262563132924 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 15.195826430941 0.110046151702 + vertex 54.002309837942 17 0.77775 + vertex 54.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 14.883210818105 27.169696006639 0.77775 + vertex 14.75 26.5 0 + vertex 14.883210818105 27.169696006639 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 52 0 0.77775 + vertex 52 0 0 + vertex 60 0 0.77775 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 14.883210818105 27.169696006639 0.77775 + vertex 14.75 26.5 0.77775 + vertex 14.75 26.5 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 15.269356918694 0 + vertex 54.002309837942 17 0.77775 + vertex 54.002309837942 17 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 16.5 11.75 0.77775 + vertex 15.306548743796 10 0.77775 + vertex 17.169696006639 11.883210818105 0.77775 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 17.169696006639 15.116789181895 0 + vertex 17.169696006639 15.116789181895 0.77775 + vertex 16.5 15.25 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 12.002309837942 15.195826430941 0.110046151702 + vertex 12.002309837942 15.063011877768 0.77775 + vertex 12.002309837942 17 0.77775 + endloop +endfacet +facet normal 0 0.980785280403 0.195090322015 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 48.011309837942 15.195826430941 0.110046151702 + vertex 48.011309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 16.5 11.75 0.77775 + vertex 16.5 11.75 0 + vertex 15.830303993361 11.883210818105 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 48.011309837942 15.063011877768 0.77775 + vertex 48.011309837942 15.269356918694 0 + vertex 48.011309837942 17 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 24.936988122232 0.77775 + vertex 52 30 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal -0.195090322016 0.980785280403 0 + outer loop + vertex 42.5 24.75 0.77775 + vertex 43.169696006639 24.883210818105 0 + vertex 42.5 24.75 0 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 14.75 26.5 0 + vertex 14.75 26.5 0.77775 + vertex 14.883210818105 25.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 14.169696006639 0 + vertex 43.737436867076 14.737436867076 0 + vertex 48.011309837942 17 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 44.116789181895 14.169696006639 0 + vertex 48.011309837942 17 0 + vertex 48.011309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.169696006639 24.883210818105 0 + vertex 41.830303993361 24.883210818105 0 + vertex 42.5 24.75 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 17.169696006639 24.883210818105 0 + vertex 42.5 24.75 0 + vertex 12.002309837942 23 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 52 40 0 + vertex 52 40 0.77775 + vertex 60 40 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 52 40 0 + vertex 60 40 0.77775 + vertex 60 40 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 0 + outer loop + vertex 18.116789181895 27.169696006639 0.77775 + vertex 17.737436867076 27.737436867076 0.77775 + vertex 17.737436867076 27.737436867076 0 + endloop +endfacet +facet normal -0.831469612302 -0.555570233021 -0 + outer loop + vertex 18.116789181895 27.169696006639 0.77775 + vertex 17.737436867076 27.737436867076 0 + vertex 18.116789181895 27.169696006639 0 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 15.830303993361 24.883210818105 0 + vertex 15.830303993361 24.883210818105 0.77775 + vertex 16.5 24.75 0.77775 + endloop +endfacet +facet normal 0.195090322016 0.980785280403 0 + outer loop + vertex 15.830303993361 24.883210818105 0 + vertex 16.5 24.75 0.77775 + vertex 16.5 24.75 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 12.002309837942 24.936988122232 0.77775 + vertex 12.002309837942 23 0.77775 + vertex 15.262563132924 25.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 12.002309837942 24.936988122232 0.77775 + vertex 15.262563132924 25.262563132924 0.77775 + vertex 14.883210818105 25.830303993361 0.77775 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 60 40 0.77775 + vertex 60 23 0.77775 + vertex 60 23 0 + endloop +endfacet +facet normal 1 0 0 + outer loop + vertex 60 40 0.77775 + vertex 60 23 0 + vertex 60 40 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 15.262563132924 27.737436867076 0 + vertex 15.262563132924 27.737436867076 0.77775 + vertex 14.883210818105 27.169696006639 0.77775 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 15.262563132924 27.737436867076 0 + vertex 14.883210818105 27.169696006639 0.77775 + vertex 14.883210818105 27.169696006639 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 37.474084379442 23 0.77775 + vertex 48.011309837942 23 0.77775 + vertex 42.5 24.75 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 37.474084379442 23 0.77775 + vertex 42.5 24.75 0.77775 + vertex 41.830303993361 24.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.262563132924 14.737436867076 0 + vertex 12.002309837942 17 0 + vertex 41.422110817733 17 0 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 0 + outer loop + vertex 43.169696006639 15.116789181895 0.77775 + vertex 42.5 15.25 0.77775 + vertex 42.5 15.25 0 + endloop +endfacet +facet normal -0.195090322016 -0.980785280403 -0 + outer loop + vertex 43.169696006639 15.116789181895 0.77775 + vertex 42.5 15.25 0 + vertex 43.169696006639 15.116789181895 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 52 10 0.77775 + vertex 60 0 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 15.063011877768 0.77775 + vertex 60 0 0.77775 + vertex 60 17 0.77775 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 40.75 13.5 0.77775 + vertex 40.883210818105 12.830303993361 0.77775 + vertex 40.883210818105 12.830303993361 0 + endloop +endfacet +facet normal 0.980785280403 0.195090322016 0 + outer loop + vertex 40.75 13.5 0.77775 + vertex 40.883210818105 12.830303993361 0 + vertex 40.75 13.5 0 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 0 + outer loop + vertex 17.737436867076 27.737436867076 0 + vertex 17.737436867076 27.737436867076 0.77775 + vertex 17.169696006639 28.116789181895 0.77775 + endloop +endfacet +facet normal -0.555570233021 -0.831469612302 0 + outer loop + vertex 17.737436867076 27.737436867076 0 + vertex 17.169696006639 28.116789181895 0.77775 + vertex 17.169696006639 28.116789181895 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.883210818105 27.169696006639 0.77775 + vertex 0 30 0.77775 + vertex 12.002309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.883210818105 27.169696006639 0.77775 + vertex 12.002309837942 24.936988122232 0.77775 + vertex 14.75 26.5 0.77775 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 43.737436867076 12.262563132924 0.77775 + vertex 44.116789181895 12.830303993361 0.77775 + vertex 44.116789181895 12.830303993361 0 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 43.737436867076 12.262563132924 0.77775 + vertex 44.116789181895 12.830303993361 0 + vertex 43.737436867076 12.262563132924 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 48.011309837942 23 0 + vertex 48.011309837942 23 0.77775 + vertex 37.474084379442 23 0.77775 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 16.5 15.25 0.77775 + vertex 15.830303993361 15.116789181895 0.77775 + vertex 15.830303993361 15.116789181895 0 + endloop +endfacet +facet normal 0.195090322016 -0.980785280403 0 + outer loop + vertex 16.5 15.25 0.77775 + vertex 15.830303993361 15.116789181895 0 + vertex 16.5 15.25 0 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 40.883210818105 12.830303993361 0.77775 + vertex 41.262563132924 12.262563132924 0.77775 + vertex 41.262563132924 12.262563132924 0 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 40.883210818105 12.830303993361 0.77775 + vertex 41.262563132924 12.262563132924 0 + vertex 40.883210818105 12.830303993361 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 17 0 + vertex 16.5 15.25 0 + vertex 15.830303993361 15.116789181895 0 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 43.737436867076 25.262563132924 0 + vertex 43.737436867076 25.262563132924 0.77775 + vertex 44.116789181895 25.830303993361 0.77775 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 43.737436867076 25.262563132924 0 + vertex 44.116789181895 25.830303993361 0.77775 + vertex 44.116789181895 25.830303993361 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 40.883210818105 14.169696006639 0.77775 + vertex 40.75 13.5 0.77775 + vertex 40.75 13.5 0 + endloop +endfacet +facet normal 0.980785280403 -0.195090322016 0 + outer loop + vertex 40.883210818105 14.169696006639 0.77775 + vertex 40.75 13.5 0 + vertex 40.883210818105 14.169696006639 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 60 23 0 + vertex 60 23 0.77775 + vertex 54.002309837942 23 0.77775 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 60 23 0 + vertex 54.002309837942 23 0.77775 + vertex 54.002309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 0 30 0 + vertex 14.042553191489 30 0 + vertex 14.883210818105 27.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 0 30 0 + vertex 14.883210818105 27.169696006639 0 + vertex 6.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 41.262563132924 25.262563132924 0 + vertex 41.262563132924 25.262563132924 0.77775 + vertex 41.830303993361 24.883210818105 0.77775 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 41.262563132924 25.262563132924 0 + vertex 41.830303993361 24.883210818105 0.77775 + vertex 41.830303993361 24.883210818105 0 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 41.830303993361 28.116789181895 0.77775 + vertex 41.262563132924 27.737436867076 0.77775 + vertex 41.262563132924 27.737436867076 0 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 41.830303993361 28.116789181895 0.77775 + vertex 41.262563132924 27.737436867076 0 + vertex 41.830303993361 28.116789181895 0 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 43.169696006639 24.883210818105 0.77775 + vertex 43.737436867076 25.262563132924 0.77775 + vertex 43.737436867076 25.262563132924 0 + endloop +endfacet +facet normal -0.555570233021 0.831469612302 0 + outer loop + vertex 43.169696006639 24.883210818105 0.77775 + vertex 43.737436867076 25.262563132924 0 + vertex 43.169696006639 24.883210818105 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 10 0 + vertex 43.169696006639 11.883210818105 0 + vertex 43.737436867076 12.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 52 10 0 + vertex 43.737436867076 12.262563132924 0 + vertex 54.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 24.936988122232 0.77775 + vertex 60 23 0.77775 + vertex 52 40 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 54.002309837942 24.936988122232 0.77775 + vertex 52 40 0.77775 + vertex 52 30 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 15.455907969142 17 0.77775 + vertex 34.416922535211 17 0.77775 + vertex 41.422110817733 17 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 15.455907969142 17 0.77775 + vertex 41.422110817733 17 0 + vertex 12.002309837942 17 0 + endloop +endfacet +facet normal 0 -0.831469612304 0.555570233017 + outer loop + vertex 12.002309837942 24.804173569059 0.110046151702 + vertex 6.011309837942 24.804173569059 0.110046151702 + vertex 6.011309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 -0.831469612304 0.555570233017 + outer loop + vertex 12.002309837942 24.804173569059 0.110046151702 + vertex 6.011309837942 24.730643081307 0 + vertex 12.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.262563132924 14.737436867076 0.77775 + vertex 15.455907969142 17 0.77775 + vertex 12.002309837942 17 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 15.262563132924 14.737436867076 0.77775 + vertex 12.002309837942 17 0.77775 + vertex 14.883210818105 14.169696006639 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 44.116789181895 25.830303993361 0.77775 + vertex 43.737436867076 25.262563132924 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 0 17 0.77775 + vertex 0 10 0.77775 + vertex 6.011309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 0 17 0.77775 + vertex 6.011309837942 15.063011877768 0.77775 + vertex 6.011309837942 17 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 17 0.77775 + vertex 34.416922535211 17 0.77775 + vertex 42.5 15.25 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 17 0.77775 + vertex 42.5 15.25 0.77775 + vertex 43.169696006639 15.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.883210818105 14.169696006639 0.77775 + vertex 12.002309837942 17 0.77775 + vertex 12.002309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 14.883210818105 14.169696006639 0.77775 + vertex 12.002309837942 15.063011877768 0.77775 + vertex 14.75 13.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 6.011309837942 24.936988122232 0.77775 + vertex 0 30 0.77775 + vertex 0 23 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 6.011309837942 24.936988122232 0.77775 + vertex 0 23 0.77775 + vertex 6.011309837942 23 0.77775 + endloop +endfacet +facet normal -0 -0 -1 + outer loop + vertex 40.75 26.5 0 + vertex 40.883210818105 25.830303993361 0 + vertex 18.116789181895 27.169696006639 0 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 0 + outer loop + vertex 44.25 13.5 0.77775 + vertex 44.116789181895 14.169696006639 0.77775 + vertex 44.116789181895 14.169696006639 0 + endloop +endfacet +facet normal -0.980785280403 -0.195090322016 -0 + outer loop + vertex 44.25 13.5 0.77775 + vertex 44.116789181895 14.169696006639 0 + vertex 44.25 13.5 0 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 17.737436867076 12.262563132924 0 + vertex 17.737436867076 12.262563132924 0.77775 + vertex 18.116789181895 12.830303993361 0.77775 + endloop +endfacet +facet normal -0.831469612302 0.555570233021 0 + outer loop + vertex 17.737436867076 12.262563132924 0 + vertex 18.116789181895 12.830303993361 0.77775 + vertex 18.116789181895 12.830303993361 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 0 10 0.77775 + vertex 15.306548743796 10 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + endloop +endfacet +facet normal 0 -0.980785280403 0.195090322015 + outer loop + vertex 54.002309837942 24.804173569059 0.110046151702 + vertex 54.002309837942 24.936988122232 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 -0.980785280403 0.195090322015 + outer loop + vertex 54.002309837942 24.804173569059 0.110046151702 + vertex 48.011309837942 24.936988122232 0.77775 + vertex 48.011309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 14.883210818105 12.830303993361 0 + vertex 14.883210818105 12.830303993361 0.77775 + vertex 15.262563132924 12.262563132924 0.77775 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 14.883210818105 12.830303993361 0 + vertex 15.262563132924 12.262563132924 0.77775 + vertex 15.262563132924 12.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 54.002309837942 15.269356918694 0 + vertex 43.737436867076 12.262563132924 0 + vertex 44.116789181895 12.830303993361 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 54.002309837942 15.269356918694 0 + vertex 44.116789181895 12.830303993361 0 + vertex 48.011309837942 15.269356918694 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 0 30 0 + vertex 0 30 0.77775 + vertex 14.607403236621 30 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 0 30 0 + vertex 14.607403236621 30 0.77775 + vertex 14.042553191489 30 0 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 41.262563132924 12.262563132924 0 + vertex 41.262563132924 12.262563132924 0.77775 + vertex 41.830303993361 11.883210818105 0.77775 + endloop +endfacet +facet normal 0.555570233021 0.831469612302 0 + outer loop + vertex 41.262563132924 12.262563132924 0 + vertex 41.830303993361 11.883210818105 0.77775 + vertex 41.830303993361 11.883210818105 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 0 10 0 + vertex 0 10 0.77775 + vertex 0 17 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 0 10 0 + vertex 0 17 0.77775 + vertex 0 17 0 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 44.116789181895 12.830303993361 0.77775 + vertex 44.25 13.5 0.77775 + vertex 44.25 13.5 0 + endloop +endfacet +facet normal -0.980785280403 0.195090322016 0 + outer loop + vertex 44.116789181895 12.830303993361 0.77775 + vertex 44.25 13.5 0 + vertex 44.116789181895 12.830303993361 0 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 41.422110817733 17 0 + vertex 34.416922535211 17 0.77775 + vertex 48.011309837942 17 0.77775 + endloop +endfacet +facet normal 0 1 0 + outer loop + vertex 41.422110817733 17 0 + vertex 48.011309837942 17 0.77775 + vertex 48.011309837942 17 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 52 30 0.77775 + vertex 52 40 0.77775 + vertex 52 40 0 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 52 30 0.77775 + vertex 52 40 0 + vertex 52 30 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 15.830303993361 24.883210818105 0 + vertex 16.5 24.75 0 + vertex 12.002309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.262563132924 27.737436867076 0 + vertex 40.883210818105 27.169696006639 0 + vertex 17.737436867076 27.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 41.262563132924 27.737436867076 0 + vertex 17.737436867076 27.737436867076 0 + vertex 17.169696006639 28.116789181895 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 6.011309837942 15.063011877768 0.77775 + vertex 0 10 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 6.011309837942 15.063011877768 0.77775 + vertex 14.883210818105 12.830303993361 0.77775 + vertex 12.002309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 17 0.77775 + vertex 43.737436867076 14.737436867076 0.77775 + vertex 44.116789181895 14.169696006639 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 17 0.77775 + vertex 44.116789181895 14.169696006639 0.77775 + vertex 48.011309837942 15.063011877768 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 15.262563132924 25.262563132924 0 + vertex 15.830303993361 24.883210818105 0 + vertex 12.002309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 15.262563132924 25.262563132924 0 + vertex 12.002309837942 23 0 + vertex 12.002309837942 24.730643081307 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 44.25 26.5 0.77775 + vertex 44.116789181895 25.830303993361 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 40.883210818105 27.169696006639 0 + vertex 40.75 26.5 0 + vertex 18.116789181895 27.169696006639 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 40.883210818105 27.169696006639 0 + vertex 18.116789181895 27.169696006639 0 + vertex 17.737436867076 27.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 17 0 + vertex 41.262563132924 14.737436867076 0 + vertex 17.169696006639 15.116789181895 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 17 0 + vertex 17.169696006639 15.116789181895 0 + vertex 16.5 15.25 0 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 15.830303993361 15.116789181895 0 + vertex 15.830303993361 15.116789181895 0.77775 + vertex 15.262563132924 14.737436867076 0.77775 + endloop +endfacet +facet normal 0.555570233021 -0.831469612302 0 + outer loop + vertex 15.830303993361 15.116789181895 0 + vertex 15.262563132924 14.737436867076 0.77775 + vertex 15.262563132924 14.737436867076 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 41.262563132924 27.737436867076 0.77775 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 40.883210818105 27.169696006639 0 + endloop +endfacet +facet normal 0.831469612302 -0.555570233021 0 + outer loop + vertex 41.262563132924 27.737436867076 0.77775 + vertex 40.883210818105 27.169696006639 0 + vertex 41.262563132924 27.737436867076 0 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 48.011309837942 23 0 + vertex 37.474084379442 23 0.77775 + vertex 17.379510917074 23 0.77775 + endloop +endfacet +facet normal 0 -1 0 + outer loop + vertex 48.011309837942 23 0 + vertex 17.379510917074 23 0.77775 + vertex 12.002309837942 23 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 17 0 + vertex 15.830303993361 15.116789181895 0 + vertex 15.262563132924 14.737436867076 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 12.002309837942 17 0 + vertex 15.262563132924 14.737436867076 0 + vertex 12.002309837942 15.269356918694 0 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 40.883210818105 25.830303993361 0 + vertex 40.883210818105 25.830303993361 0.77775 + vertex 41.262563132924 25.262563132924 0.77775 + endloop +endfacet +facet normal 0.831469612302 0.555570233021 0 + outer loop + vertex 40.883210818105 25.830303993361 0 + vertex 41.262563132924 25.262563132924 0.77775 + vertex 41.262563132924 25.262563132924 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.883210818105 12.830303993361 0 + vertex 15.262563132924 12.262563132924 0 + vertex 14.464705848856 10 0 + endloop +endfacet +facet normal 0 0 -1 + outer loop + vertex 14.883210818105 12.830303993361 0 + vertex 14.464705848856 10 0 + vertex 0 10 0 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 27.737436867076 0.77775 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 41.262563132924 27.737436867076 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.262563132924 27.737436867076 0.77775 + vertex 17.169696006639 28.116789181895 0.77775 + vertex 17.737436867076 27.737436867076 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 23 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + vertex 43.737436867076 25.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 23 0.77775 + vertex 43.737436867076 25.262563132924 0.77775 + vertex 43.169696006639 24.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 15.063011877768 0.77775 + vertex 44.116789181895 14.169696006639 0.77775 + vertex 44.25 13.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 44.116789181895 27.169696006639 0.77775 + vertex 44.25 26.5 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 44.116789181895 27.169696006639 0.77775 + vertex 48.011309837942 24.936988122232 0.77775 + vertex 52 30 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 27.169696006639 0.77775 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 17.737436867076 27.737436867076 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 23 0.77775 + vertex 54.002309837942 24.936988122232 0.77775 + vertex 54.002309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.262563132924 27.737436867076 0.77775 + vertex 41.866228156844 30 0.77775 + vertex 14.607403236621 30 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.262563132924 27.737436867076 0.77775 + vertex 14.607403236621 30 0.77775 + vertex 17.169696006639 28.116789181895 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 15.063011877768 0.77775 + vertex 44.25 13.5 0.77775 + vertex 44.116789181895 12.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 48.011309837942 15.063011877768 0.77775 + vertex 44.116789181895 12.830303993361 0.77775 + vertex 52 10 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 14.169696006639 0.77775 + vertex 18.25 13.5 0.77775 + vertex 41.262563132924 12.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.25 26.5 0.77775 + vertex 40.883210818105 27.169696006639 0.77775 + vertex 18.116789181895 27.169696006639 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 24.730643081307 0 + vertex 54.002309837942 23 0 + vertex 54.002309837942 23 0.77775 + endloop +endfacet +facet normal -1 0 0 + outer loop + vertex 54.002309837942 24.730643081307 0 + vertex 54.002309837942 23 0.77775 + vertex 54.002309837942 24.804173569059 0.110046151702 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.830303993361 11.883210818105 0.77775 + vertex 41.262563132924 12.262563132924 0.77775 + vertex 18.25 13.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 41.830303993361 11.883210818105 0.77775 + vertex 18.25 13.5 0.77775 + vertex 18.116789181895 12.830303993361 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 12.830303993361 0.77775 + vertex 17.737436867076 12.262563132924 0.77775 + vertex 42.5 11.75 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 12.830303993361 0.77775 + vertex 42.5 11.75 0.77775 + vertex 41.830303993361 11.883210818105 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 12.262563132924 0.77775 + vertex 15.306548743796 10 0.77775 + vertex 52 10 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 17.737436867076 12.262563132924 0.77775 + vertex 52 10 0.77775 + vertex 42.5 11.75 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 40.883210818105 12.830303993361 0.77775 + vertex 18.116789181895 14.169696006639 0.77775 + vertex 41.262563132924 12.262563132924 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.25 26.5 0.77775 + vertex 40.75 26.5 0.77775 + vertex 40.883210818105 27.169696006639 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 25.830303993361 0.77775 + vertex 37.474084379442 23 0.77775 + vertex 40.75 26.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 18.116789181895 25.830303993361 0.77775 + vertex 40.75 26.5 0.77775 + vertex 18.25 26.5 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 40.883210818105 12.830303993361 0.77775 + vertex 34.416922535211 17 0.77775 + vertex 17.737436867076 14.737436867076 0.77775 + endloop +endfacet +facet normal 0 0 1 + outer loop + vertex 40.883210818105 12.830303993361 0.77775 + vertex 17.737436867076 14.737436867076 0.77775 + vertex 18.116789181895 14.169696006639 0.77775 + endloop +endfacet +endsolid diff --git a/SkeinPyPy_NewUI/models/Screw Holder.gts b/SkeinPyPy_NewUI/models/Screw Holder.gts new file mode 100644 index 0000000..6399a12 --- /dev/null +++ b/SkeinPyPy_NewUI/models/Screw Holder.gts @@ -0,0 +1,4493 @@ +737 2253 1502 Number of Vertices,Number of Edges,Number of Faces +-6.859863212247494 0.22628773741583247 9.642249999999999 Vertex Coordinates XYZ +-6.649863212247495 0.946287737415833 9.642249999999999 +-6.319863212247495 1.396287737415833 9.642249999999999 +-5.809863212247494 1.726287737415833 9.642249999999999 +-5.2998632122474945 1.8462877374158326 9.642249999999999 +-4.519863212247495 1.8462877374158326 9.642249999999999 +-4.009863212247495 1.636287737415833 9.642249999999999 +-3.6798632122474944 1.2762877374158328 9.642249999999999 +-3.4698632122474935 0.7962877374158329 9.642249999999999 +-3.3498632122474934 0.016287737415832892 9.642249999999999 +-3.3498632122474934 -0.37371226258416745 9.642249999999999 +-5.689863212247494 -0.4037122625841675 9.642249999999999 +-5.509863212247495 -0.9437122625841673 9.642249999999999 +-5.179863212247494 -1.2437122625841672 9.642249999999999 +-4.699863212247494 -1.3937122625841676 9.642249999999999 +-3.949863212247494 -1.3637122625841673 9.642249999999999 +-3.319863212247494 -1.1837122625841672 9.642249999999999 +-3.3498632122474934 -1.9637122625841674 9.642249999999999 +-4.279863212247495 -2.143712262584168 9.642249999999999 +-5.209863212247494 -2.143712262584168 9.642249999999999 +-5.929863212247494 -1.9037122625841678 9.642249999999999 +-6.409863212247495 -1.4837122625841674 9.642249999999999 +-6.679863212247494 -1.0337122625841673 9.642249999999999 +-6.829863212247495 -0.4337122625841675 9.642249999999999 +-5.4473632122474935 0.8687877374158329 9.642249999999999 +-4.457363212247493 0.8387877374158327 9.642249999999999 +-4.367363212247493 0.26878773741583245 9.642249999999999 +-5.627363212247492 0.3887877374158325 9.642249999999999 +-4.667363212247494 1.1087877374158324 9.642249999999999 +-5.627363212247492 0.2987877374158325 9.642249999999999 +-5.177363212247494 1.1087877374158324 9.642249999999999 +-4.009863212247495 1.636287737415833 11.322250000000002 +-4.519863212247496 1.8462877374158326 11.322250000000002 +-3.6798632122474944 1.276287737415834 11.32225 +-3.3498632122474934 -0.37371226258416734 11.32225 +-3.3498632122474934 0.016287737415832892 11.32225 +-5.689863212247495 -0.4037122625841676 11.32225 +-3.9498632122474935 -1.3637122625841673 11.32225 +-3.319863212247494 -1.1837122625841672 11.32225 +-3.3498632122474943 -1.9637122625841679 11.32225 +-6.859863212247496 0.2262877374158324 11.322249999999999 +-6.829863212247496 -0.4337122625841676 11.32225 +-6.319863212247493 1.396287737415832 11.322249999999999 +-5.809863212247494 1.726287737415833 11.32225 +-6.649863212247495 0.9462877374158329 11.32225 +-5.209863212247495 -2.143712262584168 11.32225 +-5.929863212247496 -1.9037122625841687 11.32225 +-5.179863212247494 -1.2437122625841672 11.32225 +-4.699863212247494 -1.3937122625841676 11.32225 +-4.279863212247495 -2.1437122625841676 11.32225 +-6.4098632122474966 -1.483712262584167 11.32225 +-6.679863212247495 -1.0337122625841677 11.32225 +-5.299863212247494 1.846287737415833 11.32225 +-3.4698632122474944 0.796287737415833 11.32225 +-5.509863212247496 -0.9437122625841675 11.32225 +-4.667363212247494 1.1087877374158326 11.32225 +-5.177363212247494 1.1087877374158317 11.32225 +-4.457363212247494 0.8387877374158329 11.32225 +-5.627363212247493 0.38878773741583256 11.32225 +-4.367363212247493 0.2687877374158326 11.32225 +-5.627363212247492 0.2987877374158324 11.32225 +-5.4473632122474935 0.8687877374158326 11.32225 +11.759863212247497 1.8856484886916096 9.64225 +12.899863212247496 1.8556484886916098 9.64225 +12.929863212247495 1.22564848869161 9.64225 +13.439863212247495 1.6756484886916105 9.64225 +13.889863212247496 1.8556484886916098 9.64225 +14.519863212247497 1.8556484886916098 9.64225 +14.999863212247497 1.5856484886916102 9.64225 +15.359863212247497 1.0756484886916096 9.64225 +15.539863212247496 0.3556484886916095 9.64225 +15.539863212247496 -0.60435151130839 9.64225 +15.329863212247497 -1.23435151130839 9.64225 +14.999863212247497 -1.6843515113083907 9.64225 +14.609863212247497 -1.9543515113083907 9.64225 +14.039863212247496 -2.1343515113083904 9.64225 +12.899863212247496 -2.194351511308391 9.64225 +12.869863212247497 -3.5143515113083907 9.64225 +11.759863212247497 -3.484351511308391 9.64225 +12.899863212247496 -1.3843515113083904 9.64225 +13.079863212247497 -1.4443515113083905 9.64225 +14.279863212247497 -0.9043515113083903 9.64225 +12.899863212247496 0.4456484886916098 9.64225 +13.379863212247496 0.8956484886916104 9.64225 +13.769863212247497 1.0156484886916095 9.64225 +14.369863212247497 -0.21435151130839014 9.64225 +14.279863212247497 0.5056484886916098 9.64225 +14.009863212247497 -1.2943515113083905 9.64225 +14.099863212247497 0.8656484886916099 9.64225 +13.649863212247496 -1.4443515113083905 9.64225 +11.759863212247497 1.8856484886916096 11.32225 +12.899863212247498 1.8556484886916098 11.32225 +12.929863212247488 1.2256484886916095 11.32225 +13.439863212247495 1.6756484886916105 11.32225 +13.889863212247494 1.8556484886916107 11.32225 +14.519863212247497 1.8556484886916098 11.32225 +14.999863212247497 1.5856484886916107 11.32225 +15.359863212247493 1.0756484886916098 11.32225 +15.5398632122475 0.35564848869160914 11.32225 +15.539863212247498 -0.6043515113083899 11.32225 +15.329863212247494 -1.234351511308391 11.32225 +14.999863212247503 -1.684351511308392 11.32225 +14.609863212247497 -1.9543515113083898 11.32225 +14.039863212247498 -2.1343515113083895 11.32225 +12.899863212247496 -2.194351511308391 11.32225 +12.869863212247498 -3.514351511308391 11.32225 +11.759863212247494 -3.4843515113083914 11.32225 +14.279863212247493 -0.9043515113083888 11.322250000000002 +14.009863212247494 -1.2943515113083883 11.322250000000002 +13.379863212247498 0.8956484886916118 11.32225 +13.769863212247497 1.0156484886916093 11.322249999999997 +12.899863212247496 0.4456484886916098 11.32225 +12.899863212247496 -1.38435151130839 11.32225 +14.36986321224749 -0.2143515113083907 11.32225 +14.279863212247497 0.5056484886916095 11.32225 +14.099863212247497 0.8656484886916107 11.32225 +13.649863212247496 -1.4443515113083905 11.32225 +13.0798632122475 -1.4443515113083896 11.32225 +2.270136787752506 3.2862877374158317 9.64225 +5.210136787752507 3.256287737415831 9.64225 +5.7801367877525065 3.0462877374158315 9.64225 +6.110136787752507 2.716287737415832 9.64225 +6.260136787752507 2.356287737415831 9.64225 +6.320136787752506 1.6662877374158316 9.64225 +6.140136787752507 1.0962877374158313 9.64225 +5.600136787752507 0.5562877374158314 9.64225 +5.270136787752507 0.3462877374158312 9.64225 +7.040136787752506 -2.1737122625841687 9.64225 +5.720136787752507 -2.143712262584169 9.64225 +4.220136787752507 0.04628773741583159 9.64225 +3.380136787752506 0.01628773741583156 9.64225 +3.350136787752507 -2.1737122625841687 9.64225 +2.270136787752506 -2.143712262584169 9.64225 +3.3801367877525057 2.5062877374158314 9.64225 +3.3801367877525057 0.8562877374158319 9.64225 +5.120136787752506 1.9962877374158312 9.64225 +4.520136787752507 0.9162877374158317 9.64225 +5.120136787752506 1.4562877374158316 9.64225 +3.920136787752506 0.8262877374158316 9.64225 +4.580136787752507 2.476287737415832 9.64225 +4.940136787752506 2.296287737415832 9.64225 +4.880136787752506 1.0962877374158313 9.64225 +2.2701367877525054 3.286287737415832 11.32225 +5.210136787752507 3.256287737415833 11.322250000000004 +5.780136787752506 3.046287737415832 11.32225 +6.1101367877525075 2.7162877374158323 11.32225 +6.260136787752506 2.356287737415831 11.32225 +6.320136787752505 1.6662877374158316 11.32225 +6.140136787752509 1.0962877374158313 11.32225 +5.600136787752508 0.5562877374158319 11.32225 +5.270136787752508 0.3462877374158302 11.32225 +7.040136787752505 -2.1737122625841687 11.32225 +5.7201367877525096 -2.143712262584169 11.32225 +4.220136787752508 0.046287737415831476 11.32225 +3.3801367877525066 0.01628773741583056 11.32225 +3.3501367877525063 -2.1737122625841683 11.32225 +2.2701367877525063 -2.143712262584169 11.322250000000002 +3.9201367877525066 0.8262877374158334 11.32225 +4.520136787752508 0.9162877374158318 11.32225 +4.880136787752507 1.096287737415831 11.32225 +5.120136787752507 1.9962877374158317 11.32225 +4.940136787752505 2.296287737415833 11.32225 +5.120136787752507 1.4562877374158316 11.32225 +3.380136787752506 0.8562877374158319 11.32225 +4.580136787752508 2.476287737415833 11.32225 +3.3801367877525057 2.506287737415832 11.32225 +-2.4198632122474937 1.876287737415832 9.64225 +-1.279863212247494 1.8462877374158322 9.64225 +-1.2498632122474946 1.2162877374158323 9.64225 +-0.7398632122474949 1.666287737415833 9.64225 +-0.2898632122474938 1.8462877374158322 9.64225 +0.3401367877525061 1.8462877374158322 9.64225 +0.8201367877525065 1.5762877374158326 9.64225 +1.1801367877525069 1.066287737415832 9.64225 +1.3601367877525057 0.34628773741583185 9.64225 +1.3601367877525057 -0.6137122625841677 9.64225 +1.1501367877525075 -1.2437122625841677 9.64225 +0.8201367877525065 -1.6937122625841683 9.64225 +0.43013678775250686 -1.9637122625841683 9.64225 +-0.13986321224749437 -2.143712262584168 9.64225 +-1.279863212247494 -2.2037122625841685 9.64225 +-1.3098632122474934 -3.5237122625841684 9.64225 +-2.4198632122474937 -3.4937122625841686 9.64225 +-1.0998632122474925 -1.453712262584168 9.64225 +0.10013678775250578 -0.9137122625841679 9.64225 +-1.279863212247494 0.43628773741583216 9.64225 +-0.7998632122474936 0.8862877374158328 9.64225 +-0.40986321224749395 1.0062877374158319 9.64225 +-1.279863212247494 -1.393712262584168 9.64225 +0.19013678775250686 -0.22371226258416776 9.64225 +0.10013678775250612 0.4962877374158322 9.64225 +-0.16986321224749384 -1.3037122625841682 9.64225 +-0.07986321224749288 0.8562877374158323 9.64225 +-0.5298632122474941 -1.453712262584168 9.64225 +-2.4198632122474937 1.876287737415832 11.32225 +-1.2798632122474942 1.8462877374158313 11.32225 +-1.2498632122474944 1.2162877374158318 11.32225 +-0.7398632122474951 1.6662877374158342 11.322249999999999 +-0.28986321224749373 1.8462877374158322 11.32225 +0.340136787752506 1.8462877374158322 11.32225 +0.8201367877525063 1.5762877374158322 11.32225 +1.180136787752507 1.0662877374158315 11.32225 +1.3601367877525052 0.34628773741583185 11.32225 +1.3601367877525052 -0.6137122625841679 11.32225 +1.1501367877525073 -1.2437122625841677 11.32225 +0.8201367877525064 -1.6937122625841687 11.322250000000002 +0.43013678775250686 -1.9637122625841683 11.32225 +-0.13986321224749487 -2.143712262584167 11.32225 +-1.2798632122474942 -2.203712262584169 11.32225 +-1.3098632122474934 -3.5237122625841684 11.32225 +-2.4198632122474937 -3.493712262584169 11.32225 +0.10013678775250634 -0.9137122625841673 11.32225 +-0.16986321224749368 -1.3037122625841677 11.32225 +-0.40986321224749384 1.0062877374158323 11.32225 +-1.2798632122474942 0.43628773741583216 11.32225 +-1.2798632122474942 -1.393712262584168 11.32225 +0.19013678775250709 -0.22371226258416776 11.32225 +0.10013678775250609 0.4962877374158318 11.32225 +-0.07986321224749288 0.8562877374158324 11.32225 +-0.5298632122474942 -1.453712262584168 11.32225 +-1.0998632122474927 -1.453712262584168 11.32225 +-0.7998632122474936 0.8862877374158331 11.32225 +-11.929863212247497 3.2862877374158317 9.64225 +-8.989863212247496 3.256287737415831 9.64225 +-8.419863212247495 3.0462877374158315 9.64225 +-8.089863212247495 2.716287737415832 9.64225 +-7.939863212247495 2.356287737415831 9.64225 +-7.879863212247496 1.6662877374158316 9.64225 +-8.059863212247494 1.0962877374158313 9.64225 +-8.599863212247495 0.5562877374158314 9.64225 +-8.929863212247495 0.3462877374158312 9.64225 +-7.159863212247496 -2.1737122625841687 9.64225 +-8.479863212247496 -2.143712262584169 9.64225 +-9.979863212247496 0.04628773741583159 9.64225 +-10.819863212247496 0.01628773741583156 9.64225 +-10.849863212247495 -2.1737122625841687 9.64225 +-11.929863212247497 -2.143712262584169 9.64225 +-10.819863212247496 2.5062877374158314 9.64225 +-10.819863212247496 0.8562877374158319 9.64225 +-9.079863212247496 1.9962877374158312 9.64225 +-9.679863212247495 0.9162877374158317 9.64225 +-9.079863212247496 1.4562877374158316 9.64225 +-10.279863212247495 0.8262877374158316 9.64225 +-9.619863212247495 2.476287737415832 9.64225 +-9.259863212247495 2.296287737415832 9.64225 +-9.319863212247496 1.0962877374158313 9.64225 +-11.9298632122475 3.2862877374158317 11.32225 +-8.989863212247498 3.2562877374158314 11.32225 +-8.419863212247495 3.046287737415831 11.32225 +-8.089863212247495 2.7162877374158314 11.32225 +-7.939863212247496 2.3562877374158306 11.322250000000002 +-7.879863212247495 1.666287737415833 11.32225 +-8.059863212247494 1.096287737415831 11.32225 +-8.599863212247495 0.5562877374158306 11.32225 +-8.929863212247497 0.3462877374158324 11.32225 +-7.159863212247495 -2.1737122625841683 11.32225 +-8.479863212247496 -2.143712262584169 11.32225 +-9.979863212247498 0.04628773741582992 11.32225 +-10.819863212247498 0.016287737415831227 11.32225 +-10.849863212247497 -2.1737122625841687 11.32225 +-11.929863212247499 -2.1437122625841694 11.32225 +-9.679863212247495 0.9162877374158314 11.32225 +-9.319863212247498 1.096287737415831 11.322250000000002 +-9.079863212247496 1.4562877374158316 11.32225 +-9.079863212247496 1.996287737415832 11.322250000000002 +-9.259863212247495 2.2962877374158324 11.32225 +-10.819863212247496 0.8562877374158329 11.322250000000002 +-9.619863212247497 2.476287737415832 11.32225 +-10.279863212247498 0.8262877374158322 11.32225 +-10.819863212247494 2.5062877374158314 11.32225 +30.0 15.073000000000004 7.822250000000002 +30.0 15.206210818105248 8.491946006638905 +30.0 15.585563132923546 9.059686867076463 +30.0 16.153303993361096 9.43903918189475 +30.0 16.823 9.572249999999997 +30.0 17.49269600663891 9.43903918189475 +30.0 18.06043686707646 9.059686867076458 +30.0 18.439789181894753 8.49194600663891 +30.0 18.573000000000004 7.822250000000006 +30.0 18.439789181894753 7.152553993361098 +22.000000000000007 18.43978918189476 7.152553993361091 +30.0 18.06043686707646 6.5848131329235455 +30.0 17.492696006638916 6.205460818105249 +30.0 16.823 6.072249999999999 +30.0 16.153303993361092 6.205460818105249 +30.0 15.585563132923546 6.584813132923542 +30.0 15.206210818105244 7.152553993361091 +22.000000000000007 15.073000000000006 7.822250000000001 +22.000000000000007 15.206210818105252 8.491946006638905 +22.000000000000007 15.585563132923546 9.05968686707646 +22.000000000000007 16.823000000000008 9.572249999999997 +22.000000000000007 16.153303993361096 9.439039181894755 +22.000000000000014 17.492696006638912 9.439039181894753 +22.000000000000007 18.060436867076454 9.059686867076454 +22.000000000000007 18.439789181894753 8.491946006638907 +22.0 18.57300000000001 7.822250000000001 +22.000000000000007 18.060436867076465 6.584813132923542 +22.000000000000007 17.492696006638912 6.20546081810525 +22.000000000000007 16.823 6.0722499999999995 +22.000000000000007 16.1533039933611 6.205460818105244 +22.000000000000007 15.585563132923546 6.584813132923544 +22.000000000000007 15.206210818105255 7.152553993361091 +22.000000000000007 15.073000000000002 -0.17774999999999785 +22.000000000000007 15.585563132923546 1.0596868670764614 +22.000000000000007 15.206210818105255 0.49194600663890586 +22.000000000000007 16.153303993361096 1.439039181894751 +22.000000000000007 16.823000000000004 1.5722499999999995 +22.000000000000007 17.492696006638912 1.4390391818947483 +22.000000000000007 18.06043686707646 1.059686867076457 +22.000000000000014 18.573000000000004 -0.1777500000000014 +22.000000000000007 18.43978918189476 0.49194600663890853 +22.000000000000007 18.060436867076458 -1.4151868670764545 +22.000000000000007 18.43978918189476 -0.8474460066389078 +22.000000000000007 17.492696006638912 -1.7945391818947485 +22.000000000000007 16.153303993361092 -1.7945391818947503 +22.000000000000007 16.823000000000004 -1.9277499999999996 +22.000000000000007 15.585563132923546 -1.4151868670764598 +22.000000000000007 15.206210818105253 -0.8474460066389087 +30.0 15.072999999999999 -0.17774999999999785 +30.0 15.206210818105257 0.49194600663890586 +30.0 15.58556313292355 1.0596868670764596 +30.0 16.153303993361096 1.4390391818947554 +30.0 16.823000000000004 1.5722499999999995 +30.0 18.060436867076465 1.0596868670764579 +30.0 17.492696006638912 1.4390391818947492 +30.0 18.439789181894753 0.49194600663890586 +30.0 18.573 -0.1777500000000014 +30.0 18.43978918189476 -0.8474460066389105 +30.0 17.492696006638912 -1.7945391818947511 +30.0 18.06043686707646 -1.4151868670764562 +30.0 16.823000000000004 -1.9277499999999996 +30.0 16.153303993361096 -1.7945391818947494 +30.0 15.585563132923548 -1.4151868670764598 +30.0 15.20621081810525 -0.8474460066389096 +29.999999999999993 -17.927 -0.1777500000000014 +22.000000000000007 -17.793789181894752 0.491946006638905 +30.0 -17.414436867076454 1.0596868670764552 +30.0 -16.846696006638904 1.4390391818947492 +30.0 -16.176999999999996 1.5722499999999995 +30.0 -15.507303993361093 1.4390391818947545 +30.0 -14.939563132923539 1.059686867076457 +29.999999999999993 -14.560210818105249 0.4919460066389041 +29.999999999999993 -14.427 -0.17774999999999963 +30.0 -14.560210818105247 -0.8474460066389069 +30.0 -14.93956313292354 -1.4151868670764562 +30.0 -15.507303993361088 -1.794539181894752 +30.0 -16.177 -1.9277500000000023 +30.0 -16.846696006638904 -1.7945391818947556 +30.0 -17.414436867076454 -1.4151868670764527 +29.999999999999993 -17.793789181894756 -0.8474460066389087 +30.0 -17.793789181894756 0.4919460066389023 +22.000000000000007 -17.927000000000003 -0.17774999999999785 +22.000000000000007 -17.414436867076454 1.059686867076456 +22.000000000000007 -16.846696006638904 1.4390391818947519 +22.000000000000007 -16.176999999999996 1.5722499999999995 +22.000000000000007 -15.507303993361091 1.4390391818947519 +22.000000000000007 -14.939563132923539 1.0596868670764579 +22.000000000000007 -14.560210818105244 0.49194600663890764 +22.000000000000007 -14.427000000000001 -0.1777500000000023 +22.000000000000007 -14.560210818105245 -0.8474460066389105 +22.000000000000007 -14.939563132923539 -1.4151868670764625 +22.000000000000007 -15.507303993361091 -1.7945391818947538 +22.000000000000007 -16.177 -1.9277500000000014 +22.000000000000007 -16.846696006638904 -1.79453918189476 +22.000000000000007 -17.414436867076454 -1.415186867076459 +22.000000000000007 -17.79378918189475 -0.8474460066389069 +22.0 -17.92699999999999 7.822249999999998 +22.0 -17.793789181894745 8.491946006638901 +22.0 -17.414436867076454 9.059686867076454 +22.000000000000007 -16.8466960066389 9.43903918189475 +22.000000000000007 -16.177 9.572249999999999 +22.000000000000007 -15.507303993361091 9.439039181894753 +22.000000000000007 -14.939563132923539 9.059686867076458 +22.000000000000007 -14.560210818105242 8.491946006638909 +22.0 -14.426999999999989 7.822249999999997 +22.0 -14.560210818105247 7.152553993361094 +22.000000000000007 -14.939563132923539 6.584813132923543 +22.000000000000007 -15.50730399336109 6.205460818105245 +22.000000000000007 -16.177 6.072249999999999 +22.000000000000007 -16.846696006638904 6.205460818105244 +22.000000000000007 -17.414436867076454 6.584813132923531 +22.0 -17.793789181894745 7.15255399336109 +30.0 -17.92699999999999 7.822249999999999 +29.999999999999993 -17.79378918189475 8.491946006638901 +30.0 -16.8466960066389 9.439039181894747 +29.999999999999993 -17.41443686707645 9.059686867076447 +30.0 -16.177000000000003 9.572249999999999 +30.0 -15.507303993361093 9.439039181894755 +30.0 -14.939563132923542 9.05968686707646 +29.999999999999993 -14.560210818105244 8.49194600663891 +30.0 -14.426999999999994 7.822249999999999 +30.0 -14.560210818105247 7.15255399336109 +30.0 -15.507303993361091 6.205460818105246 +30.0 -14.939563132923539 6.58481313292354 +30.0 -16.846696006638908 6.20546081810525 +30.0 -16.177000000000003 6.072249999999999 +30.0 -17.414436867076454 6.5848131329235375 +29.999999999999993 -17.793789181894738 7.152553993361091 +22.000000000000007 20.323 11.32225 +-15.957446808510642 10.323 -3.6777500000000014 +22.000000000000007 10.323 -3.677749999999998 +22.000000000000007 20.323 -3.6777499999999996 +-30.0 10.323 11.32225 +22.000000000000007 10.323 11.32225 +18.512888012183033 10.323 -3.6777500000000014 +3.649215653894089 10.323 11.32225 +-30.0 10.323 -3.6777500000000014 +22.000000000000007 -19.677 -3.677750000000003 +-15.535294151144047 -9.677 -3.677749999999998 +-15.551298997002412 -9.677 11.32225 +-30.0 -9.677 11.32225 +22.000000000000007 -9.677 -3.677749999999998 +22.000000000000007 -9.677 11.32225 +14.771385376467595 -9.677000000000001 11.322249999999997 +-30.0 -9.677 -3.6777500000000014 +22.000000000000007 -19.677 11.32225 +14.25 -6.1770000000000005 11.32225 +14.116789181894752 -6.846696006638907 11.32225 +13.737436867076458 -7.414436867076454 11.32225 +13.169696006638908 -7.793789181894753 11.32225 +12.5 -7.927000000000002 11.32225 +11.830303993361095 -7.793789181894753 -3.677749999999998 +10.883210818105248 -6.846696006638907 11.32225 +10.750000000000002 -6.177000000000002 11.32225 +10.883210818105248 -5.507303993361092 11.32225 +11.262563132923546 -4.939563132923541 11.32225 +11.830303993361092 -4.56021081810525 11.32225 +12.5 -4.4270000000000005 11.32225 +13.169696006638912 -4.560210818105252 11.32225 +13.737436867076458 -4.939563132923545 11.32225 +14.116789181894745 -5.507303993361094 11.32225 +11.262563132923546 -7.414436867076459 -3.677749999999998 +14.116789181894752 -6.846696006638906 -3.677749999999998 +14.250000000000004 -6.177000000000001 -3.677749999999998 +13.737436867076454 -7.414436867076457 -3.677749999999998 +12.5 -7.9270000000000005 -3.677749999999998 +13.169696006638908 -7.793789181894753 -3.677749999999998 +11.262563132923546 -7.4144368670764615 11.322250000000004 +11.830303993361099 -7.793789181894749 11.322249999999997 +10.883210818105246 -6.846696006638908 -3.6777500000000014 +10.749999999999998 -6.177000000000003 -3.6777500000000014 +10.883210818105246 -5.50730399336109 -3.677749999999998 +11.262563132923546 -4.939563132923544 -3.677749999999998 +11.830303993361092 -4.560210818105249 -3.677749999999998 +12.5 -4.427 -3.6777500000000014 +13.169696006638912 -4.560210818105251 -3.6777500000000014 +13.737436867076458 -4.939563132923547 -3.6777500000000014 +14.116789181894745 -5.507303993361091 -3.677749999999998 +14.25 6.823000000000001 11.32225 +14.116789181894752 6.153303993361095 11.32225 +13.737436867076458 5.585563132923542 11.32225 +13.169696006638908 5.206210818105249 11.32225 +12.5 5.0729999999999995 11.32225 +11.830303993361092 5.206210818105249 11.32225 +11.262563132923542 5.58556313292354 11.32225 +10.88321081810525 6.153303993361093 11.32225 +10.75 6.823000000000001 11.32225 +10.88321081810525 7.4926960066389094 11.32225 +11.262563132923542 8.060436867076458 11.32225 +11.830303993361092 8.439789181894751 11.32225 +12.500000000000004 8.572999999999997 11.32225 +13.169696006638908 8.43978918189475 11.32225 +13.737436867076454 8.060436867076458 11.32225 +14.116789181894745 7.492696006638908 11.32225 +14.116789181894756 6.153303993361094 -3.677749999999998 +14.250000000000004 6.822999999999999 -3.677749999999998 +13.169696006638908 5.206210818105248 -3.677749999999998 +13.737436867076454 5.585563132923541 -3.6777500000000014 +11.830303993361092 5.206210818105244 -3.677749999999998 +12.500000000000007 5.073 -3.677749999999998 +11.262563132923539 5.585563132923539 -3.6777500000000014 +10.883210818105248 6.153303993361094 -3.677749999999998 +10.75 6.823000000000002 -3.677749999999998 +10.88321081810525 7.49269600663891 -3.677749999999998 +11.262563132923542 8.060436867076458 -3.677749999999998 +11.830303993361092 8.439789181894751 -3.677749999999998 +13.169696006638908 8.439789181894747 -3.677749999999998 +12.500000000000007 8.572999999999999 -3.677749999999998 +13.73743686707645 8.060436867076456 -3.677749999999998 +14.116789181894752 7.492696006638909 -3.677749999999998 +-11.75 6.8229999999999995 11.32225 +-11.883210818105248 6.153303993361091 11.32225 +-12.262563132923546 5.585563132923542 11.32225 +-12.830303993361092 5.206210818105249 11.32225 +-13.5 5.0729999999999995 11.32225 +-14.169696006638908 5.206210818105247 11.32225 +-14.737436867076461 5.585563132923542 11.32225 +-15.116789181894752 6.153303993361095 11.32225 +-15.25 6.8229999999999995 11.32225 +-15.116789181894749 7.492696006638904 11.32225 +-14.737436867076454 8.060436867076458 11.32225 +-14.169696006638908 8.439789181894753 11.32225 +-13.500000000000007 8.573 11.32225 +-12.830303993361099 8.439789181894755 11.32225 +-12.262563132923542 8.060436867076463 11.32225 +-11.883210818105244 7.492696006638908 11.32225 +-11.749999999999996 6.823 -3.6777500000000014 +-11.883210818105255 6.153303993361096 -3.677749999999998 +-12.830303993361095 5.2062108181052515 -3.677750000000005 +-12.262563132923542 5.5855631329235464 -3.677749999999998 +-13.5 5.073 -3.6777500000000014 +-14.169696006638908 5.20621081810525 -3.677749999999998 +-15.116789181894756 6.153303993361094 -3.6777500000000014 +-14.737436867076461 5.5855631329235464 -3.6777500000000014 +-15.25 6.823000000000004 -3.6777500000000014 +-15.116789181894749 7.492696006638907 -3.677749999999998 +-14.169696006638908 8.439789181894753 -3.6777500000000014 +-14.737436867076454 8.06043686707646 -3.677749999999998 +-13.500000000000007 8.573 -3.6777500000000014 +-12.830303993361099 8.439789181894756 -3.677749999999998 +-12.262563132923546 8.060436867076461 -3.677749999999998 +-11.883210818105248 7.492696006638909 -3.6777500000000014 +-11.75 -6.177000000000002 11.32225 +-11.883210818105248 -6.846696006638909 11.32225 +-12.262563132923546 -7.4144368670764615 11.32225 +-12.830303993361095 -7.793789181894753 11.32225 +-13.5 -7.927000000000004 11.32225 +-14.169696006638912 -7.793789181894753 11.32225 +-14.737436867076458 -7.414436867076458 11.32225 +-15.116789181894752 -6.846696006638909 11.32225 +-15.25 -6.177000000000002 11.32225 +-15.116789181894752 -5.507303993361099 11.32225 +-14.737436867076461 -4.9395631329235465 11.32225 +-14.169696006638908 -4.56021081810525 11.32225 +-13.5 -4.4270000000000005 11.32225 +-12.830303993361095 -4.56021081810525 11.32225 +-12.262563132923539 -4.939563132923539 11.32225 +-11.88321081810524 -5.507303993361094 11.32225 +-11.883210818105248 -6.846696006638911 -3.677749999999998 +-11.750000000000004 -6.177 -3.6777500000000014 +-12.262563132923546 -7.414436867076457 -3.677749999999998 +-12.830303993361095 -7.7937891818947564 -3.677749999999998 +-14.169696006638908 -7.793789181894753 -3.6777500000000014 +-13.5 -7.927000000000008 -3.677749999999998 +-14.737436867076454 -7.414436867076457 -3.677749999999998 +-15.116789181894752 -6.846696006638908 -3.677749999999998 +-15.250000000000004 -6.177 -3.6777500000000014 +-15.116789181894749 -5.507303993361097 -3.6777500000000014 +-14.737436867076461 -4.939563132923546 -3.677749999999998 +-14.169696006638908 -4.560210818105251 -3.677749999999998 +-13.5 -4.427 -3.6777500000000014 +-12.830303993361095 -4.560210818105254 -3.6777500000000014 +-12.262563132923546 -4.93956313292354 -3.677749999999998 +-11.88321081810524 -5.507303993361091 -3.677749999999998 +-17.997690162057708 -2.677 2.3222500000000004 +-17.997690162057708 3.323 2.3222500000000004 +30.0 3.323 2.3222500000000004 +30.0 3.323 -3.6777500000000014 +-23.988690162057708 3.323 2.3222500000000004 +-23.988690162057708 -2.677 2.3222500000000004 +30.0 -2.677 -3.6777500000000014 +18.011309837942285 -2.677 2.3222500000000004 +30.0 -2.677 2.3222500000000004 +18.011309837942285 3.323 -3.677749999999998 +-30.0 3.323 -3.6777500000000014 +-17.997690162057708 3.323 -3.6777500000000014 +-23.988690162057708 3.323 -3.677749999999998 +11.422110817733461 -2.677 -3.677749999999998 +-17.997690162057708 -2.677 -3.6777500000000014 +-30.0 -2.677 2.3222500000000004 +-23.988690162057708 -2.677 -3.677749999999998 +-30.0 3.323 2.3222500000000004 +24.002309837942285 -2.677 2.3222500000000004 +18.011309837942285 3.323 2.3222500000000004 +24.002309837942285 3.323 -3.6777500000000014 +-30.0 -2.677 -3.6777500000000014 +24.002309837942285 -2.677 -3.6777500000000014 +24.002309837942285 3.323 2.3222500000000004 +18.011309837942285 -2.677 -3.677749999999998 +-17.997690162057708 -4.876999999999995 -1.5777500000000053 +-17.997690162057708 -4.481173569058687 0.41220384829846246 +-17.997690162057708 -3.3539552621700417 2.099205262170041 +-17.997690162057708 -1.6669538482984616 3.226423569058687 +-17.997690162057708 0.3230000000000051 3.6222499999999958 +-17.997690162057708 2.312953848298472 3.226423569058687 +-17.997690162057708 3.9999552621700523 2.099205262170044 +-17.997690162057708 5.127173569058697 0.41220384829846424 +-17.997690162057708 5.523000000000007 -1.5777500000000053 +-17.997690162057708 5.127173569058698 -3.5677038482984695 +-17.997690162057708 -4.481173569058685 -3.5677038482984766 +-23.988690162057708 -4.876999999999992 -1.5777500000000053 +-23.988690162057708 -4.481173569058684 0.41220384829846246 +-23.988690162057708 -3.35395526217004 2.099205262170041 +-23.988690162057708 -1.6669538482984607 3.226423569058687 +-23.988690162057708 0.323000000000006 3.6222499999999958 +-23.988690162057708 2.312953848298473 3.226423569058687 +-23.988690162057708 3.999955262170054 2.099205262170044 +-23.988690162057708 5.127173569058697 0.41220384829846246 +-23.988690162057708 5.523000000000007 -1.5777500000000053 +-23.988690162057708 5.1271735690587 -3.567703848298473 +-23.988690162057708 -4.481173569058683 -3.5677038482984766 +-17.997690162057708 5.053643081306509 -3.6777500000000014 +-17.997690162057708 -4.4076430813064995 -3.6777500000000014 +-23.988690162057708 5.0536430813065145 -3.677749999999998 +-23.988690162057708 -4.4076430813064995 -3.677749999999998 +30.0 20.323 -3.6777499999999996 +30.0 20.323 11.32225 +30.0 -19.677 -3.677750000000003 +30.0 -19.677 11.32225 +24.002309837942285 5.053643081306509 -3.6777500000000014 +18.011309837942285 5.053643081306513 -3.677749999999998 +24.002309837942285 -4.4076430813064995 -3.6777500000000014 +18.011309837942285 -4.407643081306494 -3.677749999999998 +24.002309837942285 -4.876999999999995 -1.5777500000000053 +24.002309837942285 -4.481173569058687 0.41220384829846246 +24.002309837942285 -3.3539552621700417 2.099205262170041 +24.002309837942285 -1.6669538482984616 3.226423569058687 +24.002309837942285 0.3230000000000051 3.6222499999999958 +24.002309837942285 2.312953848298472 3.226423569058687 +24.002309837942285 3.9999552621700523 2.099205262170044 +24.002309837942285 5.127173569058697 0.41220384829846424 +24.002309837942285 5.523000000000007 -1.5777500000000053 +24.002309837942285 5.127173569058698 -3.5677038482984695 +24.002309837942285 -4.481173569058685 -3.5677038482984766 +18.011309837942285 -4.876999999999992 -1.5777500000000053 +18.011309837942285 -4.481173569058684 0.41220384829846246 +18.011309837942285 -3.35395526217004 2.099205262170041 +18.011309837942285 -1.6669538482984607 3.226423569058687 +18.011309837942285 0.323000000000006 3.6222499999999958 +18.011309837942285 2.312953848298473 3.226423569058687 +18.011309837942285 3.999955262170054 2.099205262170044 +18.011309837942285 5.127173569058697 0.41220384829846246 +18.011309837942285 5.523000000000007 -1.5777500000000053 +18.011309837942285 5.1271735690587 -3.567703848298473 +18.011309837942285 -4.481173569058683 -3.5677038482984766 +-12.919863212247494 1.006287737415828 11.322250000000007 +-13.189863212247493 1.4862877374158323 11.32225 +-15.109863212247499 3.4362877374158303 11.32225 +-16.999863212247483 1.5762877374158215 11.32225 +-17.53986321224749 0.19628773741582983 11.322249999999999 +-12.739863212247494 -0.3737122625841701 11.322250000000006 +-12.739863212247498 0.34628773741582886 11.322250000000004 +-12.979863212247494 -1.0937122625841718 11.322250000000004 +-13.999863212247496 -2.08371226258417 11.32225 +-14.689863212247495 -2.3237122625841695 11.32225 +-13.459863212247495 -1.7237122625841699 11.32225 +-16.939863212247495 -1.57371226258417 11.32225 +-17.35986321224749 0.9762877374158305 11.32225 +-16.3398632122475 -2.05371226258417 11.322250000000002 +-15.649863212247492 -2.3237122625841677 11.32225 +-17.329863212247496 -0.9137122625841699 11.32224999999999 +-17.509863212247502 -0.25371226258417345 11.322249999999986 +-17.539863212247496 0.1962877374158304 9.64225 +-17.359863212247497 0.9762877374158304 9.64225 +-16.999863212247497 1.5762877374158304 9.64225 +-15.109863212247495 3.43628773741583 9.64225 +-13.189863212247493 1.48628773741583 9.64225 +-12.919863212247494 1.0062877374158297 9.64225 +-12.739863212247494 0.34628773741582985 9.64225 +-12.739863212247494 -0.3737122625841701 9.64225 +-12.979863212247494 -1.09371226258417 9.64225 +-13.459863212247495 -1.7237122625841703 9.64225 +-13.999863212247494 -2.08371226258417 9.64225 +-14.689863212247495 -2.32371226258417 9.64225 +-15.649863212247494 -2.32371226258417 9.64225 +-16.339863212247494 -2.05371226258417 9.64225 +-16.939863212247495 -1.57371226258417 9.64225 +-17.329863212247496 -0.9137122625841699 9.64225 +-17.509863212247495 -0.25371226258417 9.64225 +7.756738351254477 -1.9418924731182776 11.32225 +7.60673835125448 -0.38189247311828 11.32225 +7.996738351254475 -0.05189247311827905 11.322249999999997 +8.14673835125448 -2.18189247311828 11.32225 +10.906738351254479 -1.4618924731182803 11.32225 +8.746738351254475 -2.2418924731182805 11.32225 +9.226738351254475 -2.0918924731182775 11.322249999999997 +7.786738351254477 0.7881075268817213 11.32225 +7.426738351254476 -0.891892473118279 11.32225 +7.426738351254478 -1.1618924731182783 11.322249999999993 +11.146738351254479 -1.4918924731182788 11.32225 +11.146738351254477 -2.1518924731182794 11.32225 +8.56673835125448 1.7481075268817183 11.322249999999999 +7.816738351254481 1.5381075268817215 11.32225 +8.05673835125448 0.9081075268817214 11.322249999999999 +8.776738351254474 -1.4318924731182783 11.32225 +8.536738351254476 0.12810752688172422 11.322249999999997 +9.61673835125448 -1.7018924731182792 11.322250000000002 +8.83673835125448 1.1181075268817215 11.322249999999997 +7.546738351254474 -1.6418924731182787 11.322249999999997 +8.536738351254476 -1.1618924731182818 11.32225 +9.316738351254479 1.0581075268817208 11.32225 +9.256738351254473 -1.4318924731182792 11.322250000000002 +10.36673835125448 -2.2418924731182828 11.322249999999997 +10.096738351254476 1.6581075268817225 11.322249999999997 +8.686738351254476 -0.5618924731182797 11.322249999999997 +8.506738351254475 -0.8618924731182792 11.32225 +9.946738351254476 -2.091892473118282 11.322249999999997 +9.496738351254475 1.8081075268817197 11.32225 +9.616738351254476 0.24810752688172077 11.322250000000004 +9.586738351254477 -1.1918924731182794 11.322249999999993 +10.726738351254475 -1.3118924731182808 11.322249999999993 +10.666738351254477 0.8781075268817216 11.32225 +9.586738351254478 0.7881075268817216 11.32225 +9.706738351254476 -1.7918924731182786 11.32225 +9.166738351254473 -0.41189247311827915 11.322249999999997 +10.486738351254473 1.3281075268817206 11.32225 +9.496738351254475 0.21810752688172008 11.322250000000002 +9.61673835125448 -0.44189247311827906 11.322249999999997 +7.4267383512544765 -0.891892473118279 9.67725 +7.606738351254478 -0.3818924731182791 9.67725 +7.996738351254475 -0.05189247311827877 9.67725 +8.536738351254474 0.12810752688172145 9.67725 +9.496738351254475 0.21810752688172086 9.67725 +9.616738351254478 0.24810752688172089 9.67725 +9.586738351254478 0.7881075268817215 9.67725 +9.316738351254479 1.058107526881721 9.67725 +8.836738351254478 1.118107526881721 9.67725 +8.056738351254479 0.9081075268817216 9.67725 +7.786738351254474 0.7881075268817215 9.67725 +7.816738351254479 1.5381075268817215 9.67725 +8.566738351254479 1.748107526881721 9.67725 +9.496738351254475 1.8081075268817215 9.67725 +10.096738351254478 1.6581075268817216 9.67725 +10.486738351254475 1.3281075268817215 9.67725 +10.666738351254477 0.8781075268817216 9.67725 +10.726738351254475 -1.311892473118279 9.67725 +10.906738351254477 -1.461892473118279 9.67725 +11.146738351254477 -1.4918924731182792 9.67725 +11.146738351254477 -2.15189247311828 9.67725 +10.366738351254478 -2.241892473118279 9.67725 +9.946738351254476 -2.0918924731182797 9.67725 +9.706738351254476 -1.7918924731182795 9.67725 +9.616738351254478 -1.7018924731182796 9.67725 +9.226738351254475 -2.0918924731182797 9.67725 +8.746738351254475 -2.241892473118279 9.67725 +8.146738351254477 -2.1818924731182796 9.67725 +7.756738351254475 -1.9418924731182798 9.67725 +7.546738351254474 -1.6418924731182791 9.67725 +7.4267383512544765 -1.1618924731182791 9.67725 +8.536738351254474 -1.1618924731182791 9.67725 +8.686738351254476 -0.5618924731182792 9.67725 +8.506738351254475 -0.861892473118279 9.67725 +9.586738351254478 -1.1918924731182794 9.67725 +9.256738351254475 -1.4318924731182792 9.67725 +9.616738351254478 -0.44189247311827906 9.67725 +9.166738351254477 -0.41189247311827915 9.67725 +8.776738351254474 -1.4318924731182792 9.67725 +671 426 Edge Vertex Indices Starting from 1 +426 107 +647 646 +646 645 +633 261 +672 455 +431 417 +417 101 +193 174 +174 172 +80 63 +627 247 +190 176 +176 175 +191 190 +190 175 +188 172 +172 171 +481 143 +83 63 +18 17 +17 16 +142 137 +137 127 +85 67 +67 66 +85 68 +68 67 +736 731 +731 685 +671 107 +667 148 +661 148 +193 191 +191 174 +20 19 +19 14 +190 177 +177 176 +19 15 +15 14 +448 434 +434 431 +481 249 +249 33 +19 18 +18 16 +19 16 +16 15 +484 247 +665 425 +708 674 +24 23 +23 12 +188 171 +171 170 +694 677 +677 666 +191 175 +175 174 +193 172 +153 128 +142 127 +127 126 +627 626 +626 247 +20 14 +14 13 +30 12 +710 673 +673 667 +366 352 +352 335 +32 6 +597 276 +34 7 +470 453 +34 32 +32 7 +33 32 +195 33 +280 279 +296 280 +300 285 +187 170 +170 169 +295 278 +314 312 +402 314 +639 637 +637 522 +474 458 +389 373 +358 342 +40 17 +646 627 +194 181 +181 180 +328 310 +44 43 +43 3 +44 3 +92 63 +111 84 +401 317 +317 315 +404 289 +289 288 +43 2 +624 600 +45 2 +562 549 +46 20 +510 494 +731 702 +702 701 +38 35 +49 38 +643 630 +348 347 +363 348 +679 152 +44 4 +222 219 +219 214 +402 313 +313 310 +38 15 +683 665 +665 426 +49 15 +363 362 +408 363 +42 23 +323 322 +322 306 +481 195 +195 143 +52 23 +569 558 +558 552 +55 12 +724 665 +55 37 +37 12 +614 604 +604 567 +153 152 +152 128 +434 417 +735 733 +733 716 +673 146 +53 5 +620 564 +621 620 +366 365 +408 366 +494 493 +509 494 +596 328 +251 43 +57 56 +56 29 +57 29 +293 291 +399 293 +287 286 +301 287 +56 26 +30 27 +60 30 +339 338 +354 339 +61 59 +59 28 +61 28 +467 453 +453 452 +509 493 +210 209 +209 182 +615 605 +605 604 +391 271 +357 341 +597 274 +413 404 +404 99 +417 100 +562 555 +555 549 +512 497 +497 496 +569 552 +616 569 +253 229 +254 253 +505 489 +402 312 +658 641 +326 309 +596 327 +292 291 +291 274 +328 312 +330 328 +598 349 +365 349 +610 568 +253 41 +325 323 +323 307 +640 639 +639 522 +324 308 +625 615 +615 604 +412 361 +361 360 +501 485 +659 643 +643 642 +592 503 +399 296 +296 295 +325 307 +408 362 +412 408 +401 318 +318 317 +399 281 +291 275 +275 274 +459 458 +475 459 +295 294 +399 295 +195 34 +325 308 +53 6 +621 611 +611 610 +256 51 +470 454 +454 453 +350 349 +365 350 +323 306 +596 329 +362 346 +607 563 +281 280 +296 281 +326 324 +324 309 +597 275 +332 315 +333 332 +506 490 +278 277 +294 278 +327 310 +328 327 +566 560 +399 294 +294 293 +39 35 +282 280 +485 484 +499 485 +341 340 +356 341 +616 615 +615 569 +497 481 +401 305 +305 303 +554 467 +508 491 +597 278 +412 360 +360 359 +252 43 +638 629 +629 488 +211 39 +554 470 +470 467 +320 286 +299 284 +251 53 +46 21 +47 46 +576 546 +620 609 +642 411 +282 281 +297 282 +623 613 +613 612 +596 330 +330 329 +585 574 +618 608 +608 607 +503 489 +489 488 +582 571 +466 449 +408 336 +366 335 +622 611 +619 608 +36 35 +195 36 +554 469 +61 30 +647 626 +408 365 +365 364 +302 287 +195 54 +54 36 +256 47 +623 612 +55 48 +48 37 +597 277 +479 464 +464 463 +480 464 +597 279 +331 314 +401 303 +346 345 +361 346 +404 290 +290 289 +540 524 +251 44 +598 335 +329 312 +331 329 +329 314 +408 352 +328 313 +313 312 +285 284 +299 285 +330 312 +333 315 +404 292 +292 290 +614 602 +284 283 +298 284 +58 26 +638 488 +483 247 +669 150 +246 231 +231 230 +78 77 +79 78 +736 685 +84 65 +86 71 +87 86 +80 77 +81 80 +561 560 +560 550 +534 409 +264 262 +93 92 +92 64 +93 64 +363 347 +430 104 +479 477 +477 405 +93 65 +417 414 +414 413 +94 93 +93 66 +431 101 +96 67 +616 605 +97 68 +446 445 +558 446 +98 97 +97 69 +98 69 +100 71 +445 428 +101 72 +102 101 +101 73 +165 163 +163 162 +362 347 +347 346 +103 74 +451 94 +104 75 +718 670 +670 664 +452 94 +518 410 +105 77 +429 428 +445 429 +451 95 +583 550 +584 583 +79 63 +107 79 +107 63 +446 429 +539 523 +601 600 +624 601 +352 336 +412 362 +362 361 +598 348 +242 229 +229 228 +50 20 +101 100 +100 72 +514 210 +394 377 +102 73 +439 410 +562 411 +411 403 +605 567 +106 78 +108 88 +88 82 +364 349 +349 348 +478 405 +113 80 +413 375 +375 374 +364 348 +109 90 +90 88 +611 568 +608 563 +596 401 +591 570 +592 579 +594 592 +624 613 +599 385 +421 420 +437 421 +374 373 +413 374 +573 546 +546 545 +350 335 +598 350 +463 404 +450 95 +453 92 +605 563 +557 549 +517 410 +599 408 +538 537 +593 538 +582 572 +572 571 +520 410 +505 490 +490 489 +621 610 +598 408 +462 404 +618 552 +619 618 +437 420 +402 399 +596 402 +572 545 +461 406 +117 90 +461 404 +188 170 +113 81 +118 113 +506 400 +508 506 +196 167 +584 574 +574 573 +414 412 +597 399 +494 406 +460 406 +413 99 +534 532 +532 409 +614 567 +515 514 +529 515 +396 378 +438 422 +368 367 +416 368 +588 577 +420 414 +625 604 +414 410 +410 409 +531 515 +591 581 +581 570 +625 614 +614 603 +106 79 +107 106 +419 418 +433 419 +387 385 +385 370 +562 560 +560 411 +406 403 +493 406 +510 509 +509 400 +352 351 +351 335 +382 367 +383 382 +416 370 +370 369 +192 180 +180 179 +427 107 +593 537 +537 536 +567 551 +602 567 +715 692 +437 436 +436 421 +439 414 +586 549 +587 586 +418 414 +453 91 +357 342 +342 341 +476 400 +532 517 +517 516 +491 403 +109 88 +435 419 +599 387 +396 379 +464 404 +366 350 +396 393 +393 378 +524 523 +539 524 +493 403 +534 517 +599 384 +617 606 +437 412 +428 107 +622 612 +612 611 +373 372 +413 373 +491 490 +506 491 +421 414 +439 422 +593 539 +539 538 +490 403 +599 386 +386 385 +553 547 +547 391 +581 571 +571 570 +525 524 +540 525 +420 419 +435 420 +416 369 +369 368 +599 416 +416 408 +412 409 +436 412 +386 384 +384 368 +508 400 +598 347 +387 370 +450 96 +386 368 +606 563 +599 413 +535 533 +533 409 +449 98 +408 364 +364 363 +538 523 +523 522 +398 382 +541 525 +613 600 +600 565 +562 550 +394 392 +392 376 +622 554 +623 622 +618 607 +507 493 +493 492 +573 545 +431 102 +464 449 +449 404 +428 427 +444 428 +562 407 +384 367 +478 476 +476 405 +509 507 +507 400 +429 106 +492 403 +114 109 +109 108 +395 379 +394 376 +519 410 +384 383 +383 367 +428 106 +419 414 +562 403 +587 577 +577 576 +619 609 +609 608 +596 399 +597 596 +355 339 +617 552 +618 617 +480 479 +479 405 +393 377 +599 383 +412 359 +359 358 +594 579 +114 110 +110 109 +115 110 +30 24 +24 12 +632 247 +26 8 +8 7 +129 128 +128 127 +115 111 +111 110 +134 120 +120 119 +135 134 +134 119 +139 130 +140 120 +141 136 +136 123 +144 143 +143 119 +144 119 +334 303 +203 175 +298 283 +145 120 +532 516 +516 515 +502 501 +556 502 +392 341 +600 548 +147 122 +460 459 +475 460 +585 584 +586 585 +418 417 +434 418 +584 550 +550 549 +592 400 +533 518 +534 533 +495 458 +556 501 +501 499 +415 409 +416 367 +154 129 +407 400 +592 407 +399 291 +638 489 +489 403 +351 336 +353 351 +583 572 +592 556 +157 133 +133 119 +260 259 +259 236 +145 144 +144 120 +80 79 +79 77 +147 146 +146 122 +148 124 +616 606 +606 605 +542 526 +526 525 +149 125 +587 575 +162 136 +164 135 +166 134 +479 463 +463 462 +578 576 +166 140 +140 134 +587 576 +576 575 +597 388 +388 274 +548 332 +559 540 +540 539 +570 559 +593 570 +590 579 +519 518 +533 519 +595 591 +591 580 +321 304 +296 279 +279 278 +592 506 +506 505 +598 398 +599 598 +575 546 +389 274 +569 446 +564 554 +622 564 +518 517 +534 518 +612 565 +359 343 +343 342 +516 439 +439 438 +609 568 +621 564 +622 621 +164 139 +354 337 +559 541 +541 540 +322 321 +321 306 +571 559 +118 117 +117 113 +495 459 +402 315 +579 556 +402 310 +160 158 +504 486 +206 157 +594 407 +535 519 +401 315 +402 401 +558 542 +559 558 +201 143 +563 553 +567 563 +334 318 +318 303 +292 273 +296 278 +354 338 +338 337 +316 314 +402 316 +620 610 +610 609 +542 525 +566 415 +595 566 +566 561 +157 119 +583 582 +582 550 +565 548 +548 547 +620 619 +619 564 +398 383 +599 398 +558 444 +444 443 +372 371 +416 372 +613 565 +532 515 +571 545 +475 458 +448 430 +459 406 +494 459 +573 572 +583 573 +479 462 +415 411 +560 415 +611 565 +617 607 +607 606 +520 519 +535 520 +292 274 +274 273 +568 565 +565 547 +412 358 +413 412 +301 300 +304 301 +589 578 +578 577 +556 546 +578 556 +586 584 +584 549 +413 372 +416 413 +165 140 +166 165 +556 499 +205 157 +595 415 +319 303 +153 129 +154 153 +542 541 +559 542 +592 505 +505 503 +575 573 +340 339 +355 340 +457 143 +578 546 +359 342 +584 573 +388 387 +387 372 +588 549 +456 144 +504 487 +487 486 +536 535 +535 409 +569 447 +447 446 +594 590 +590 557 +371 370 +416 371 +595 561 +200 143 +580 570 +593 580 +568 563 +608 568 +445 444 +558 445 +579 578 +589 579 +455 454 +469 455 +555 407 +594 555 +619 552 +590 589 +589 557 +360 344 +344 343 +404 399 +399 292 +589 577 +551 345 +499 484 +500 499 +283 282 +297 283 +356 340 +405 400 +406 405 +202 143 +166 135 +448 431 +431 430 +471 455 +484 483 +500 484 +552 545 +564 552 +595 580 +415 410 +598 412 +457 144 +593 559 +559 539 +564 545 +553 551 +567 553 +302 271 +591 561 +516 410 +288 271 +302 288 +425 152 +476 460 +161 136 +162 161 +599 388 +602 551 +551 412 +404 302 +433 418 +434 433 +458 457 +473 458 +360 343 +353 337 +334 333 +333 318 +557 555 +594 557 +589 588 +588 557 +480 466 +466 464 +603 434 +387 371 +404 288 +320 319 +319 305 +617 616 +616 552 +333 317 +203 143 +587 549 +588 587 +536 520 +470 469 +469 454 +512 474 +474 473 +683 666 +666 665 +174 173 +173 172 +221 184 +183 182 +182 181 +673 148 +187 169 +194 180 +323 284 +270 269 +269 262 +393 339 +389 273 +197 168 +355 354 +379 355 +504 502 +556 504 +196 195 +195 167 +199 170 +354 353 +380 354 +598 351 +673 147 +672 456 +456 146 +510 400 +396 395 +395 338 +404 98 +204 203 +203 176 +597 280 +496 458 +514 423 +423 210 +553 342 +378 355 +379 378 +218 190 +496 481 +481 457 +211 210 +210 182 +211 182 +543 443 +443 442 +302 301 +305 302 +561 550 +582 561 +200 199 +199 171 +200 171 +379 354 +380 379 +670 107 +107 91 +210 208 +438 423 +515 438 +496 457 +298 297 +309 298 +325 324 +324 282 +380 353 +381 380 +322 286 +358 357 +376 358 +392 342 +553 392 +208 207 +207 179 +208 179 +394 393 +393 340 +320 287 +547 320 +297 281 +311 297 +213 192 +192 185 +212 192 +602 435 +435 433 +215 187 +187 186 +390 273 +548 333 +215 186 +397 337 +198 196 +402 311 +311 281 +217 212 +212 185 +217 185 +219 193 +193 188 +214 193 +480 405 +601 480 +376 375 +375 358 +596 280 +599 597 +597 413 +548 334 +334 319 +500 498 +498 469 +597 404 +396 338 +627 484 +353 336 +381 353 +531 432 +432 422 +357 356 +377 357 +325 282 +382 381 +381 336 +398 351 +351 337 +510 476 +476 475 +325 283 +548 319 +322 284 +416 336 +652 633 +436 409 +532 436 +601 466 +531 422 +500 469 +398 337 +327 280 +603 433 +553 391 +323 283 +547 319 +602 433 +603 602 +551 344 +592 504 +322 285 +215 189 +216 215 +377 356 +378 377 +326 280 +356 355 +378 356 +516 438 +598 346 +326 282 +543 542 +542 443 +309 297 +311 309 +222 214 +214 187 +396 339 +222 187 +413 358 +544 543 +543 442 +601 465 +551 346 +598 551 +394 340 +497 473 +473 472 +515 423 +305 301 +581 561 +582 581 +397 338 +558 443 +510 475 +475 474 +304 300 +306 304 +321 286 +416 382 +382 336 +532 531 +531 436 +394 341 +401 302 +554 499 +499 469 +402 281 +210 207 +376 357 +377 376 +308 298 +309 308 +512 473 +472 457 +457 456 +190 185 +185 177 +699 668 +241 234 +234 231 +239 238 +238 223 +243 234 +241 231 +246 241 +220 184 +249 248 +248 225 +712 688 +250 249 +249 225 +250 225 +251 250 +250 227 +90 77 +77 76 +252 227 +253 228 +473 457 +255 230 +234 233 +233 231 +648 626 +257 232 +663 660 +660 152 +258 233 +720 719 +719 716 +259 235 +189 181 +261 237 +237 223 +247 237 +634 524 +635 524 +248 224 +250 226 +252 251 +251 227 +253 252 +252 228 +719 717 +717 716 +254 229 +655 639 +257 256 +256 232 +697 689 +260 236 +630 411 +642 630 +263 262 +262 241 +263 241 +267 238 +243 241 +262 243 +635 634 +654 635 +629 628 +628 488 +257 233 +258 257 +260 258 +628 487 +656 639 +654 634 +647 627 +530 441 +441 440 +111 85 +85 84 +636 634 +634 525 +652 636 +636 633 +270 238 +269 243 +711 688 +688 672 +641 411 +156 132 +209 181 +692 670 +670 91 +113 109 +665 663 +663 425 +711 672 +498 472 +472 471 +710 667 +25 3 +3 2 +704 693 +693 689 +482 249 +668 661 +661 149 +714 692 +718 717 +719 718 +674 667 +667 662 +705 693 +702 662 +720 687 +687 683 +112 84 +84 83 +729 669 +705 681 +202 173 +709 708 +710 709 +245 224 +736 735 +735 703 +707 681 +150 149 +149 126 +62 57 +57 31 +679 669 +669 152 +90 76 +648 632 +632 626 +649 632 +708 678 +669 151 +511 510 +510 474 +736 702 +707 678 +712 707 +707 706 +240 228 +228 227 +165 160 +149 148 +148 125 +544 441 +210 157 +92 91 +91 63 +688 684 +684 455 +691 664 +711 707 +712 711 +136 124 +124 123 +735 704 +704 703 +233 232 +232 231 +713 712 +712 706 +112 110 +110 84 +717 664 +696 684 +714 696 +86 82 +82 72 +498 497 +497 472 +710 708 +711 710 +390 389 +389 374 +737 725 +725 724 +50 19 +714 684 +726 663 +725 663 +717 691 +688 455 +710 672 +692 691 +691 670 +449 97 +729 699 +699 669 +694 666 +698 695 +695 690 +737 724 +736 703 +703 702 +450 97 +711 708 +708 707 +62 31 +31 25 +721 687 +706 681 +242 228 +725 665 +117 109 +514 211 +211 46 +514 46 +25 4 +4 3 +27 10 +10 9 +156 155 +155 132 +214 188 +188 187 +512 511 +511 474 +31 4 +696 692 +692 91 +31 5 +5 4 +33 6 +36 11 +36 10 +40 39 +39 17 +211 50 +50 46 +478 477 +477 461 +477 462 +462 461 +49 48 +48 15 +48 14 +52 51 +51 22 +52 22 +53 44 +44 5 +163 161 +54 34 +34 8 +54 8 +103 102 +102 74 +478 460 +478 461 +461 460 +50 40 +40 18 +50 18 +253 45 +252 45 +45 43 +704 689 +211 40 +528 257 +260 257 +650 632 +650 631 +61 60 +60 59 +60 58 +544 442 +442 441 +195 32 +211 195 +195 39 +195 35 +600 401 +548 401 +596 548 +116 111 +687 666 +290 272 +290 273 +273 272 +294 276 +294 277 +277 276 +361 344 +361 345 +345 344 +327 326 +326 310 +326 311 +311 310 +657 641 +641 637 +289 271 +289 272 +272 271 +301 285 +301 286 +286 285 +500 482 +500 483 +483 482 +332 331 +331 316 +332 316 +316 315 +51 47 +47 21 +51 21 +512 495 +512 496 +496 495 +511 494 +511 495 +495 494 +663 152 +49 37 +49 35 +83 65 +65 63 +218 217 +217 190 +65 64 +64 63 +90 81 +81 77 +96 95 +95 67 +508 507 +507 491 +507 492 +492 491 +105 104 +104 76 +105 76 +106 105 +105 78 +268 264 +386 370 +386 369 +97 96 +96 68 +100 99 +99 71 +202 201 +201 173 +104 103 +103 75 +113 112 +112 80 +112 83 +83 80 +114 108 +108 86 +108 82 +439 421 +422 421 +436 422 +538 521 +538 522 +522 521 +389 388 +388 373 +388 372 +431 103 +430 103 +452 92 +94 92 +397 395 +395 381 +395 380 +521 410 +521 411 +411 410 +398 397 +397 382 +397 381 +543 526 +543 527 +527 526 +414 409 +89 70 +70 69 +544 527 +544 528 +528 527 +586 574 +586 575 +575 574 +443 426 +442 426 +426 425 +537 520 +537 521 +521 520 +417 413 +413 100 +118 81 +117 81 +429 104 +106 104 +133 132 +132 131 +139 135 +135 131 +139 131 +131 130 +456 145 +504 503 +503 487 +503 488 +488 487 +152 151 +151 128 +151 127 +593 536 +536 409 +155 131 +625 603 +615 603 +603 569 +146 145 +145 122 +145 121 +559 545 +558 545 +466 465 +465 449 +465 450 +450 449 +159 158 +158 139 +159 139 +139 137 +160 159 +159 142 +159 137 +441 423 +441 424 +424 423 +624 623 +623 601 +623 554 +637 521 +637 411 +468 451 +465 451 +451 450 +425 153 +444 427 +443 427 +427 426 +407 403 +403 400 +406 400 +442 424 +442 425 +425 424 +293 276 +291 276 +276 275 +596 332 +596 331 +472 456 +471 456 +456 455 +498 482 +497 482 +482 481 +199 196 +199 143 +196 143 +468 467 +467 452 +468 452 +452 451 +204 157 +203 157 +157 143 +321 320 +320 304 +320 305 +305 304 +156 154 +156 153 +603 448 +569 448 +448 447 +194 184 +184 181 +632 631 +631 261 +218 216 +628 486 +498 471 +471 469 +632 261 +261 247 +211 167 +211 183 +183 167 +199 198 +198 170 +203 202 +202 175 +202 174 +659 653 +653 643 +219 218 +218 191 +219 191 +308 307 +307 299 +308 299 +299 298 +197 196 +196 168 +30 28 +28 24 +307 306 +306 300 +307 300 +300 299 +207 157 +481 33 +553 343 +551 343 +391 272 +390 272 +667 661 +86 72 +72 71 +237 236 +236 235 +270 267 +630 403 +256 255 +255 232 +255 231 +636 525 +659 658 +658 657 +157 156 +156 133 +88 76 +76 75 +268 265 +255 254 +254 230 +261 260 +260 237 +718 664 +530 440 +654 653 +656 654 +656 655 +655 654 +653 652 +652 650 +652 651 +651 650 +254 41 +640 522 +26 9 +9 8 +58 56 +683 426 +669 668 +668 150 +668 149 +734 723 +734 724 +724 723 +729 679 +700 668 +700 661 +113 110 +27 9 +673 672 +672 146 +638 403 +702 676 +676 662 +678 674 +676 674 +674 662 +703 689 +721 694 +694 687 +23 22 +22 13 +23 13 +13 12 +636 261 +22 21 +21 13 +21 20 +20 13 +736 698 +736 695 +42 1 +42 24 +24 1 +27 12 +12 11 +27 11 +11 10 +568 553 +568 547 +250 33 +250 53 +53 33 +37 35 +35 11 +37 11 +649 648 +648 644 +245 240 +240 226 +240 227 +227 226 +246 229 +246 230 +230 229 +85 66 +84 66 +66 65 +595 593 +593 415 +593 409 +640 523 +635 523 +95 94 +94 67 +94 66 +45 41 +41 1 +45 1 +447 429 +447 430 +430 429 +116 115 +115 89 +115 87 +115 114 +114 87 +114 86 +116 85 +116 89 +89 85 +644 630 +644 638 +638 630 +696 453 +696 91 +89 87 +87 70 +87 71 +71 70 +148 147 +147 124 +147 123 +728 679 +727 679 +679 660 +99 98 +98 70 +99 70 +163 136 +163 138 +138 136 +502 486 +501 486 +486 485 +544 530 +530 528 +530 513 +406 401 +406 404 +404 401 +438 432 +440 438 +440 423 +601 405 +600 405 +405 401 +28 25 +25 2 +183 181 +189 183 +201 200 +200 173 +200 172 +221 216 +216 189 +221 189 +189 184 +531 529 +529 432 +529 440 +440 432 +601 554 +554 468 +601 468 +468 465 +530 529 +529 513 +529 514 +514 513 +547 271 +547 287 +287 271 +29 26 +26 7 +29 7 +7 6 +697 678 +678 676 +653 634 +652 634 +88 74 +88 75 +75 74 +264 263 +263 242 +263 246 +246 242 +266 265 +265 245 +265 240 +265 264 +264 240 +264 242 +242 240 +268 266 +266 244 +266 245 +245 244 +528 260 +527 260 +735 716 +716 715 +735 715 +715 704 +138 124 +138 125 +125 124 +31 29 +29 5 +29 6 +6 5 +255 41 +255 42 +42 41 +645 638 +645 629 +727 660 +726 660 +723 677 +735 698 +733 698 +698 690 +715 714 +714 705 +715 705 +705 704 +185 178 +178 177 +732 730 +730 686 +730 680 +483 249 +483 248 +142 125 +142 126 +126 125 +89 68 +89 69 +69 68 +724 666 +723 666 +186 169 +169 167 +169 168 +168 167 +734 733 +733 690 +734 690 +690 682 +733 723 +723 722 +733 722 +722 716 +714 713 +713 705 +713 706 +706 705 +716 691 +715 691 +720 683 +719 683 +683 671 +165 158 +82 74 +74 73 +82 73 +73 72 +719 671 +718 671 +671 670 +701 662 +700 662 +662 661 +737 726 +726 725 +155 154 +154 130 +155 130 +696 454 +684 454 +732 686 +731 686 +686 685 +209 208 +208 180 +209 180 +722 721 +721 720 +722 720 +720 716 +39 38 +38 17 +38 16 +54 9 +36 9 +222 218 +392 391 +391 375 +392 375 +602 412 +435 412 +437 435 +133 131 +135 133 +135 119 +137 130 +130 127 +130 129 +129 127 +151 150 +150 126 +151 126 +163 160 +160 138 +160 142 +142 138 +556 554 +564 556 +564 546 +391 390 +390 375 +390 374 +198 197 +197 169 +198 169 +628 627 +627 485 +628 485 +703 676 +703 697 +697 676 +205 204 +204 176 +205 176 +207 206 +206 178 +207 178 +220 194 +213 194 +194 192 +709 674 +709 667 +248 247 +247 223 +248 223 +651 631 +651 633 +633 631 +259 258 +258 235 +258 234 +659 657 +657 656 +659 656 +656 653 +649 644 +644 643 +659 642 +658 642 +642 641 +646 629 +646 628 +210 156 +657 639 +657 637 +713 688 +713 684 +141 121 +140 121 +121 120 +423 156 +528 513 +513 257 +513 256 +732 728 +730 728 +728 727 +28 1 +28 2 +2 1 +722 677 +721 677 +729 728 +732 729 +732 699 +141 123 +123 121 +123 122 +122 121 +636 526 +636 527 +527 261 +62 59 +59 58 +62 58 +58 57 +55 14 +55 13 +62 25 +59 25 +513 46 +256 46 +648 647 +647 645 +648 645 +645 644 +655 640 +654 640 +640 635 +165 162 +162 141 +165 141 +141 140 +189 186 +186 183 +186 167 +206 205 +205 177 +206 177 +256 52 +255 52 +52 42 +697 693 +693 681 +697 681 +681 678 +244 224 +238 224 +224 223 +732 731 +731 701 +737 675 +730 675 +270 262 +268 262 +192 178 +192 179 +179 178 +166 158 +166 164 +164 158 +686 682 +695 685 +690 685 +685 682 +737 730 +730 727 +737 727 +727 726 +60 26 +60 27 +27 26 +222 215 +218 215 +270 268 +268 244 +270 244 +244 238 +237 235 +239 237 +239 223 +243 239 +239 235 +243 235 +235 234 +653 650 +650 643 +650 649 +649 643 +732 701 +701 699 +701 700 +700 699 +221 217 +217 216 +220 213 +213 212 +424 153 +423 153 +737 734 +734 675 +734 682 +682 675 +686 680 +682 680 +680 675 +245 226 +226 225 +245 225 +225 224 +269 267 +267 239 +269 239 +221 220 +220 212 +221 212 +1 2 31 Face Edge Indices Starting from 1 +2161 3 4 +1709 2105 5 +6 1683 1035 +388 7 8 +9 10 61 +11 419 744 +12 50 1126 +16 13 14 +59 15 16 +17 18 55 +899 1052 19 +11 1569 20 +21 22 47 +63 23 24 +25 26 1842 +27 28 25 +29 30 366 +2027 31 1067 +1747 32 33 +33 1362 1320 +9 34 35 +36 37 67 +13 38 39 +40 41 37 +949 42 43 +44 45 1742 +46 47 48 +48 49 40 +50 360 952 +130 51 1609 +52 1801 1351 +53 54 682 +55 56 496 +57 58 1408 +35 59 60 +17 1111 61 +62 686 890 +1992 63 64 +65 66 12 +67 68 1815 +1821 171 69 +1313 70 71 +72 73 312 +1933 79 74 +1501 329 75 +1463 76 685 +289 77 178 +78 79 76 +80 1487 81 +82 778 83 +84 350 1740 +85 86 1016 +87 262 237 +88 200 89 +90 91 215 +92 981 1007 +1393 93 464 +561 1080 94 +21 1471 95 +96 2083 2119 +97 98 1017 +347 99 127 +102 100 101 +1428 123 102 +103 1541 377 +1382 689 104 +105 106 817 +994 107 108 +1315 101 109 +460 110 628 +1475 111 109 +719 630 112 +1814 293 113 +160 538 114 +115 116 2188 +117 1535 118 +1272 223 119 +120 379 121 +1246 1882 122 +1441 1460 123 +124 125 1175 +126 127 811 +49 2050 128 +1784 129 130 +118 131 128 +132 228 133 +53 1819 134 +249 135 136 +19 137 138 +2179 139 134 +195 140 141 +1810 2155 142 +143 1009 1997 +144 145 142 +511 146 147 +148 149 62 +43 150 7 +151 152 1958 +1034 153 746 +1968 241 154 +155 797 156 +157 320 158 +159 180 160 +161 207 302 +100 341 162 +165 163 164 +1965 1344 165 +166 731 167 +168 1515 169 +1930 164 170 +171 2208 172 +173 828 174 +177 175 176 +1735 318 177 +1691 178 179 +180 636 648 +1054 181 182 +183 184 218 +1513 1745 185 +1131 186 273 +234 257 187 +508 188 189 +8 190 433 +112 191 192 +1528 193 194 +195 998 196 +197 1265 198 +307 895 199 +200 348 126 +1759 201 1509 +1174 1507 202 +203 264 161 +868 204 205 +206 351 207 +247 469 208 +321 209 445 +210 796 833 +1853 1473 211 +227 212 213 +214 215 1780 +1212 256 216 +217 218 520 +282 219 220 +271 725 221 +222 223 2115 +224 1626 1159 +238 225 226 +1729 240 227 +228 427 229 +230 231 105 +232 254 225 +205 233 234 +235 854 236 +237 266 238 +1462 323 239 +1075 240 216 +241 1442 1829 +242 243 484 +1456 2177 244 +77 245 246 +247 209 248 +1737 213 249 +344 1680 250 +428 251 337 +252 617 865 +253 83 254 +202 255 256 +257 1678 75 +258 352 259 +781 260 482 +261 1500 262 +263 99 264 +265 372 837 +167 266 267 +2048 268 117 +253 298 269 +270 940 271 +272 944 273 +196 274 275 +1051 194 276 +277 278 335 +1690 1920 279 +500 280 588 +779 333 281 +675 282 283 +284 162 1259 +285 286 359 +1445 1477 287 +279 288 289 +168 1089 290 +291 357 1732 +292 1828 1233 +293 1525 294 +295 784 764 +843 296 664 +2117 297 1302 +298 1091 299 +300 301 326 +302 303 250 +714 304 502 +305 306 635 +1628 307 308 +1191 309 598 +640 991 310 +426 346 311 +468 572 312 +798 313 242 +488 314 305 +315 1490 316 +1004 288 317 +318 172 1482 +319 65 1289 +622 320 321 +1058 322 169 +323 324 316 +1524 244 325 +634 326 583 +327 328 144 +261 281 329 +758 330 331 +671 332 330 +82 1043 333 +830 1521 334 +825 230 335 +336 1503 337 +107 338 339 +576 769 340 +1459 292 341 +342 541 1033 +345 343 88 +334 344 345 +158 346 72 +206 347 348 +349 291 350 +303 351 343 +106 999 352 +338 353 354 +1157 527 355 +356 703 357 +358 170 1783 +359 308 732 +1990 360 2100 +1352 361 2067 +362 363 1840 +364 745 365 +366 2200 1817 +367 1536 1331 +368 1875 369 +370 1543 371 +1060 372 373 +620 722 374 +375 1273 1941 +378 376 377 +1540 383 378 +379 404 132 +380 1617 1858 +672 381 382 +387 383 1844 +1613 384 385 +1852 386 387 +400 639 388 +28 1559 389 +274 390 183 +1996 395 391 +392 924 393 +396 394 395 +1600 1884 396 +1749 434 397 +643 923 398 +2024 401 399 +400 401 437 +2167 402 403 +251 404 405 +1940 1565 406 +1850 416 407 +1551 408 1763 +1769 409 410 +407 1693 411 +475 789 412 +364 1554 413 +414 398 415 +416 471 1665 +417 718 418 +419 421 420 +1068 421 1367 +422 415 392 +423 624 592 +424 110 425 +734 540 426 +219 427 428 +446 208 429 +430 431 1421 +113 1448 432 +399 433 434 +1424 435 1053 +1087 436 673 +2022 1466 437 +521 556 438 +661 439 440 +441 147 184 +528 442 365 +1572 443 444 +451 445 446 +382 1449 447 +371 497 448 +449 450 465 +451 121 623 +567 452 453 +210 243 454 +455 252 306 +456 818 491 +457 919 776 +458 677 459 +1658 460 300 +461 532 569 +462 489 463 +464 586 465 +466 467 638 +468 342 469 +486 759 470 +1544 615 471 +472 1584 179 +473 823 441 +192 986 474 +967 565 475 +476 485 783 +477 549 478 +479 480 309 +656 866 481 +199 482 483 +156 484 832 +229 958 485 +495 1452 486 +487 931 488 +2059 489 603 +490 666 491 +853 480 492 +507 1469 493 +494 452 1423 +1896 493 495 +1434 496 85 +497 1615 498 +499 612 500 +501 2002 1734 +502 503 902 +873 385 504 +936 1124 505 +537 159 506 +856 712 507 +1614 508 1560 +374 509 510 +552 355 511 +512 1925 513 +570 514 1049 +1130 1898 515 +516 727 517 +1002 518 662 +660 602 519 +520 146 526 +521 522 1598 +1915 523 513 +524 525 457 +526 527 1636 +528 420 529 +530 978 531 +532 533 614 +534 535 439 +536 577 537 +1037 538 539 +73 540 541 +542 658 543 +544 545 604 +546 547 2194 +548 2 1669 +549 550 1633 +551 975 552 +1980 553 1321 +463 554 555 +589 1573 556 +557 1001 558 +559 384 716 +560 1366 472 +186 561 562 +563 945 647 +705 564 565 +593 587 566 +567 443 653 +531 1097 568 +569 903 974 +1038 570 654 +470 331 571 +572 248 157 +514 573 574 +575 423 576 +651 637 577 +578 564 509 +610 594 579 +997 580 749 +609 554 581 +582 548 642 +583 584 313 +585 882 586 +587 260 588 +519 462 589 +795 590 515 +591 592 478 +733 483 593 +594 595 461 +596 597 1153 +598 599 525 +600 340 601 +602 568 603 +604 605 517 +606 607 476 +608 1145 609 +616 610 611 +649 1546 612 +120 429 613 +915 993 614 +1414 615 1558 +605 1557 616 +617 473 750 +618 883 606 +910 619 620 +621 1040 641 +622 623 133 +1577 624 625 +626 543 845 +601 802 627 +851 628 629 +630 373 534 +655 631 632 +633 1660 634 +670 635 864 +1548 636 637 +638 492 858 +1465 1582 639 +571 640 641 +642 1667 643 +929 191 644 +516 611 645 +447 646 647 +539 648 649 +650 659 414 +566 1549 651 +652 653 1570 +1066 1588 654 +1210 436 655 +412 773 656 +645 657 658 +529 582 659 +559 530 660 +1670 644 661 +662 663 763 +664 665 314 +666 505 667 +668 174 1024 +669 487 670 +1113 671 672 +574 673 1164 +657 579 674 +675 676 872 +772 913 677 +678 679 652 +678 1862 680 +681 682 69 +1349 683 66 +1931 684 685 +686 687 2066 +680 688 689 +693 690 691 +692 693 2062 +2063 1651 694 +762 695 690 +2143 696 697 +698 699 700 +691 743 700 +1121 701 889 +1042 702 14 +703 943 1073 +1641 704 2127 +705 706 852 +707 724 708 +709 562 1082 +710 841 629 +2145 1879 711 +712 236 713 +714 880 715 +716 150 717 +718 719 881 +720 499 780 +721 790 722 +235 808 723 +724 725 886 +726 522 957 +727 542 1199 +2065 2034 728 +729 720 730 +731 204 937 +732 733 1798 +734 1127 735 +839 736 479 +737 810 458 +838 738 739 +740 741 1270 +704 742 743 +744 745 370 +711 746 747 +1964 1363 748 +749 750 390 +751 752 834 +1993 1342 753 +558 754 1605 +696 2168 755 +1621 799 756 +692 948 757 +758 759 860 +877 760 663 +757 761 762 +763 764 754 +187 765 766 +767 259 1101 +768 769 961 +770 960 771 +772 926 932 +773 721 774 +775 776 956 +777 1197 804 +778 779 827 +894 780 781 +782 846 783 +784 466 896 +766 1579 785 +140 786 393 +787 633 788 +789 578 790 +791 863 584 +792 793 901 +1168 794 795 +796 922 665 +797 788 798 +799 1649 2198 +1031 800 983 +801 802 768 +803 804 136 +599 805 770 +498 806 807 +857 1532 808 +1523 831 809 +810 879 925 +811 1508 1105 +1648 1652 812 +1026 813 1889 +814 887 2174 +815 730 459 +816 774 619 +817 809 818 +819 893 820 +918 1908 821 +822 964 823 +824 825 701 +354 826 1497 +226 827 87 +828 829 800 +830 89 831 +832 833 296 +892 834 627 +835 862 265 +917 836 837 +838 699 1696 +839 840 417 +841 842 871 +843 844 155 +845 674 846 +847 848 1193 +849 916 850 +851 791 301 +1201 852 523 +853 1642 805 +92 1195 854 +1705 855 1857 +856 506 857 +858 736 859 +381 860 1451 +861 535 862 +870 454 863 +864 865 580 +866 816 867 +868 869 826 +870 871 1826 +872 1179 873 +874 1196 875 +876 877 938 +878 900 879 +880 881 557 +882 850 883 +884 761 885 +2074 886 1205 +1694 2089 887 +888 835 836 +889 278 996 +890 728 891 +892 801 893 +224 894 895 +1606 896 503 +897 668 898 +698 959 899 +900 295 760 +676 901 94 +902 859 418 +903 904 1581 +905 474 989 +906 742 1625 +907 908 813 +909 910 1634 +911 912 786 +913 914 987 +915 544 916 +917 966 775 +1688 1062 918 +919 771 920 +921 455 922 +923 847 924 +925 876 926 +927 1005 928 +929 815 930 +931 955 844 +932 933 914 +934 935 982 +936 937 353 +988 938 518 +336 1183 939 +940 953 941 +942 299 943 +944 898 1166 +945 1672 946 +821 1562 947 +948 756 2197 +949 950 855 +951 928 1713 +952 1518 953 +954 962 955 +1845 956 920 +861 957 1591 +1184 958 976 +906 1215 959 +960 961 591 +467 2076 962 +963 551 964 +1929 322 965 +966 1190 524 +967 438 794 +968 965 969 +1666 970 148 +713 1139 971 +972 755 973 +1118 974 765 +975 976 2057 +1897 977 1204 +978 717 979 +980 1239 981 +283 982 792 +1137 735 983 +984 985 824 +986 930 987 +988 989 933 +332 990 991 +1703 992 42 +993 849 904 +994 969 977 +995 996 1699 +669 997 998 +999 231 985 +1000 947 1721 +1001 905 1002 +1003 867 909 +245 1004 1005 +1006 1007 1213 +1008 1009 129 +1010 1011 10 +2251 1012 1226 +1906 1013 1014 +32 71 1015 +1016 1999 1099 +1017 546 2097 +135 1018 1142 +1019 1020 2191 +1021 897 1088 +869 785 1022 +2001 2081 1023 +1024 1065 1025 +1026 708 1027 +1028 1029 501 +56 1063 1030 +1031 1077 1032 +1033 1136 782 +1015 1034 1877 +1035 1036 1797 +1037 563 1138 +1038 1039 1125 +189 1040 1883 +2090 1041 1042 +1043 1117 667 +723 1529 1044 +435 1045 1046 +793 1743 1047 +1048 1025 1049 +1050 15 1727 +1072 1051 1052 +1055 1053 1054 +1013 1717 1055 +1056 1057 1181 +1058 1189 1059 +1060 840 1061 +1064 1062 1063 +18 1910 1064 +1065 1032 1066 +1067 1068 1306 +1069 2041 181 +1070 1188 1071 +980 1044 1072 +1073 1173 1074 +1075 1076 1133 +1077 1128 1078 +803 1079 1198 +1080 1209 1081 +1082 1047 1083 +1086 1084 1085 +547 2042 1086 +1087 1088 1185 +1089 1928 1090 +1091 1106 1092 +1095 2234 1093 +1094 1108 1095 +1156 1096 1097 +1102 1098 1099 +1022 1392 1100 +1101 984 1120 +1161 1102 2171 +1103 829 1192 +2080 1104 1733 +1105 1106 1207 +1109 1107 1108 +1216 1539 1109 +1112 125 1110 +1111 1433 1112 +1113 1901 1114 +1081 1115 1116 +1117 1151 203 +1118 1119 618 +1120 1121 1141 +1122 1123 1149 +1124 188 1119 +1125 173 1177 +2084 1126 270 +1127 1135 1128 +1129 1130 1148 +1131 1163 1132 +1133 942 1140 +1134 1135 1200 +1136 1137 1150 +1194 1138 1139 +212 1140 1154 +842 1141 1155 +1142 349 1160 +607 1143 311 +1144 2104 1777 +1145 510 1146 +990 1114 1147 +1148 1575 1202 +941 1149 1206 +1592 1150 1103 +1165 1505 1151 +979 992 1152 +2054 1083 1153 +1154 356 1018 +995 1090 1155 +1156 1152 1157 +1504 939 1158 +1159 1027 737 +1160 1516 1079 +1161 1912 1162 +1163 1167 1164 +1165 269 1170 +1166 1048 1167 +1168 1071 706 +405 613 1169 +255 1170 1076 +1056 1171 1172 +1173 1092 1174 +1178 1175 1176 +573 1177 1021 +1178 1098 2210 +1179 1116 449 +1180 1181 1485 +1644 1147 1182 +1183 1169 1184 +1185 272 1203 +1186 1187 1389 +512 1188 1045 +875 1700 1189 +1190 1061 1191 +1586 1192 1039 +1193 1172 819 +1354 1194 1195 +1196 1738 1197 +1697 1198 290 +1199 1200 1143 +1201 1202 1146 +631 1203 709 +1204 1059 277 +1205 1206 317 +490 1207 232 +1069 1208 1084 +1209 1132 1210 +1211 1074 1212 +193 1213 1186 +1214 1215 1681 +1216 1217 38 +1218 1785 1407 +1224 1219 1220 +1221 1222 2218 +1219 1277 1223 +1224 362 1225 +1226 1706 2095 +1231 1227 1228 +1372 1229 1300 +1232 1230 1231 +2245 1258 1232 +1260 1233 1234 +1235 1236 1347 +1360 1262 1237 +431 197 1238 +1187 1239 1214 +363 1756 1240 +1220 1241 1242 +2160 1243 319 +1377 1283 1244 +1245 1246 1533 +1241 2108 1247 +2047 1248 1249 +1751 741 1250 +1914 1251 1707 +1715 1252 1254 +1253 2101 1254 +1295 1255 600 +1279 1256 1255 +2247 1228 1257 +1838 1234 1258 +1237 1259 1260 +1238 1261 1262 +1249 1263 1264 +1841 1766 1265 +214 2164 1266 +1244 1267 1268 +1269 1317 2180 +1750 1768 1270 +1271 297 1272 +1275 1273 1274 +1225 1943 1275 +1221 2249 1276 +1277 1274 1278 +1279 1288 1280 +1281 1282 286 +1283 1247 1284 +740 1285 2106 +1282 1286 1629 +1287 1266 1773 +1288 1936 1771 +1289 96 3 +1770 1290 1291 +104 1292 1293 +1294 1295 1757 +1296 1297 1144 +1298 1276 1752 +1299 1278 1020 +1300 1301 1310 +1510 1302 1662 +1761 1303 1619 +182 1304 1014 +1305 1306 1439 +1794 1307 679 +1308 1309 51 +1391 1310 1402 +1311 1312 1712 +2099 1336 1313 +1905 1314 1315 +1316 1317 1476 +44 1686 1318 +1319 1320 1787 +1321 1438 1385 +1322 1263 1323 +1324 1325 1803 +1983 1326 1316 +116 1327 2028 +1328 1329 2017 +1330 1331 1568 +1791 1332 1345 +1333 2181 1326 +1334 1010 1723 +1335 1390 1336 +2184 1955 1337 +1338 1339 1412 +1340 1420 1358 +1341 1342 2068 +1343 1344 1417 +122 1345 1346 +453 1347 1762 +1348 1349 1243 +1833 1350 1348 +1416 1351 1356 +1346 1352 1630 +1353 1354 1436 +115 29 1355 +1356 2183 1340 +1380 1357 1358 +1359 1360 1837 +1361 2070 402 +1362 1363 753 +1290 1892 1364 +1365 1741 1208 +1366 1367 103 +1368 1369 1401 +1404 1370 410 +1371 1357 1372 +1373 1374 697 +1339 1375 1376 +1242 1377 1378 +1379 1380 2013 +1381 1382 1330 +1370 1400 1383 +1384 1397 1385 +1386 1387 1748 +1388 1389 1311 +1390 1415 1391 +2079 1392 1393 +1411 1394 1395 +432 1396 36 +2011 1397 2124 +2032 1398 1399 +1422 1399 1308 +1264 1400 2015 +6 1301 1401 +1402 1796 70 +1305 1403 1404 +1405 394 621 +1406 1407 1332 +1806 1408 1495 +1409 1410 1979 +1789 2237 1411 +1355 1412 1413 +1647 1414 1405 +1371 1415 1416 +1417 1418 2156 +2045 1419 1328 +2014 1420 1333 +1359 1951 1421 +1395 1422 143 +807 1423 1307 +1426 1424 1425 +2158 1926 1426 +1314 1427 1428 +1429 1430 1795 +1303 1431 1432 +1176 1433 1434 +1435 1436 1006 +1418 1437 1427 +1438 1439 1872 +1440 1441 1437 +80 1442 74 +1831 315 1443 +1443 1444 1824 +1445 1446 95 +1425 1447 1448 +1468 1449 1450 +1450 1451 1452 +131 1453 1454 +1454 1455 41 +1456 1457 1458 +1458 1807 139 +154 1459 1460 +403 1461 973 +1462 1463 1464 +1464 1782 2051 +406 1465 1466 +971 646 1467 +1467 1468 1469 +1470 1471 1472 +1472 46 1396 +1261 1473 1474 +1474 1475 284 +1376 1476 1804 +1447 1477 1470 +1956 1478 1479 +1479 1284 1285 +1350 2225 1480 +1480 1481 1708 +175 1482 1483 +1483 1484 2151 +1364 1485 1486 +239 1487 78 +287 1488 1489 +1489 1490 268 +710 1491 1492 +1492 456 1493 +1859 1494 688 +1329 1495 1008 +1512 339 1496 +1496 1497 1498 +1676 267 1499 +1499 1500 1501 +934 220 1502 +1502 1503 1504 +263 1505 1506 +1506 1507 1508 +1509 1510 2122 +968 108 1511 +1511 1512 1513 +84 874 1514 +1514 1515 1516 +1684 1122 1517 +1517 1518 1519 +1520 1521 1522 +1522 1523 258 +1524 1525 1526 +1526 1812 1457 +1531 1435 1527 +1527 1528 1529 +114 1353 1530 +1530 1531 1532 +1533 970 1309 +328 1453 1534 +1534 1535 1830 +20 1536 1537 +1538 1539 1050 +1537 1540 1541 +1542 1543 1235 +1544 1545 389 +280 1546 1547 +1547 1548 1549 +1550 1551 1552 +1552 1236 413 +442 1553 1554 +1948 1764 1555 +533 595 1556 +1556 1557 545 +391 1558 1559 +397 1560 1561 +1562 1563 1334 +408 1564 1565 +448 1566 1567 +1567 1568 1569 +1864 1570 1571 +1571 1572 1386 +590 1573 1574 +1574 555 1575 +1611 477 1576 +1576 1577 1578 +93 1579 1580 +1580 1581 585 +950 1582 1583 +1583 1564 380 +411 1584 1585 +1585 376 386 +1594 1586 1587 +1587 1588 1078 +481 1612 1589 +1589 1590 1591 +626 1592 1593 +1593 1594 1134 +751 1171 1595 +1595 1596 1597 +504 1598 608 +1599 1600 1995 +1596 1180 1601 +1601 1602 1603 +304 715 1604 +1604 1605 1606 +1057 1607 1608 +1608 1609 1674 +1003 550 1610 +1610 1611 1612 +190 1613 1614 +806 1615 1616 +1616 1542 494 +650 1617 1618 +1618 1550 1553 +2060 1619 1620 +1621 1622 1623 +1623 1624 694 +1036 1625 1639 +907 1626 1627 +1627 1628 1629 +149 1630 1631 +1631 1632 687 +1847 1633 1634 +1432 1635 1620 +217 1636 1637 +1637 1638 275 +747 1639 1640 +1640 1641 2146 +820 1642 1643 +1643 954 141 +310 1644 1645 +1645 1646 1647 +1648 1649 1650 +1650 1651 1654 +2072 1652 1653 +1653 1654 23 +1900 1291 1655 +1655 1656 1657 +425 1658 1659 +1659 1660 1919 +1578 91 1661 +1661 1662 1590 +1922 1663 1664 +1664 1665 1646 +1675 1666 2235 +848 1667 1668 +1668 1669 1607 +729 1670 1671 +1671 536 1672 +1656 1486 1673 +1673 1674 1675 +166 1676 1677 +1677 1678 233 +767 1493 1679 +1679 1680 1520 +1312 1681 1682 +1682 1683 951 +1388 1684 1685 +1685 1686 276 +1104 1719 1687 +1687 1688 1689 +138 1028 1689 +1690 1691 1692 +1692 1693 1663 +1041 1694 1695 +1695 1696 1000 +777 1697 1698 +1698 1699 1700 +2033 1431 1701 +1701 1702 891 +1638 1703 1704 +1704 1705 911 +1706 1707 97 +1708 1709 1714 +1538 1710 2232 +1711 908 1286 +1712 1713 1123 +1714 1715 683 +1029 1488 1716 +1716 1717 1718 +1030 1719 1720 +702 1721 1722 +1722 1723 60 +1724 1725 222 +1726 1727 1728 +1728 34 1110 +1729 1730 1731 +1731 1732 1211 +1733 1734 1023 +681 1735 1736 +1737 1738 1739 +1739 1740 1730 +1741 814 2092 +1742 81 137 +963 1743 1744 +1744 935 1158 +2077 1745 1746 +1746 1498 1100 +2030 1325 1747 +1748 1749 368 +2216 1750 1751 +1019 1752 2248 +1753 440 1271 +1268 1754 1755 +1755 1756 1378 +2147 1757 752 +2109 1758 1759 +738 1760 1761 +1762 1763 1939 +1945 1952 1764 +1765 1766 1240 +1767 1768 1252 +1383 1322 1769 +1917 1923 1770 +1771 2112 1772 +1772 1773 1774 +2223 1775 1776 +1776 1777 1778 +1779 211 198 +1848 1780 625 +684 1781 1782 +2153 1783 163 +1 2019 1784 +361 1785 1786 +1786 1787 1341 +2007 2003 1788 +1788 1789 1790 +1880 2140 1791 +1218 2230 1792 +1792 1793 1319 +1381 1566 1794 +2209 1795 1781 +153 1796 1797 +1870 1798 1753 +1799 1800 1327 +1935 1801 1802 +1802 1803 1800 +1804 1269 2087 +1805 1806 1419 +1807 1808 1809 +1809 1810 54 +5 1297 1811 +1808 1812 1813 +1813 1814 1815 +1977 1338 1816 +1816 1817 1409 +1854 1971 1818 +1818 1819 1820 +1821 1822 1823 +1823 1824 1429 +822 921 1825 +1825 1826 596 +45 1230 1827 +1827 1828 1829 +1830 1831 1832 +1832 1822 145 +2113 1833 1834 +2244 1835 1836 +1836 1837 1838 +430 1944 1839 +1839 1840 1841 +1293 1842 1843 +1843 1844 367 +888 1845 1846 +1846 1847 726 +2166 1848 1849 +1849 575 1256 +1545 1850 1851 +1851 1852 26 +1853 1854 1855 +1855 2137 111 +422 912 1856 +1856 1857 1858 +1866 1859 1860 +1860 1861 1873 +1861 1862 1863 +1863 1864 369 +1292 1494 1865 +1865 1866 1867 +119 2114 1868 +1868 1869 1870 +246 2036 1871 +1871 1872 560 +1599 1873 1874 +1874 1875 1876 +748 1877 1878 +1878 1879 1374 +2134 1880 1881 +1881 1882 1974 +1883 1884 1885 +1885 1876 1561 +972 1461 1886 +1886 1887 1888 +707 1889 1890 +1890 1891 221 +1602 1892 1893 +1893 1894 2129 +1903 946 1895 +1895 1896 1897 +1898 1918 1899 +1899 1900 1070 +424 1901 1902 +1902 1903 1491 +1904 1905 2136 +1906 1251 1907 +1563 1908 1909 +1909 1910 1011 +1911 1912 1913 +1913 1914 1012 +1129 1915 1916 +1916 1917 1918 +1919 1920 1921 +1921 1922 1182 +1894 1923 1924 +1924 1925 1926 +185 597 1927 +1927 1928 1929 +1930 1931 1932 +1932 1933 1967 +1934 1935 2088 +1775 1936 1937 +1937 1294 1296 +2021 444 1938 +1938 1939 1940 +1950 1941 1942 +1942 1943 1944 +1954 1945 1946 +1946 1947 1835 +1947 1948 1949 +1949 1950 1951 +2213 1952 1953 +1953 1954 1955 +1603 1956 1957 +1957 1767 2149 +1958 1959 1960 +1960 1961 1375 +1373 1888 1962 +1962 1963 1964 +1440 1965 1966 +1966 1967 1968 +1779 1765 1969 +1969 1970 1971 +1869 2163 1972 +1972 1973 285 +2206 1974 1975 +1975 1245 1398 +2008 1976 2138 +151 1977 1978 +1978 1979 2004 +1980 1981 1982 +1982 1983 1961 +1984 1985 1217 +2038 1986 1987 +1987 1988 2241 +1318 1519 1989 +1989 1990 1227 +1963 2073 1991 +1991 1992 1993 +27 1867 1994 +1994 1995 1996 +1790 1997 1998 +1998 58 1976 +2173 1999 2000 +2000 2001 2002 +2003 2004 2005 +2005 2006 2239 +2007 2008 2009 +2009 2010 152 +1981 2011 2012 +2012 2013 2014 +1959 2015 2016 +2016 1403 553 +1248 2017 2018 +2018 2019 2025 +2020 812 1361 +2021 2022 2023 +2023 2024 1387 +1323 2025 2026 +2026 2027 409 +2229 2028 2029 +2029 2030 1793 +2031 2032 1394 +2033 2034 2035 +2035 1624 1635 +1384 2036 2037 +2037 927 1369 +2187 2038 2039 +2039 2040 30 +2041 2042 2043 +2043 98 1304 +2044 2045 2046 +2046 2047 2010 +1446 2048 2049 +2049 2050 22 +324 2051 2052 +2052 1430 1444 +1726 124 2053 +2054 2055 2056 +2056 1115 632 +1096 2057 2058 +2058 581 2059 +2060 1622 2061 +2061 2062 739 +24 2063 2064 +2064 2065 2066 +2067 2068 2069 +2069 64 1632 +1887 2070 2071 +2071 2072 2073 +2074 787 2075 +2075 2076 878 +2055 2077 2078 +2078 2079 450 +2080 2081 2082 +2082 86 1720 +2083 2084 2085 +2085 1891 1711 +1799 1413 2086 +2086 2087 2088 +2089 2090 2091 +2091 39 2175 +2092 2093 2094 +2094 2195 1085 +2233 2095 2096 +2096 2097 1093 +52 1335 2098 +2098 2099 1324 +2100 2101 2102 +2102 2186 1257 +1481 1778 2103 +2103 2104 2105 +1250 2106 2107 +2107 2108 2222 +2109 2110 2111 +2111 2112 1724 +2226 2113 2114 +1758 2115 2116 +2116 2117 201 +1973 4 2118 +2118 2119 1281 +2120 1760 1365 +1287 2110 2121 +2121 2122 90 +1229 1379 2123 +2123 2124 1368 +2170 2125 2126 +2126 2127 695 +2128 2120 1046 +1478 2129 2130 +2130 2131 1267 +1986 2132 2133 +2133 2134 2204 +1820 1736 2135 +2135 2136 2137 +2044 2138 2139 +2139 57 1805 +2140 2132 2141 +2141 2142 1406 +2125 2143 2144 +2144 2145 2146 +2147 1597 2148 +2148 2149 1811 +2150 2151 2152 +2152 2153 1343 +1455 327 2154 +2154 2155 68 +2150 2156 2157 +2157 1904 176 +2131 2158 2159 +2159 294 325 +2160 2161 2162 +2162 2163 1834 +1774 2164 2165 +2165 2166 1280 +2167 2168 2169 +2169 2170 884 +1907 2171 2172 +2172 2173 1718 +2174 2175 2176 +2176 1985 2093 +1754 2177 2178 +2178 2179 1970 +2180 2181 2182 +2182 2183 1934 +2215 2184 2185 +2185 2186 1222 +2187 2188 2227 +2203 2189 2190 +2190 2243 1988 +2212 2191 2192 +2192 375 1555 +1984 1094 2193 +2193 2194 2195 +2020 885 2196 +2196 2197 2198 +2202 2040 2199 +1410 2200 2201 +2201 2202 2006 +2203 2204 2205 +2205 2206 2031 +358 1484 2207 +2207 2208 2209 +2053 2210 2211 +2211 1162 1710 +2212 2213 2214 +2214 2215 1298 +2216 2220 2217 +2217 2218 1253 +2219 2220 2221 +2221 2222 1223 +1725 2223 2224 +2224 2225 2226 +2142 2227 2228 +2228 2229 2230 +2231 2232 1911 +2252 2233 2234 +1657 2235 2236 +2236 1702 2128 +2189 2237 2238 +2238 2239 2240 +2199 2241 2242 +2242 2243 2240 +2244 2245 2246 +2246 2247 1337 +2248 2249 2250 +2250 2219 1299 +2251 2252 2253 +2253 1107 2231 diff --git a/SkeinPyPy_NewUI/models/box.obj b/SkeinPyPy_NewUI/models/box.obj new file mode 100644 index 0000000..1e1c785 --- /dev/null +++ b/SkeinPyPy_NewUI/models/box.obj @@ -0,0 +1,79 @@ +# ----------------- +# Start of obj file +g Box01 +mtllib box.mat +usemtl box +v -62.0579 -41.4791 0.0 +v 58.8424 -41.4791 0.0 +v -62.0579 22.1865 0.0 +v 58.8424 22.1865 0.0 +v -62.0579 -41.4791 39.8714 +v 58.8424 -41.4791 39.8714 +v -62.0579 22.1865 39.8714 +v 58.8424 22.1865 39.8714 + +vt 0.843206 0.405444 0.000499517 +vt 0.482802 0.71377 0.9995 +vt 0.478066 0.404023 0.000499636 +vt 0.482802 0.716612 0.9995 +vt 0.841627 0.688332 0.000499517 +vt 0.482013 0.981029 0.9995 +vt 0.480434 0.688332 0.000499636 +vt 0.485959 0.978188 0.9995 +vt 0.450102 0.00618343 0.000499547 +vt 0.45247 0.509304 0.000499547 +vt 0.000499517 0.512146 0.000499547 +vt 0.000499517 0.512146 0.000499547 +vt -0.0010791 0.00618302 0.000499547 +vt 0.450102 0.00618343 0.000499547 +vt 0.000499517 0.512009 0.9995 +vt 0.450891 0.510588 0.9995 +vt 0.45247 0.995237 0.9995 +vt 0.45247 0.996658 0.9995 +vt 0.000499636 0.9995 0.9995 +vt 0.000499517 0.51343 0.9995 +vt 0.478855 0.405444 0.000500023 +vt 0.841627 0.408286 0.000499576 +vt 0.83847 0.688332 0.000499576 +vt 0.83847 0.688332 0.000499576 +vt 0.477276 0.694016 0.000500023 +vt 0.478855 0.405444 0.000500023 +vt 0.482802 0.71377 0.9995 +vt 0.845574 0.71377 0.999501 +vt 0.844784 0.976767 0.999501 +vt 0.844784 0.976767 0.999501 +vt 0.482802 0.716612 0.9995 +vt 0.842417 0.710929 0.9995 +vt 0.843995 0.975346 0.9995 +vt 0.843995 0.975346 0.9995 +vt 0.478066 0.404023 0.000499636 +vt 0.841627 0.688332 0.000499517 + +vn 0.0 0.0 -1.0 +vn 0.0 0.0 -1.0 +vn 0.0 0.0 1.0 +vn 0.0 0.0 1.0 +vn 0.0 -1.0 0.0 +vn 0.0 -1.0 0.0 +vn 1.0 0.0 0.0 +vn 1.0 0.0 0.0 +vn 0.0 1.0 0.0 +vn 0.0 1.0 0.0 +vn -1.0 0.0 0.0 +vn -1.0 0.0 0.0 + +f 1/9/1 3/10/1 4/11/1 +f 4/12/2 2/13/2 1/14/2 +f 5/15/3 6/16/3 8/17/3 +f 8/18/4 7/19/4 5/20/4 +f 1/21/5 2/22/5 6/23/5 +f 6/24/6 5/25/6 1/26/6 +f 2/27/7 4/28/7 8/29/7 +f 8/30/8 6/6/8 2/2/8 +f 4/31/9 3/32/9 7/33/9 +f 7/34/10 8/8/10 4/4/10 +f 3/35/11 1/1/11 5/36/11 +f 5/5/12 7/7/12 3/3/12 + +# end of obj file +# --------------- diff --git a/SkeinPyPy_NewUI/models/inkscape_star.svg b/SkeinPyPy_NewUI/models/inkscape_star.svg new file mode 100644 index 0000000..8d99449 --- /dev/null +++ b/SkeinPyPy_NewUI/models/inkscape_star.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/SkeinPyPy_NewUI/newui/__init__.py b/SkeinPyPy_NewUI/newui/__init__.py new file mode 100644 index 0000000..1638033 --- /dev/null +++ b/SkeinPyPy_NewUI/newui/__init__.py @@ -0,0 +1,13 @@ +""" +This page is in the table of contents. +This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +""" +import os +import sys + +numberOfLevelsDeepInPackageHierarchy = 1 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/newui/mainWindow.py b/SkeinPyPy_NewUI/newui/mainWindow.py new file mode 100644 index 0000000..fa47d24 --- /dev/null +++ b/SkeinPyPy_NewUI/newui/mainWindow.py @@ -0,0 +1,53 @@ +from __future__ import absolute_import +import __init__ + +import wx + +from newui import preview3d + +def main(): + app = wx.App(False) + mainWindow() + app.MainLoop() + +class mainWindow(wx.Frame): + "Main user interface window" + def __init__(self): + super(mainWindow, self).__init__(None, title='SkeinPyPy') + menubar = wx.MenuBar() + fileMenu = wx.Menu() + fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application') + menubar.Append(fileMenu, '&File') + menubar.Append(wx.Menu(), 'Expert') + self.SetMenuBar(menubar) + + p = wx.Panel(self) + nb = wx.Notebook(p, size=(400,10)) + + printConfig = wx.Panel(nb); + wx.StaticText(printConfig, -1, "Test", (20,20)) + nb.AddPage(printConfig, "Print") + nb.AddPage(wx.Panel(nb), "Machine") + nb.AddPage(wx.Panel(nb), "Start/End-GCode") + + p3d = preview3d.myGLCanvas(p) + + loadButton = wx.Button(p, 1, 'Load STL') + + sizer = wx.GridBagSizer() + sizer.Add(nb, (0,0), span=(2,1), flag=wx.EXPAND) + sizer.Add(p3d, (0,1), flag=wx.EXPAND) + sizer.Add(loadButton, (1,1)) + sizer.AddGrowableCol(1) + sizer.AddGrowableRow(0) + p.SetSizer(sizer) + + self.Bind(wx.EVT_MENU, self.OnQuit, fitem) + + self.SetSize((800, 400)) + self.Centre() + self.Show(True) + + def OnQuit(self, e): + self.Close() + diff --git a/SkeinPyPy_NewUI/newui/preview3d.py b/SkeinPyPy_NewUI/newui/preview3d.py new file mode 100644 index 0000000..c8f50aa --- /dev/null +++ b/SkeinPyPy_NewUI/newui/preview3d.py @@ -0,0 +1,151 @@ +from wxPython.glcanvas import wxGLCanvas +from wxPython.wx import * +from OpenGL.GLUT import * +from OpenGL.GLU import * +from OpenGL.GL import * +import sys,math + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 + +class myGLCanvas(wxGLCanvas): + def __init__(self, parent): + wxGLCanvas.__init__(self, parent,-1) + EVT_PAINT(self, self.OnPaint) + EVT_SIZE(self, self.OnSize) + EVT_ERASE_BACKGROUND(self, self.OnEraseBackground) + EVT_MOTION(self, self.OnMouseMotion) + self.init = 0 + self.triangleMesh = None + self.yaw = 0 + self.pitch = 80 + self.zoom = 150 + self.machineSize = Vector3(210, 210, 200) + self.machineCenter = Vector3(100, 100, 0) + return + + def loadFile(self, filename): + self.triangleMesh = fabmetheus_interpret.getCarving(filename) + minZ = self.triangleMesh.getMinimumZ() + min = self.triangleMesh.getCarveCornerMinimum() + max = self.triangleMesh.getCarveCornerMaximum() + + for v in self.triangleMesh.vertexes: + v.z -= minZ + v.x -= min.x + (max.x - min.x) / 2 + v.y -= min.y + (max.y - min.y) / 2 + v.x += self.machineCenter.x + v.y += self.machineCenter.y + + def OnMouseMotion(self,e): + if e.Dragging() and e.LeftIsDown(): + self.yaw += e.GetX() - self.oldX + self.pitch += e.GetY() - self.oldY + if self.pitch > 170: + self.pitch = 170 + if self.pitch < 10: + self.pitch = 10 + if e.Dragging() and e.RightIsDown(): + self.zoom += e.GetY() - self.oldY + self.oldX = e.GetX() + self.oldY = e.GetY() + self.Refresh() + + def OnEraseBackground(self,event): + pass + + def OnSize(self,event): + self.Refresh() + return + + def OnPaint(self,event): + dc = wxPaintDC(self) + self.SetCurrent() + self.InitGL() + self.OnDraw() + return + + def OnDraw(self): + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) + + glTranslate(-self.machineCenter.x, -self.machineCenter.y, 0) + + if self.triangleMesh != None: + glBegin(GL_TRIANGLES) + for face in self.triangleMesh.faces: + v1 = self.triangleMesh.vertexes[face.vertexIndexes[0]] + v2 = self.triangleMesh.vertexes[face.vertexIndexes[1]] + v3 = self.triangleMesh.vertexes[face.vertexIndexes[2]] + normal = (v2 - v1).cross(v3 - v1) + normal.normalize() + glNormal3f(normal.x, normal.y, normal.z) + glVertex3f(v1.x, v1.y, v1.z) + glVertex3f(v2.x, v2.y, v2.z) + glVertex3f(v3.x, v3.y, v3.z) + glEnd() + + glLineWidth(4) + glDisable(GL_LIGHTING) + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0, 0) + glVertex3f(self.machineSize.x, 0, 0) + glVertex3f(self.machineSize.x, self.machineSize.y, 0) + glVertex3f(0, self.machineSize.y, 0) + glEnd() + glLineWidth(2) + glBegin(GL_LINES) + for i in xrange(0, self.machineSize.x, 10): + glVertex3f(i, 0, 0) + glVertex3f(i, self.machineSize.y, 0) + for i in xrange(0, self.machineSize.y, 10): + glVertex3f(0, i, 0) + glVertex3f(self.machineSize.x, i, 0) + glEnd() + glLineWidth(1) + glBegin(GL_LINE_LOOP) + glVertex3f(0, 0, self.machineSize.z) + glVertex3f(self.machineSize.x, 0, self.machineSize.z) + glVertex3f(self.machineSize.x, self.machineSize.y, self.machineSize.z) + glVertex3f(0, self.machineSize.y, self.machineSize.z) + glEnd() + glBegin(GL_LINES) + glVertex3f(0, 0, 0) + glVertex3f(0, 0, self.machineSize.z) + glVertex3f(self.machineSize.x, 0, 0) + glVertex3f(self.machineSize.x, 0, self.machineSize.z) + glVertex3f(self.machineSize.x, self.machineSize.y, 0) + glVertex3f(self.machineSize.x, self.machineSize.y, self.machineSize.z) + glVertex3f(0, self.machineSize.y, 0) + glVertex3f(0, self.machineSize.y, self.machineSize.z) + glEnd() + self.SwapBuffers() + return + + def InitGL(self): + # set viewing projection + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + glViewport(0,0, self.GetSize().GetWidth(), self.GetSize().GetHeight()) + + glLightfv(GL_LIGHT0, GL_DIFFUSE, [1.0, 0.8, 0.6, 1.0]) + glLightfv(GL_LIGHT0, GL_POSITION, [1.0, 1.0, 1.0, 0.0]) + glLightfv(GL_LIGHT0, GL_AMBIENT, [0.2, 0.2, 0.2, 0.0]) + + glEnable(GL_LIGHTING) + glEnable(GL_LIGHT0) + glEnable(GL_DEPTH_TEST) + glClearColor(0.0, 0.0, 0.0, 1.0) + glClearDepth(1.0) + + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + gluPerspective(90.0, float(self.GetSize().GetWidth()) / float(self.GetSize().GetHeight()), 1.0, 1000.0) + + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + glTranslate(0,0,-self.zoom) + glRotate(-self.pitch, 1,0,0) + glRotate(self.yaw, 0,0,1) + #glRotate(90, 1,0,0) + + return diff --git a/SkeinPyPy_NewUI/skeinforge_application/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/__init__.py new file mode 100644 index 0000000..bdac25d --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 1 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/end.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/end.gcode new file mode 100644 index 0000000..a981292 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/end.gcode @@ -0,0 +1,10 @@ +(start of end.gcode) +M104 S0 (extruder heat off) +M106 (fan on) +G91 (relative positioning) +G1 Z+10 E-5 F400 (move Z up a bit and retract filament by 5mm) +G1 X-20 Y-20 F1500 (move X and Y over a bit) +M84 (steppers off) +G90 (absolute positioning) +(end of end.gcode) + diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_end.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_end.gcode new file mode 100644 index 0000000..730c606 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_end.gcode @@ -0,0 +1,2 @@ +(this is a sample cool end file, it must be renamed cool_end.gcode for skeinforge to recognize it) +M107 diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_start.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_start.gcode new file mode 100644 index 0000000..90285c7 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_cool_start.gcode @@ -0,0 +1,2 @@ +(this is a sample cool start file, it must be renamed cool_start.gcode for skeinforge to recognize it) +M106 diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_end.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_end.gcode new file mode 100644 index 0000000..86dc43b --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_end.gcode @@ -0,0 +1,2 @@ +(this is a sample gcode end file, it must be renamed end.gcode for skeinforge to recognize it) +M2 diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_home.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_home.gcode new file mode 100644 index 0000000..a089a31 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_home.gcode @@ -0,0 +1,5 @@ +(this is a sample gcode homing file, it must be renamed homing.gcode for skeinforge to recognize it) +G1 X-250.0 +G92 X0 ;set x 0 +G1 Y-250.0 +G92 Y0 ;set y 0 diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace.csv b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace.csv new file mode 100644 index 0000000..dbcbd1b --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace.csv @@ -0,0 +1,3 @@ +M101 +M103 + diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace_M108.csv b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace_M108.csv new file mode 100644 index 0000000..4cac815 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_replace_M108.csv @@ -0,0 +1,2 @@ +M108 S M108 P +M113 S M108 S diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_start.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_start.gcode new file mode 100644 index 0000000..eeafa85 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_start.gcode @@ -0,0 +1,3 @@ +(This is a sample gcode start file, it must be renamed start.gcode for skeinforge to recognize it. Also, to remove confusion this comment line should be deleted.) +G28 +M140 S diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_end.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_end.gcode new file mode 100644 index 0000000..63315e9 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_end.gcode @@ -0,0 +1,2 @@ +(this is a sample support end file, it must be renamed support_end.gcode for skeinforge to recognize it) + diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_start.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_start.gcode new file mode 100644 index 0000000..04392f0 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/example_support_start.gcode @@ -0,0 +1,2 @@ +(this is a sample support start file, it must be renamed support_start.gcode for skeinforge to recognize it) + diff --git a/SkeinPyPy_NewUI/skeinforge_application/alterations/start.gcode b/SkeinPyPy_NewUI/skeinforge_application/alterations/start.gcode new file mode 100644 index 0000000..0dca1c4 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/alterations/start.gcode @@ -0,0 +1,31 @@ +(start of start.txt) +M92 E926.5 (the number of extruder steps to take in 1mm of filament) +G21 (metric values) +G21 +G21 (all the extra G21 commands are comments - skeinforge eats lines without a gcode) +G21 +G90 (absolute positioning) +G21 +G28 X0 Y0 (move X/Y to min endstops) +G28 Z0 (move Z to min endstops) +G21 +G21 ( if your prints start too high, try changing the Z0.0 below ) +G21 ( to Z1.0 - the number after the Z is the actual, physical ) +G21 ( height of the nozzle in mm. This can take some messing around ) +G21 ( with to get just right... ) +G21 +G92 X0 Y0 Z0 E0 (reset software position to front/left/z=0.0) +G21 +G1 Z15.0 F400 (move the platform down 15mm) +G92 E0 (zero the extruded length) +G21 +G1 F75 E5 (extrude 5mm of feed stock) +G1 F75 E3.5 (reverse feed stock by 1.5mm) +G92 E0 (zero the extruded length again) +G21 +M1 (Clean the nozzle then press YES to continue...) +G21 +G1 X100 Y100 F3500 (go to the middle of the platform) +G1 Z0.0 F400 (back to Z=0 and start the print!) +(end of start.txt) + diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/__init__.py new file mode 100644 index 0000000..2dc8ddc --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 2 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze.py new file mode 100644 index 0000000..99604b2 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze.py @@ -0,0 +1,60 @@ +""" +This page is in the table of contents. +Analyze is a script to access the plugins which analyze a gcode file. + +The plugin buttons which are commonly used are bolded and the ones which are rarely used have normal font weight. + +==Gcodes== +An explanation of the gcodes is at: +http://reprap.org/bin/view/Main/Arduino_GCode_Interpreter + +and at: +http://reprap.org/bin/view/Main/MCodeReference + +A gode example is at: +http://forums.reprap.org/file.php?12,file=565 + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_analyze +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addToMenu(master, menu, repository, window): + "Add a tool plugin menu." + analyzeFilePath = archive.getSkeinforgePluginsPath('analyze.py') + pluginsDirectoryPath = skeinforge_analyze.getPluginsDirectoryPath() + settings.addPluginsParentToMenu(pluginsDirectoryPath, menu, analyzeFilePath, skeinforge_analyze.getPluginFileNames()) + +def getNewRepository(): + 'Get new repository.' + return skeinforge_analyze.AnalyzeRepository() + +def writeOutput(fileName): + "Analyze a gcode file." + repository = getNewRepository() + repository.fileNameInput.value = fileName + repository.execute() + settings.startMainLoopFromConstructor(repository) + + +def main(): + "Display the analyze dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/statistic.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/statistic.py new file mode 100644 index 0000000..dcc51d6 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/statistic.py @@ -0,0 +1,404 @@ +""" +This page is in the table of contents. +Statistic is an extremely valuable analyze plugin to print and/or save the statistics of the generated gcode. + +The statistic manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Statistic + +==Operation== +The default 'Activate Statistic' checkbox is on. When it is on, the functions described below will work when called from the skeinforge toolchain, when it is off, the functions will not be called from the toolchain. The functions will still be called, whether or not the 'Activate Statistic' checkbox is on, when statistic is run directly. + +==Settings== +===Extrusion Diameter over Thickness=== +Default is 1.25. + +The 'Extrusion Diameter over Thickness is the ratio of the extrusion diameter over the layer height, the default is 1.25. The extrusion fill density ratio that is printed to the console, ( it is derived quantity not a parameter ) is the area of the extrusion diameter over the extrusion width over the layer height. Assuming the extrusion diameter is correct, a high value means the filament will be packed tightly, and the object will be almost as dense as the filament. If the fill density ratio is too high, there could be too little room for the filament, and the extruder will end up plowing through the extra filament. A low fill density ratio means the filaments will be far away from each other, the object will be leaky and light. The fill density ratio with the default extrusion settings is around 0.68. + +===Print Statistics=== +Default is on. + +When the 'Print Statistics' checkbox is on, the statistics will be printed to the console. + +===Save Statistics=== +Default is off. + +When the 'Save Statistics' checkbox is on, the statistics will be saved as a .txt file. + +==Gcodes== +An explanation of the gcodes is at: +http://reprap.org/bin/view/Main/Arduino_GCode_Interpreter + +and at: +http://reprap.org/bin/view/Main/MCodeReference + +A gode example is at: +http://forums.reprap.org/file.php?12,file=565 + +==Examples== +Below are examples of statistic being used. These examples are run in a terminal in the folder which contains Screw Holder_penultimate.gcode and statistic.py. The 'Save Statistics' checkbox is selected. + +> python statistic.py +This brings up the statistic dialog. + +> python statistic.py Screw Holder_penultimate.gcode +Statistics are being generated for the file /home/enrique/Desktop/backup/babbleold/script/reprap/fabmetheus/models/Screw Holder_penultimate.gcode + +Cost +Machine time cost is 0.31$. +Material cost is 0.2$. +Total cost is 0.51$. + +Extent +X axis extrusion starts at 61 mm and ends at 127 mm, for a width of 65 mm. +Y axis extrusion starts at 81 mm and ends at 127 mm, for a depth of 45 mm. +Z axis extrusion starts at 0 mm and ends at 15 mm, for a height of 15 mm. + +Extruder +Build time is 18 minutes 47 seconds. +Distance extruded is 46558.4 mm. +Distance traveled is 58503.3 mm. +Extruder speed is 50.0 +Extruder was extruding 79.6 percent of the time. +Extruder was toggled 1688 times. +Operating flow rate is 9.8 mm3/s. +Feed rate average is 51.9 mm/s, (3113.8 mm/min). + +Filament +Cross section area is 0.2 mm2. +Extrusion diameter is 0.5 mm. +Extrusion fill density ratio is 0.68 + +Material +Mass extruded is 9.8 grams. +Volume extruded is 9.1 cc. + +Meta +Text has 33738 lines and a size of 1239.0 KB. +Version is 11.09.28 + +Procedures +carve +bottom +preface +inset +fill +multiply +speed +temperature +raft +skirt +dimension +bookend + +Profile +UM-PLA-HighQuality + +Slice +Edge width is 0.72 mm. +Layer height is 0.4 mm. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return StatisticRepository() + +def getWindowAnalyzeFile(fileName): + "Write statistics for a gcode file." + return getWindowAnalyzeFileGivenText( fileName, archive.getFileText(fileName) ) + +def getWindowAnalyzeFileGivenText( fileName, gcodeText, repository=None): + "Write statistics for a gcode file." + print('') + print('') + print('Statistics are being generated for the file ' + archive.getSummarizedFileName(fileName) ) + if repository == None: + repository = settings.getReadRepository( StatisticRepository() ) + skein = StatisticSkein() + statisticGcode = skein.getCraftedGcode(gcodeText, repository) + if repository.printStatistics.value: + print(statisticGcode) + if repository.saveStatistics.value: + archive.writeFileMessageEnd('.txt', fileName, statisticGcode, 'The statistics file is saved as ') + +def writeOutput(fileName, fileNamePenultimate, fileNameSuffix, filePenultimateWritten, gcodeText=''): + "Write statistics for a skeinforge gcode file, if 'Write Statistics File for Skeinforge Chain' is selected." + repository = settings.getReadRepository( StatisticRepository() ) + if gcodeText == '': + gcodeText = archive.getFileText( fileNameSuffix ) + if repository.activateStatistic.value: + getWindowAnalyzeFileGivenText( fileNameSuffix, gcodeText, repository ) + + +class StatisticRepository: + "A class to handle the statistics settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.analyze_plugins.statistic.html', self) + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Statistic') + self.activateStatistic = settings.BooleanSetting().getFromValue('Activate Statistic', self, True ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Cost -', self ) + self.machineTime = settings.FloatSpin().getFromValue( 0.0, 'Machine Time ($/hour):', self, 5.0, 1.0 ) + self.material = settings.FloatSpin().getFromValue( 0.0, 'Material ($/kg):', self, 40.0, 20.0 ) + settings.LabelSeparator().getFromRepository(self) + self.density = settings.FloatSpin().getFromValue( 500.0, 'Density (kg/m3):', self, 2000.0, 930.0 ) + self.extrusionDiameterOverThickness = settings.FloatSpin().getFromValue( 1.0, 'Extrusion Diameter over Thickness (ratio):', self, 1.5, 1.25 ) + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File to Generate Statistics for', self, '') + self.printStatistics = settings.BooleanSetting().getFromValue('Print Statistics', self, True ) + self.saveStatistics = settings.BooleanSetting().getFromValue('Save Statistics', self, False ) + self.executeTitle = 'Generate Statistics' + + def execute(self): + "Write button has been clicked." + fileNames = skeinforge_polyfile.getFileOrGcodeDirectory( self.fileNameInput.value, self.fileNameInput.wasCancelled, ['_comment'] ) + for fileName in fileNames: + getWindowAnalyzeFile(fileName) + + +class StatisticSkein: + "A class to get statistics for a gcode skein." + def __init__(self): + self.extrusionDiameter = None + self.oldLocation = None + self.operatingFeedRatePerSecond = None + self.output = cStringIO.StringIO() + self.profileName = None + self.version = None + + def addLine(self, line): + "Add a line of text and a newline to the output." + self.output.write(line + '\n') + + def addToPath(self, location): + "Add a point to travel and maybe extrusion." + if self.oldLocation != None: + travel = location.distance( self.oldLocation ) + if self.feedRateMinute > 0.0: + self.totalBuildTime += 60.0 * travel / self.feedRateMinute + self.totalDistanceTraveled += travel + if self.extruderActive: + self.totalDistanceExtruded += travel + self.cornerMaximum.maximize(location) + self.cornerMinimum.minimize(location) + self.oldLocation = location + + def extruderSet( self, active ): + "Maybe increment the number of times the extruder was toggled." + if self.extruderActive != active: + self.extruderToggled += 1 + self.extruderActive = active + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the statistics." + self.absoluteEdgeWidth = 0.4 + self.characters = 0 + self.cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0) + self.cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0) + self.extruderActive = False + self.extruderSpeed = None + self.extruderToggled = 0 + self.feedRateMinute = 600.0 + self.layerHeight = 0.4 + self.numberOfLines = 0 + self.procedures = [] + self.repository = repository + self.totalBuildTime = 0.0 + self.totalDistanceExtruded = 0.0 + self.totalDistanceTraveled = 0.0 + lines = archive.getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + averageFeedRate = self.totalDistanceTraveled / self.totalBuildTime + self.characters += self.numberOfLines + kilobytes = round( self.characters / 1024.0 ) + halfEdgeWidth = 0.5 * self.absoluteEdgeWidth + halfExtrusionCorner = Vector3( halfEdgeWidth, halfEdgeWidth, halfEdgeWidth ) + self.cornerMaximum += halfExtrusionCorner + self.cornerMinimum -= halfExtrusionCorner + extent = self.cornerMaximum - self.cornerMinimum + roundedHigh = euclidean.getRoundedPoint( self.cornerMaximum ) + roundedLow = euclidean.getRoundedPoint( self.cornerMinimum ) + roundedExtent = euclidean.getRoundedPoint( extent ) + axisString = " axis extrusion starts at " + crossSectionArea = 0.9 * self.absoluteEdgeWidth * self.layerHeight # 0.9 if from the typical fill density + if self.extrusionDiameter != None: + crossSectionArea = math.pi / 4.0 * self.extrusionDiameter * self.extrusionDiameter + volumeExtruded = 0.001 * crossSectionArea * self.totalDistanceExtruded + mass = volumeExtruded / repository.density.value + machineTimeCost = repository.machineTime.value * self.totalBuildTime / 3600.0 + materialCost = repository.material.value * mass + self.addLine(' ') + self.addLine('Cost') + self.addLine( "Machine time cost is %s$." % round( machineTimeCost, 2 ) ) + self.addLine( "Material cost is %s$." % round( materialCost, 2 ) ) + self.addLine( "Total cost is %s$." % round( machineTimeCost + materialCost, 2 ) ) + self.addLine(' ') + self.addLine('Extent') + self.addLine( "X%s%s mm and ends at %s mm, for a width of %s mm." % ( axisString, int( roundedLow.x ), int( roundedHigh.x ), int( extent.x ) ) ) + self.addLine( "Y%s%s mm and ends at %s mm, for a depth of %s mm." % ( axisString, int( roundedLow.y ), int( roundedHigh.y ), int( extent.y ) ) ) + self.addLine( "Z%s%s mm and ends at %s mm, for a height of %s mm." % ( axisString, int( roundedLow.z ), int( roundedHigh.z ), int( extent.z ) ) ) + self.addLine(' ') + self.addLine('Extruder') + self.addLine( "Build time is %s." % euclidean.getDurationString( self.totalBuildTime ) ) + self.addLine( "Distance extruded is %s mm." % euclidean.getThreeSignificantFigures( self.totalDistanceExtruded ) ) + self.addLine( "Distance traveled is %s mm." % euclidean.getThreeSignificantFigures( self.totalDistanceTraveled ) ) + if self.extruderSpeed != None: + self.addLine( "Extruder speed is %s" % euclidean.getThreeSignificantFigures( self.extruderSpeed ) ) + self.addLine( "Extruder was extruding %s percent of the time." % euclidean.getThreeSignificantFigures( 100.0 * self.totalDistanceExtruded / self.totalDistanceTraveled ) ) + self.addLine( "Extruder was toggled %s times." % self.extruderToggled ) + if self.operatingFeedRatePerSecond != None: + flowRate = crossSectionArea * self.operatingFeedRatePerSecond + self.addLine( "Operating flow rate is %s mm3/s." % euclidean.getThreeSignificantFigures( flowRate ) ) + self.addLine( "Feed rate average is %s mm/s, (%s mm/min)." % ( euclidean.getThreeSignificantFigures( averageFeedRate ), euclidean.getThreeSignificantFigures( 60.0 * averageFeedRate ) ) ) + self.addLine(' ') + self.addLine('Filament') + self.addLine( "Cross section area is %s mm2." % euclidean.getThreeSignificantFigures( crossSectionArea ) ) + if self.extrusionDiameter != None: + self.addLine( "Extrusion diameter is %s mm." % euclidean.getThreeSignificantFigures( self.extrusionDiameter ) ) + self.addLine('Extrusion fill density ratio is %s' % euclidean.getThreeSignificantFigures( crossSectionArea / self.absoluteEdgeWidth / self.layerHeight ) ) + self.addLine(' ') + self.addLine('Material') + self.addLine( "Mass extruded is %s grams." % euclidean.getThreeSignificantFigures( 1000.0 * mass ) ) + self.addLine( "Volume extruded is %s cc." % euclidean.getThreeSignificantFigures( volumeExtruded ) ) + self.addLine(' ') + self.addLine('Meta') + self.addLine( "Text has %s lines and a size of %s KB." % ( self.numberOfLines, kilobytes ) ) + if self.version != None: + self.addLine( "Version is " + self.version ) + self.addLine(' ') + self.addLine( "Procedures" ) + for procedure in self.procedures: + self.addLine(procedure) + if self.profileName != None: + self.addLine(' ') + self.addLine( 'Profile' ) + self.addLine(self.profileName) + self.addLine(' ') + self.addLine('Slice') + self.addLine( "Edge width is %s mm." % euclidean.getThreeSignificantFigures( self.absoluteEdgeWidth ) ) + self.addLine( "Layer height is %s mm." % euclidean.getThreeSignificantFigures( self.layerHeight ) ) + self.addLine(' ') + return self.output.getvalue() + + def getLocationSetFeedRateToSplitLine( self, splitLine ): + "Get location ans set feed rate to the plsit line." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + indexOfF = gcodec.getIndexOfStartingWithSecond( "F", splitLine ) + if indexOfF > 0: + self.feedRateMinute = gcodec.getDoubleAfterFirstLetter( splitLine[indexOfF] ) + return location + + def helicalMove( self, isCounterclockwise, splitLine ): + "Get statistics for a helical move." + if self.oldLocation == None: + return + location = self.getLocationSetFeedRateToSplitLine(splitLine) + location += self.oldLocation + center = self.oldLocation.copy() + indexOfR = gcodec.getIndexOfStartingWithSecond( "R", splitLine ) + if indexOfR > 0: + radius = gcodec.getDoubleAfterFirstLetter( splitLine[ indexOfR ] ) + halfLocationMinusOld = location - self.oldLocation + halfLocationMinusOld *= 0.5 + halfLocationMinusOldLength = halfLocationMinusOld.magnitude() + centerMidpointDistanceSquared = radius * radius - halfLocationMinusOldLength * halfLocationMinusOldLength + centerMidpointDistance = math.sqrt( max( centerMidpointDistanceSquared, 0.0 ) ) + centerMinusMidpoint = euclidean.getRotatedWiddershinsQuarterAroundZAxis( halfLocationMinusOld ) + centerMinusMidpoint.normalize() + centerMinusMidpoint *= centerMidpointDistance + if isCounterclockwise: + center.setToVector3( halfLocationMinusOld + centerMinusMidpoint ) + else: + center.setToVector3( halfLocationMinusOld - centerMinusMidpoint ) + else: + center.x = gcodec.getDoubleForLetter( "I", splitLine ) + center.y = gcodec.getDoubleForLetter( "J", splitLine ) + curveSection = 0.5 + center += self.oldLocation + afterCenterSegment = location - center + beforeCenterSegment = self.oldLocation - center + afterCenterDifferenceAngle = euclidean.getAngleAroundZAxisDifference( afterCenterSegment, beforeCenterSegment ) + absoluteDifferenceAngle = abs( afterCenterDifferenceAngle ) + steps = int( round( 0.5 + max( absoluteDifferenceAngle * 2.4, absoluteDifferenceAngle * beforeCenterSegment.magnitude() / curveSection ) ) ) + stepPlaneAngle = euclidean.getWiddershinsUnitPolar( afterCenterDifferenceAngle / steps ) + zIncrement = ( afterCenterSegment.z - beforeCenterSegment.z ) / float( steps ) + for step in xrange( 1, steps ): + beforeCenterSegment = euclidean.getRoundZAxisByPlaneAngle( stepPlaneAngle, beforeCenterSegment ) + beforeCenterSegment.z += zIncrement + arcPoint = center + beforeCenterSegment + self.addToPath( arcPoint ) + self.addToPath( location ) + + def linearMove( self, splitLine ): + "Get statistics for a linear move." + location = self.getLocationSetFeedRateToSplitLine(splitLine) + self.addToPath( location ) + + def parseLine(self, line): + "Parse a gcode line and add it to the statistics." + self.characters += len(line) + self.numberOfLines += 1 + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + elif firstWord == 'G2': + self.helicalMove( False, splitLine ) + elif firstWord == 'G3': + self.helicalMove( True, splitLine ) + elif firstWord == 'M101': + self.extruderSet( True ) + elif firstWord == 'M102': + self.extruderSet( False ) + elif firstWord == 'M103': + self.extruderSet( False ) + elif firstWord == 'M108': + self.extruderSpeed = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + self.extrusionDiameter = self.repository.extrusionDiameterOverThickness.value * self.layerHeight + elif firstWord == '(': + self.operatingFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + elif firstWord == '(': + self.procedures.append(splitLine[1]) + elif firstWord == '(': + self.profileName = line.replace('(', '').replace(')', '').strip() + elif firstWord == '(': + self.version = splitLine[1] + + +def main(): + "Display the statistics dialog." + if len(sys.argv) > 1: + getWindowAnalyzeFile(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/vectorwrite.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/vectorwrite.py new file mode 100644 index 0000000..ab1a903 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/analyze_plugins/vectorwrite.py @@ -0,0 +1,359 @@ +""" +This page is in the table of contents. +Vectorwrite is a very interesting analyze plugin that will create an SVG vector image for each layer that you can then use in some other printing system. + +The Scalable Vector Graphics file can be opened by an SVG viewer or an SVG capable browser like Mozilla: +http://www.mozilla.com/firefox/ + +The vectorwrite manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Vectorwrite + +==Operation== +The default 'Activate Vectorwrite' checkbox is off. When it is on, the functions described below will work when called from the skeinforge toolchain, when it is off, the functions will not be called from the toolchain. The functions will still be called, whether or not the 'Activate Vectorwrite' checkbox is on, when vectorwrite is run directly. + +==Settings== +===Add Loops=== +Default is on. + +If 'Add Loops' is selected, the loops will be added in yellow to the the scalable vector graphics output. + +===Add Paths=== +Default is on. + +If 'Add Paths' is selected, the paths will be added in pink to the the scalable vector graphics output. + +===Add Perimeters=== +Default is on. + +If 'Add Perimeters' is selected, the edges will be added to the the scalable vector graphics output. The outer edges will be red and the inner edges will be orange. + +===Layers=== +====Layers From==== +Default is zero. + +The "Layers From" is the index of the bottom layer that will be displayed. If the layer from is the default zero, the display will start from the lowest layer. If the the layer from index is negative, then the display will start from the layer from index below the top layer. + +====Layers To==== +Default is a huge number, which will be limited to the highest index layer. + +The "Layers To" is the index of the top layer that will be displayed. If the layer to index is a huge number like the default, the display will go to the top of the model, at least until we model habitats:) If the layer to index is negative, then the display will go to the layer to index below the top layer. The layer from until layer to index is a python slice. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +Below are examples of vectorwrite being used. These examples are run in a terminal in the folder which contains Screw Holder_penultimate.gcode and vectorwrite.py. + +> python vectorwrite.py +This brings up the vectorwrite dialog. + +> python vectorwrite.py Screw Holder_penultimate.gcode +The vectorwrite file is saved as Screw_Holder_penultimate_vectorwrite.svg + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import os +import sys +import time + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Nophead ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return VectorwriteRepository() + +def getWindowAnalyzeFile(fileName): + 'Write scalable vector graphics for a gcode file.' + gcodeText = archive.getFileText(fileName) + return getWindowAnalyzeFileGivenText(fileName, gcodeText) + +def getWindowAnalyzeFileGivenText( fileName, gcodeText, repository=None): + 'Write scalable vector graphics for a gcode file given the settings.' + if gcodeText == '': + return None + if repository == None: + repository = settings.getReadRepository( VectorwriteRepository() ) + startTime = time.time() + vectorwriteGcode = VectorwriteSkein().getCarvedSVG( fileName, gcodeText, repository ) + if vectorwriteGcode == '': + return None + suffixFileName = fileName[ : fileName.rfind('.') ] + '_vectorwrite.svg' + suffixDirectoryName = os.path.dirname(suffixFileName) + suffixReplacedBaseName = os.path.basename(suffixFileName).replace(' ', '_') + suffixFileName = os.path.join( suffixDirectoryName, suffixReplacedBaseName ) + archive.writeFileText( suffixFileName, vectorwriteGcode ) + print('The vectorwrite file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + print('It took %s to vectorwrite the file.' % euclidean.getDurationString( time.time() - startTime ) ) + settings.openSVGPage( suffixFileName, repository.svgViewer.value ) + +def writeOutput(fileName, fileNamePenultimate, fileNameSuffix, filePenultimateWritten, gcodeText=''): + 'Write scalable vector graphics for a skeinforge gcode file, if activate vectorwrite is selected.' + repository = settings.getReadRepository( VectorwriteRepository() ) + if not repository.activateVectorwrite.value: + return + gcodeText = archive.getTextIfEmpty( fileNameSuffix, gcodeText ) + getWindowAnalyzeFileGivenText( fileNameSuffix, gcodeText, repository ) + + +class SVGWriterVectorwrite( svg_writer.SVGWriter ): + 'A class to vectorwrite a carving.' + def addPaths( self, colorName, paths, transformString ): + 'Add paths to the output.' + pathString = '' + for path in paths: + pathString += self.getSVGStringForPath(path) + ' ' + if len( pathString ) < 1: + return + pathElementNodeCopy = self.pathElementNode.getCopy('', self.pathElementNode.parentNode ) + pathCopyDictionary = pathElementNodeCopy.attributes + pathCopyDictionary['d'] = pathString[ : - 1 ] + pathCopyDictionary['fill'] = 'none' + pathCopyDictionary['stroke'] = colorName + pathCopyDictionary['transform'] = transformString + + def addLoopLayerToOutput( self, layerIndex, threadLayer ): + 'Add rotated boundary layer to the output.' + settings.printProgress(self.layerIndex, 'vectorwrite') + self.addLayerBegin( layerIndex, threadLayer ) + transformString = self.getTransformString() + self.pathDictionary['d'] = self.getSVGStringForLoops( threadLayer.boundaryLoops ) + self.pathDictionary['transform'] = transformString + self.addPaths('#fa0', threadLayer.innerPerimeters, transformString ) #orange + self.addPaths('#ff0', threadLayer.loops, transformString ) #yellow + self.addPaths('#f00', threadLayer.outerPerimeters, transformString ) #red + self.addPaths('#f5c', threadLayer.paths, transformString ) #light violetred + + +class ThreadLayer: + 'Threads with a z.' + def __init__( self, z ): + self.boundaryLoops = [] + self.innerPerimeters = [] + self.loops = [] + self.outerPerimeters = [] + self.paths = [] + self.z = z + + def __repr__(self): + 'Get the string representation of this loop layer.' + return str(self.__dict__) + + def getTotalNumberOfThreads(self): + 'Get the total number of loops, paths and edges.' + return len(self.boundaryLoops) + len(self.innerPerimeters) + len(self.loops) + len(self.outerPerimeters) + len(self.paths) + + def maximize(self, vector3): + 'Maximize the vector3 over the loops, paths and edges.' + pointComplex = vector3.dropAxis() + pointComplex = euclidean.getMaximum(euclidean.getMaximumByComplexPaths(self.boundaryLoops), pointComplex) + pointComplex = euclidean.getMaximum(euclidean.getMaximumByComplexPaths(self.innerPerimeters), pointComplex) + pointComplex = euclidean.getMaximum(euclidean.getMaximumByComplexPaths(self.loops), pointComplex) + pointComplex = euclidean.getMaximum(euclidean.getMaximumByComplexPaths(self.outerPerimeters), pointComplex) + pointComplex = euclidean.getMaximum(euclidean.getMaximumByComplexPaths(self.paths), pointComplex) + vector3.setToXYZ(pointComplex.real, pointComplex.imag, max(self.z, vector3.z)) + + def minimize(self, vector3): + 'Minimize the vector3 over the loops, paths and edges.' + pointComplex = vector3.dropAxis() + pointComplex = euclidean.getMinimum(euclidean.getMinimumByComplexPaths(self.boundaryLoops), pointComplex) + pointComplex = euclidean.getMinimum(euclidean.getMinimumByComplexPaths(self.innerPerimeters), pointComplex) + pointComplex = euclidean.getMinimum(euclidean.getMinimumByComplexPaths(self.loops), pointComplex) + pointComplex = euclidean.getMinimum(euclidean.getMinimumByComplexPaths(self.outerPerimeters), pointComplex) + pointComplex = euclidean.getMinimum(euclidean.getMinimumByComplexPaths(self.paths), pointComplex) + vector3.setToXYZ(pointComplex.real, pointComplex.imag, min(self.z, vector3.z)) + + +class VectorwriteRepository: + 'A class to handle the vectorwrite settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.analyze_plugins.vectorwrite.html', self ) + self.activateVectorwrite = settings.BooleanSetting().getFromValue('Activate Vectorwrite', self, False ) + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File to Write Vector Graphics for', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Vectorwrite') + self.addLoops = settings.BooleanSetting().getFromValue('Add Loops', self, True) + self.addPaths = settings.BooleanSetting().getFromValue('Add Paths', self, True) + self.addPerimeters = settings.BooleanSetting().getFromValue('Add Perimeters', self, True) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Layers -', self ) + self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) + self.layersTo = settings.IntSpin().getSingleIncrementFromValue( 0, 'Layers To (index):', self, 912345678, 912345678 ) + settings.LabelSeparator().getFromRepository(self) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Vectorwrite' + + def execute(self): + 'Write button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrGcodeDirectory( self.fileNameInput.value, self.fileNameInput.wasCancelled ) + for fileName in fileNames: + getWindowAnalyzeFile(fileName) + + +class VectorwriteSkein: + 'A class to vectorwrite a carving.' + def __init__(self): + 'Initialize.' + self.layerCount = settings.LayerCount() + + def addLoopLayer(self, z): + 'Add loop layer.' + self.layerCount.printProgressIncrement('vectorwrite') + self.threadLayer = ThreadLayer(z) + self.threadLayers.append(self.threadLayer) + + def addToLoops(self): + 'Add the thread to the loops.' + self.isLoop = False + if len(self.thread) < 1: + return + if self.repository.addLoops.value: + self.threadLayer.loops.append(self.thread) + self.thread = [] + + def addToPerimeters(self): + 'Add the thread to the edges.' + self.isEdge = False + if len(self.thread) < 1: + return + if self.repository.addPerimeters.value: + if self.isOuter: + self.threadLayer.outerPerimeters.append(self.thread) + else: + self.threadLayer.innerPerimeters.append(self.thread) + self.thread = [] + + def getCarvedSVG(self, fileName, gcodeText, repository): + 'Parse gnu triangulated surface text and store the vectorwrite gcode.' + cornerMaximum = Vector3(-987654321.0, -987654321.0, -987654321.0) + cornerMinimum = Vector3(987654321.0, 987654321.0, 987654321.0) + self.boundaryLoop = None + self.extruderActive = False + self.isEdge = False + self.isLoop = False + self.isOuter = False + self.lines = archive.getTextLines(gcodeText) + self.oldLocation = None + self.thread = [] + self.threadLayers = [] + self.repository = repository + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + self.removeEmptyLayers() + for threadLayer in self.threadLayers: + threadLayer.maximize(cornerMaximum) + threadLayer.minimize(cornerMinimum) + halfLayerThickness = 0.5 * self.layerHeight + cornerMaximum.z += halfLayerThickness + cornerMinimum.z -= halfLayerThickness + svgWriter = SVGWriterVectorwrite( + True, cornerMaximum, cornerMinimum, self.decimalPlacesCarried, self.layerHeight, self.edgeWidth) + return svgWriter.getReplacedSVGTemplate(fileName, 'vectorwrite', self.threadLayers) + + def getCarveLayerHeight(self): + 'Get the layer height.' + return self.layerHeight + + def linearMove( self, splitLine ): + 'Get statistics for a linear move.' + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.extruderActive: + if len(self.thread) == 0: + self.thread = [ self.oldLocation.dropAxis() ] + self.thread.append(location.dropAxis()) + self.oldLocation = location + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '(': + self.decimalPlacesCarried = int(splitLine[1]) + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '()': + return + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + + def parseLine(self, line): + 'Parse a gcode line and add it to the outset skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + if self.isLoop: + self.addToLoops() + return + if self.isEdge: + self.addToPerimeters() + return + if self.repository.addPaths.value: + self.threadLayer.paths.append(self.thread) + self.thread = [] + elif firstWord == '()': + self.boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if self.boundaryLoop == None: + self.boundaryLoop = [] + self.threadLayer.boundaryLoops.append( self.boundaryLoop ) + self.boundaryLoop.append(location.dropAxis()) + elif firstWord == '(': + self.addLoopLayer(float(splitLine[1])) + elif firstWord == '()': + self.addToLoops() + elif firstWord == '(': + self.isLoop = True + elif firstWord == '(': + self.isEdge = True + self.isOuter = ( splitLine[1] == 'outer') + elif firstWord == '()': + self.addToPerimeters() + + def removeEmptyLayers(self): + 'Remove empty layers.' + for threadLayerIndex, threadLayer in enumerate(self.threadLayers): + if threadLayer.getTotalNumberOfThreads() > 0: + self.threadLayers = self.threadLayers[threadLayerIndex :] + return + + +def main(): + 'Display the vectorwrite dialog.' + if len(sys.argv) > 1: + getWindowAnalyzeFile(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft.py new file mode 100644 index 0000000..61b8103 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft.py @@ -0,0 +1,128 @@ +""" +This page is in the table of contents. +Craft is a script to access the plugins which craft a gcode file. + +The plugin buttons which are commonly used are bolded and the ones which are rarely used have normal font weight. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addSubmenus( menu, pluginFileName, pluginFolderPath, pluginPath ): + "Add a tool plugin menu." + submenu = settings.Tkinter.Menu( menu, tearoff = 0 ) + menu.add_cascade( label = pluginFileName.capitalize(), menu = submenu ) + settings.ToolDialog().addPluginToMenu( submenu, pluginPath ) + submenu.add_separator() + submenuFileNames = archive.getPluginFileNamesFromDirectoryPath( pluginFolderPath ) + for submenuFileName in submenuFileNames: + settings.ToolDialog().addPluginToMenu( submenu, os.path.join( pluginFolderPath, submenuFileName ) ) + +def addToCraftMenu( menu ): + "Add a craft plugin menu." + settings.ToolDialog().addPluginToMenu(menu, archive.getUntilDot(archive.getSkeinforgePluginsPath('craft.py'))) + menu.add_separator() + directoryPath = skeinforge_craft.getPluginsDirectoryPath() + directoryFolders = settings.getFolders(directoryPath) + pluginFileNames = skeinforge_craft.getPluginFileNames() + for pluginFileName in pluginFileNames: + pluginFolderName = pluginFileName + '_plugins' + pluginPath = os.path.join( directoryPath, pluginFileName ) + if pluginFolderName in directoryFolders: + addSubmenus( menu, pluginFileName, os.path.join( directoryPath, pluginFolderName ), pluginPath ) + else: + settings.ToolDialog().addPluginToMenu( menu, pluginPath ) + +def addToMenu( master, menu, repository, window ): + "Add a tool plugin menu." + CraftMenuSaveListener( menu, window ) + +def getNewRepository(): + 'Get new repository.' + return skeinforge_craft.CraftRepository() + +def writeOutput(fileName): + "Craft a gcode file." + return skeinforge_craft.writeOutput(fileName) + + +class CraftMenuSaveListener: + "A class to update a craft menu." + def __init__( self, menu, window ): + "Set the menu." + self.menu = menu + addToCraftMenu( menu ) + euclidean.addElementToListDictionaryIfNotThere( self, window, settings.globalProfileSaveListenerListTable ) + + def save(self): + "Profile has been saved and profile menu should be updated." + settings.deleteMenuItems( self.menu ) + addToCraftMenu( self.menu ) + + +class CraftRadioButtonsSaveListener: + "A class to update the craft radio buttons." + def addToDialog( self, gridPosition ): + "Add this to the dialog." + euclidean.addElementToListDictionaryIfNotThere( self, self.repository.repositoryDialog, settings.globalProfileSaveListenerListTable ) + self.gridPosition = gridPosition.getCopy() + self.gridPosition.increment() + self.gridPosition.row = gridPosition.rowStart + self.setRadioButtons() + + def getFromRadioPlugins( self, radioPlugins, repository ): + "Initialize." + self.name = 'CraftRadioButtonsSaveListener' + self.radioPlugins = radioPlugins + self.repository = repository + repository.displayEntities.append(self) + return self + + def save(self): + "Profile has been saved and craft radio plugins should be updated." + self.setRadioButtons() + + def setRadioButtons(self): + "Profile has been saved and craft radio plugins should be updated." + craftSequence = skeinforge_profile.getCraftTypePluginModule().getCraftSequence() + gridPosition = self.gridPosition.getCopy() + maximumValue = False + activeRadioPlugins = [] + for radioPlugin in self.radioPlugins: + if radioPlugin.name in craftSequence: + activeRadioPlugins.append( radioPlugin ) + radioPlugin.incrementGridPosition(gridPosition) + maximumValue = max( radioPlugin.value, maximumValue ) + else: + radioPlugin.radiobutton.grid_remove() + if not maximumValue: + selectedRadioPlugin = settings.getSelectedRadioPlugin( self.repository.importantFileNames + [ activeRadioPlugins[0].name ], activeRadioPlugins ).setSelect() + self.repository.pluginFrame.update() + + +def main(): + "Display the craft dialog." + if len(sys.argv) > 1: + settings.startMainLoopFromWindow(writeOutput(' '.join(sys.argv[1 :]))) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/alteration.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/alteration.py new file mode 100644 index 0000000..be5eb4f --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/alteration.py @@ -0,0 +1,260 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +The alteration plugin adds the start and end files to the gcode. + +This plugin also removes the alteration prefix tokens from the alteration lines. Alteration lines have a prefix token so they can go through the craft plugins without being modified. However, the tokens are not recognized by the firmware so they have to be removed before export. The alteration token is: +() + +The alteration manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Alteration + +==Operation== +The default 'Activate Alteration' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +Alteration looks for alteration files in the alterations folder in the .skeinforge folder in the home directory. Alteration does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. + +===Name of End File=== +Default is 'end.gcode'. + +If there is a file with the name of the "Name of End File" setting, it will be added to the very end of the gcode. + +===Name of Start File=== +Default is 'start.gcode'. + +If there is a file with the name of the "Name of Start File" setting, it will be added to the very beginning of the gcode. + +===Remove Redundant Mcode=== +Default: True + +If 'Remove Redundant Mcode' is selected then M104 and M108 lines which are followed by a different value before there is a movement will be removed. For example, if there is something like: +M113 S1.0 +M104 S60.0 +( 0.72 ) +M104 S200.0 +() + +with Remove Redundant Mcode selected, that snippet would become: +M113 S1.0 +M104 S200.0 +( 0.72 ) +() + +This is a relatively safe procedure, the only reason it is optional is because someone might make an alteration file which, for some unknown reason, requires the redundant mcode. + +===Replace Variable with Setting=== +Default: True + +If 'Replace Variable with Setting' is selected and there is an alteration line with a setting token, the token will be replaced by the value. + +For example, if there is an alteration line like: + +M140 S + +the token would be replaced with the value and assuming the bed chamber was 60.0, the output would be: + +M140 S60.0 + +==Examples== +The following examples add the alteration information to the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and alteration.py. + +> python alteration.py +This brings up the alteration dialog. + +> python alteration.py Screw Holder Bottom.stl +The alteration tool is parsing the file: +Screw Holder Bottom.stl +.. +The alteration tool has created the file: +.. Screw Holder Bottom_alteration.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text='', repository=None): + 'Alteration a gcode linear move text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Alteration a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'alteration'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(AlterationRepository()) + if not repository.activateAlteration.value: + return gcodeText + return AlterationSkein().getCraftedGcode(gcodeText, repository) + +def getGcodeTextWithoutRedundantMcode(gcodeText): + 'Get gcode text without redundant M104 and M108.' + lines = archive.getTextLines(gcodeText) + lines = getLinesWithoutRedundancy('M104', lines) + lines = getLinesWithoutRedundancy('M108', lines) + output = cStringIO.StringIO() + gcodec.addLinesToCString(output, lines) + return output.getvalue() + +def getLinesWithoutRedundancy(duplicateWord, lines): + 'Get gcode lines without redundant first words.' + oldDuplicationIndex = None + for lineIndex, line in enumerate(lines): + firstWord = gcodec.getFirstWordFromLine(line) + if firstWord == duplicateWord: + if oldDuplicationIndex == None: + oldDuplicationIndex = lineIndex + else: + lines[oldDuplicationIndex] = line + lines[lineIndex] = '' + elif firstWord.startswith('G') or firstWord == 'M101' or firstWord == 'M103': + oldDuplicationIndex = None + return lines + +def getNewRepository(): + 'Get new repository.' + return AlterationRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Alteration a gcode linear move file. Chain alteration the gcode if the alteration procedure has not been done.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'alteration', shouldAnalyze) + + +class AlterationRepository: + "A class to handle the alteration settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.alteration.html', self ) + self.baseNameSynonym = 'bookend.csv' + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Alteration', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Alteration') + self.activateAlteration = settings.BooleanSetting().getFromValue('Activate Alteration', self, True) + self.nameOfEndFile = settings.StringSetting().getFromValue('Name of End File:', self, 'end.gcode') + self.nameOfStartFile = settings.StringSetting().getFromValue('Name of Start File:', self, 'start.gcode') + self.removeRedundantMcode = settings.BooleanSetting().getFromValue('Remove Redundant Mcode', self, True) + self.replaceVariableWithSetting = settings.BooleanSetting().getFromValue('Replace Variable with Setting', self, True) + self.executeTitle = 'Alteration' + + def execute(self): + 'Alteration button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class AlterationSkein: + "A class to alteration a skein of extrusions." + def __init__(self): + 'Initialize.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.lineIndex = 0 + self.settingDictionary = None + + def addFromUpperLowerFile(self, fileName): + "Add lines of text from the fileName or the lowercase fileName, if there is no file by the original fileName in the directory." + alterationFileLines = settings.getAlterationFileLines(fileName) + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(alterationFileLines) + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the bevel gcode." + self.lines = archive.getTextLines(gcodeText) + if repository.replaceVariableWithSetting.value: + self.setSettingDictionary() + self.addFromUpperLowerFile(repository.nameOfStartFile.value) # Add a start file if it exists. + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.distanceFeedRate.addLine(line) + self.addFromUpperLowerFile(repository.nameOfEndFile.value) # Add an end file if it exists. + gcodeText = self.getReplacedAlterationText() + if repository.removeRedundantMcode.value: + gcodeText = getGcodeTextWithoutRedundantMcode(gcodeText) + return gcodeText + + def getReplacedAlterationLine(self, alterationFileLine, searchIndex=0): + 'Get the alteration file line with variables replaced with the settings.' + settingIndex = alterationFileLine.find('setting.', searchIndex) + beginIndex = settingIndex - 1 + if beginIndex < 0: + return alterationFileLine + endBracketIndex = alterationFileLine.find('>', settingIndex) + if alterationFileLine[beginIndex] != '<' or endBracketIndex == -1: + return alterationFileLine + endIndex = endBracketIndex + 1 + innerToken = alterationFileLine[settingIndex + len('setting.'): endIndex].replace('>', '').replace(' ', '').replace('_', '').lower() + if innerToken in self.settingDictionary: + replacedSetting = self.settingDictionary[innerToken] + replacedAlterationLine = alterationFileLine[: beginIndex] + replacedSetting + alterationFileLine[endIndex :] + return self.getReplacedAlterationLine(replacedAlterationLine, beginIndex + len(replacedSetting)) + return alterationFileLine + + def getReplacedAlterationText(self): + 'Replace the alteration lines if there are settings.' + if self.settingDictionary == None: + return self.distanceFeedRate.output.getvalue().replace('()', '') + lines = archive.getTextLines(self.distanceFeedRate.output.getvalue()) + distanceFeedRate = gcodec.DistanceFeedRate() + for line in lines: + if line.startswith('()'): + line = self.getReplacedAlterationLine(line[len('()') :]) + distanceFeedRate.addLine(line) + return distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('alteration') + return + self.distanceFeedRate.addLine(line) + + def setSettingDictionary(self): + 'Set the setting dictionary from the gcode text.' + for line in self.lines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '(' and self.settingDictionary != None: + if len(splitLine) > 4: + procedure = splitLine[1] + name = splitLine[2].replace('_', ' ').replace(' ', '') + if '(' in name: + name = name[: name.find('(')] + value = ' '.join(splitLine[3 : -1]) + self.settingDictionary[(procedure + '.' + name).lower()] = value + elif firstWord == '()': + self.settingDictionary = {} + elif firstWord == '()': + return + + +def main(): + "Display the alteration dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/bottom.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/bottom.py new file mode 100644 index 0000000..a761b74 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/bottom.py @@ -0,0 +1,158 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Bottom sets the bottom of the carving to the defined altitude. + +The bottom manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Bottom + +==Operation== +The default 'Activate Bottom' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Additional Height over Layer Thickness=== +Default is half. + +The layers will start at the altitude plus the 'Additional Height over Layer Thickness' times the layer height. The default value of half means that the bottom layer is at the height of the bottom slice, because each slice is made through the middle of each layer. Raft expects the layers to start at an additional half layer height. You should only change 'Additional Height over Layer Thickness' if you are manipulating the skeinforge output with your own program which does not use the raft tool. + +===Altitude=== +Default is zero. + +Defines the altitude of the bottom of the model. The bottom slice has a z of the altitude plus the 'Additional Height over Layer Thickness' times the layer height. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +The following examples bottom the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and bottom.py. + +> python bottom.py +This brings up the bottom dialog. + +> python bottom.py Screw Holder Bottom.stl +The bottom tool is parsing the file: +Screw Holder Bottom.stl +.. +The bottom tool has created the file: +.. Screw Holder Bottom_bottom.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from datetime import date +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.svg_reader import SVGReader +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, svgText='', repository=None): + "Bottom and convert an svg file or svgText." + return getCraftedTextFromText(fileName, archive.getTextIfEmpty(fileName, svgText), repository) + +def getCraftedTextFromText(fileName, svgText, repository=None): + "Bottom and convert an svgText." + if gcodec.isProcedureDoneOrFileIsEmpty(svgText, 'bottom'): + return svgText + if repository == None: + repository = settings.getReadRepository(BottomRepository()) + if not repository.activateBottom.value: + return svgText + return BottomSkein().getCraftedGcode(fileName, repository, svgText) + +def getNewRepository(): + 'Get new repository.' + return BottomRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Bottom the carving.' + skeinforge_craft.writeSVGTextWithNounMessage(fileName, BottomRepository(), shouldAnalyze) + + +class BottomRepository: + "A class to handle the bottom settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository( + 'skeinforge_application.skeinforge_plugins.craft_plugins.bottom.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Bottom', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Bottom') + self.activateBottom = settings.BooleanSetting().getFromValue('Activate Bottom', self, True) + self.additionalHeightOverLayerThickness = settings.FloatSpin().getFromValue( + 0.0, 'Additional Height over Layer Thickness (ratio):', self, 1.0, 0.5) + self.altitude = settings.FloatSpin().getFromValue(-1.0, 'Altitude (mm):', self, 1.0, 0.0) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + self.executeTitle = 'Bottom' + + def execute(self): + "Bottom button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class BottomSkein: + "A class to bottom a skein of extrusions." + def getCraftedGcode(self, fileName, repository, svgText): + "Parse svgText and store the bottom svgText." + svgReader = SVGReader() + svgReader.parseSVG('', svgText) + if svgReader.sliceDictionary == None: + print('Warning, nothing will be done because the sliceDictionary could not be found getCraftedGcode in preface.') + return '' + decimalPlacesCarried = int(svgReader.sliceDictionary['decimalPlacesCarried']) + edgeWidth = float(svgReader.sliceDictionary['edgeWidth']) + layerHeight = float(svgReader.sliceDictionary['layerHeight']) + loopLayers = svgReader.loopLayers + zMinimum = 987654321.0 + for loopLayer in loopLayers: + zMinimum = min(loopLayer.z, zMinimum) + deltaZ = repository.altitude.value + repository.additionalHeightOverLayerThickness.value * layerHeight - zMinimum + for loopLayer in loopLayers: + loopLayer.z += deltaZ + cornerMaximum = Vector3(-912345678.0, -912345678.0, -912345678.0) + cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0) + svg_writer.setSVGCarvingCorners(cornerMaximum, cornerMinimum, layerHeight, loopLayers) + svgWriter = svg_writer.SVGWriter( + True, + cornerMaximum, + cornerMinimum, + decimalPlacesCarried, + layerHeight, + edgeWidth) + commentElement = svg_writer.getCommentElement(svgReader.documentElement) + procedureNameString = svgReader.sliceDictionary['procedureName'] + ',bottom' + return svgWriter.getReplacedSVGTemplate(fileName, loopLayers, procedureNameString, commentElement) + + +def main(): + "Display the bottom dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py new file mode 100644 index 0000000..7114caa --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py @@ -0,0 +1,224 @@ +""" +This page is in the table of contents. +Carve is the most important plugin to define for your printer. + +It carves a shape into svg slice layers. It also sets the layer height and edge width for the rest of the tool chain. + +The carve manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Carve + +On the Arcol Blog a method of deriving the layer height is posted. That article "Machine Calibrating" is at: +http://blog.arcol.hu/?p=157 + +==Settings== +===Add Layer Template to SVG=== +Default is on. + +When selected, the layer template will be added to the svg output, which adds javascript control boxes. So 'Add Layer Template to SVG' should be selected when the svg will be viewed in a browser. + +When off, no controls will be added, the svg output will only include the fabrication paths. So 'Add Layer Template to SVG' should be deselected when the svg will be used by other software, like Inkscape. + +===Edge Width over Height=== +Default is 1.8. + +Defines the ratio of the extrusion edge width to the layer height. This parameter tells skeinforge how wide the edge wall is expected to be in relation to the layer height. Default value of 1.8 for the default layer height of 0.4 states that a single filament edge wall should be 0.4 mm * 1.8 = 0.72 mm wide. The higher the value the more the edge will be inset. A ratio of one means the extrusion is a circle, the default ratio of 1.8 means the extrusion is a wide oval. + +This is an important value because if you are calibrating your machine you need to ensure that the speed of the head and the extrusion rate in combination produce a wall that is 'Layer Height' * 'Edge Width over Height' wide. To start with 'Edge Width over Height' is probably best left at the default of 1.8 and the extrusion rate adjusted to give the correct calculated wall thickness. + +Adjustment is in the 'Speed' section with 'Feed Rate' controlling speed of the head in X & Y and 'Flow Rate' controlling the extrusion rate. Initially it is probably easier to start adjusting the flow rate only a little at a time until you get a single filament of the correct width. If you change too many parameters at once you can get in a right mess. + +===Extra Decimal Places=== +Default is two. + +Defines the number of extra decimal places export will output compared to the number of decimal places in the layer height. The higher the 'Extra Decimal Places', the more significant figures the output numbers will have. + +===Import Coarseness=== +Default is one. + +When a triangle mesh has holes in it, the triangle mesh slicer switches over to a slow algorithm that spans gaps in the mesh. The higher the 'Import Coarseness' setting, the wider the gaps in the mesh it will span. An import coarseness of one means it will span gaps of the edge width. + +===Layer Height=== +Default is 0.4 mm. + +Defines the the height of the layers skeinforge will cut your object into, in the z direction. This is the most important carve setting, many values in the toolchain are derived from the layer height. + +For a 0.5 mm nozzle usable values are 0.3 mm to 0.5 mm. Note; if you are using thinner layers make sure to adjust the extrusion speed as well. + +===Layers=== +Carve slices from bottom to top. To get a single layer, set the "Layers From" to zero and the "Layers To" to one. The 'Layers From' until 'Layers To' range is a python slice. + +====Layers From==== +Default is zero. + +Defines the index of the bottom layer that will be carved. If the 'Layers From' is the default zero, the carving will start from the lowest layer. If the 'Layers From' index is negative, then the carving will start from the 'Layers From' index below the top layer. + +For example if your object is 5 mm tall and your layer thicknes is 1 mm if you set layers from to 3 you will ignore the first 3 mm and start from 3 mm. + +====Layers To==== +Default is a huge number, which will be limited to the highest index layer. + +Defines the index of the top layer that will be carved. If the 'Layers To' index is a huge number like the default, the carving will go to the top of the model. If the 'Layers To' index is negative, then the carving will go to the 'Layers To' index below the top layer. + +This is the same as layers from, only it defines when to end the generation of gcode. + +===Mesh Type=== +Default is 'Correct Mesh'. + +====Correct Mesh==== +When selected, the mesh will be accurately carved, and if a hole is found, carve will switch over to the algorithm that spans gaps. + +====Unproven Mesh==== +When selected, carve will use the gap spanning algorithm from the start. The problem with the gap spanning algothm is that it will span gaps, even if there is not actually a gap in the model. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +The following examples carve the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and carve.py. + +> python carve.py +This brings up the carve dialog. + +> python carve.py Screw Holder Bottom.stl +The carve tool is parsing the file: +Screw Holder Bottom.stl +.. +The carve tool has created the file: +.. Screw Holder Bottom_carve.svg + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + "Get carved text." + if fileName.endswith('.svg'): + gcodeText = archive.getTextIfEmpty(fileName, gcodeText) + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'carve'): + return gcodeText + carving = svg_writer.getCarving(fileName) + if carving == None: + return '' + if repository == None: + repository = CarveRepository() + settings.getReadRepository(repository) + return CarveSkein().getCarvedSVG( carving, fileName, repository ) + +def getNewRepository(): + 'Get new repository.' + return CarveRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Carve a GNU Triangulated Surface file." + startTime = time.time() + print('File ' + archive.getSummarizedFileName(fileName) + ' is being carved.') + repository = CarveRepository() + settings.getReadRepository(repository) + carveGcode = getCraftedText(fileName, '', repository) + if carveGcode == '': + return + suffixFileName = archive.getFilePathWithUnderscoredBasename(fileName, '_carve.svg') + archive.writeFileText(suffixFileName, carveGcode) + print('The carved file is saved as ' + archive.getSummarizedFileName(suffixFileName)) + print('It took %s to carve the file.' % euclidean.getDurationString(time.time() - startTime)) + if shouldAnalyze: + settings.openSVGPage(suffixFileName, repository.svgViewer.value) + + +class CarveRepository: + "A class to handle the carve settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.carve.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getTranslatorFileTypeTuples(), 'Open File for Carve', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Carve') + self.addLayerTemplateToSVG = settings.BooleanSetting().getFromValue('Add Layer Template to SVG', self, True) + self.edgeWidth = settings.FloatSpin().getFromValue( 0.1, 'Edge Width (mm):', self, 2.2, 0.4 ) + self.extraDecimalPlaces = settings.FloatSpin().getFromValue(0.0, 'Extra Decimal Places (float):', self, 3.0, 2.0) + self.importCoarseness = settings.FloatSpin().getFromValue( 0.5, 'Import Coarseness (ratio):', self, 2.0, 1.0 ) + self.layerHeight = settings.FloatSpin().getFromValue( 0.1, 'Layer Height (mm):', self, 1.0, 0.2 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Layers -', self ) + self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) + self.layersTo = settings.IntSpin().getSingleIncrementFromValue( 0, 'Layers To (index):', self, 912345678, 912345678 ) + settings.LabelSeparator().getFromRepository(self) + self.meshTypeLabel = settings.LabelDisplay().getFromName('Mesh Type: ', self ) + importLatentStringVar = settings.LatentStringVar() + self.correctMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Correct Mesh', self, True ) + self.unprovenMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Unproven Mesh', self, False ) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Carve' + + def execute(self): + "Carve button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypes(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class CarveSkein: + "A class to carve a carving." + def getCarvedSVG(self, carving, fileName, repository): + "Parse gnu triangulated surface text and store the carved gcode." + layerHeight = repository.layerHeight.value + edgeWidth = repository.edgeWidth.value + carving.setCarveLayerHeight(layerHeight) + importRadius = 0.5 * repository.importCoarseness.value * abs(edgeWidth) + carving.setCarveImportRadius(max(importRadius, 0.001 * layerHeight)) + carving.setCarveIsCorrectMesh(repository.correctMesh.value) + loopLayers = carving.getCarveBoundaryLayers() + if len(loopLayers) < 1: + print('Warning, there are no slices for the model, this could be because the model is too small for the Layer Height.') + return '' + layerHeight = carving.getCarveLayerHeight() + decimalPlacesCarried = euclidean.getDecimalPlacesCarried(repository.extraDecimalPlaces.value, layerHeight) + edgeWidth = repository.edgeWidth.value + svgWriter = svg_writer.SVGWriter( + repository.addLayerTemplateToSVG.value, + carving.getCarveCornerMaximum(), + carving.getCarveCornerMinimum(), + decimalPlacesCarried, + carving.getCarveLayerHeight(), + edgeWidth) + truncatedRotatedBoundaryLayers = svg_writer.getTruncatedRotatedBoundaryLayers(loopLayers, repository) + return svgWriter.getReplacedSVGTemplate(fileName, truncatedRotatedBoundaryLayers, 'carve', carving.getFabmetheusXML()) + + +def main(): + "Display the carve dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py new file mode 100644 index 0000000..ce54b11 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py @@ -0,0 +1,300 @@ +""" +This page is in the table of contents. +Some filaments contract too much and warp the extruded object. To prevent this you have to print the object in a temperature regulated chamber and/or on a temperature regulated bed. The chamber tool allows you to control the bed and chamber temperature and the holding pressure. + +The chamber gcodes are also described at: + +http://reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes + +The chamber manual page is at: + +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Chamber + +==Operation== +The default 'Activate Chamber' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Bed=== +The initial bed temperature is defined by 'Bed Temperature'. If the 'Bed Temperature End Change Height' is greater or equal to the 'Bed Temperature Begin Change Height' and the 'Bed Temperature Begin Change Height' is greater or equal to zero, then the temperature will be ramped toward the 'Bed Temperature End'. The ramp will start once the extruder reaches the 'Bed Temperature Begin Change Height', then the bed temperature will approach the 'Bed Temperature End' as the extruder reaches the 'Bed Temperature End Change Height', finally the bed temperature will stay at the 'Bed Temperature End' for the remainder of the build. + +====Bed Temperature==== +Default: 60C + +Defines the initial print bed temperature in Celcius by adding an M140 command. + +====Bed Temperature Begin Change Height==== +Default: -1 mm + +Defines the height of the beginning of the temperature ramp. If the 'Bed Temperature End Change Height' is less than zero, the bed temperature will remain at the initial 'Bed Temperature'. + +====Bed Temperature End Change Height==== +Default: -1 mm + +Defines the height of the end of the temperature ramp. If the 'Bed Temperature End Change Height' is less than zero or less than the 'Bed Temperature Begin Change Height', the bed temperature will remain at the initial 'Bed Temperature'. + +====Bed Temperature End==== +Default: 20C + +Defines the end bed temperature if there is a temperature ramp. + +===Chamber Temperature=== +Default: 30C + +Defines the chamber temperature in Celcius by adding an M141 command. + +===Holding Force=== +Default: 0 + +Defines the holding pressure of a mechanism, like a vacuum table or electromagnet, to hold the bed surface or object, by adding an M142 command. The holding pressure is in bars. For hardware which only has on/off holding, when the holding pressure is zero, turn off holding, when the holding pressure is greater than zero, turn on holding. + +==Heated Beds== +===Bothacker=== +A resistor heated aluminum plate by Bothacker: + +http://bothacker.com + +with an article at: + +http://bothacker.com/2009/12/18/heated-build-platform/ + +===Domingo=== +A heated copper build plate by Domingo: + +http://casainho-emcrepstrap.blogspot.com/ + +with articles at: + +http://casainho-emcrepstrap.blogspot.com/2010/01/first-time-with-pla-testing-it-also-on.html + +http://casainho-emcrepstrap.blogspot.com/2010/01/call-for-helpideas-to-develop-heated.html + +http://casainho-emcrepstrap.blogspot.com/2010/01/new-heated-build-platform.html + +http://casainho-emcrepstrap.blogspot.com/2010/01/no-acrylic-and-instead-kapton-tape-on.html + +http://casainho-emcrepstrap.blogspot.com/2010/01/problems-with-heated-build-platform-and.html + +http://casainho-emcrepstrap.blogspot.com/2010/01/perfect-build-platform.html + +http://casainho-emcrepstrap.blogspot.com/2009/12/almost-no-warp.html + +http://casainho-emcrepstrap.blogspot.com/2009/12/heated-base-plate.html + +===Jmil=== +A heated build stage by jmil, over at: + +http://www.hive76.org + +with articles at: + +http://www.hive76.org/handling-hot-build-surfaces + +http://www.hive76.org/heated-build-stage-success + +===Metalab=== +A heated base by the Metalab folks: + +http://reprap.soup.io + +with information at: + +http://reprap.soup.io/?search=heated%20base + +===Nophead=== +A resistor heated aluminum bed by Nophead: + +http://hydraraptor.blogspot.com + +with articles at: + +http://hydraraptor.blogspot.com/2010/01/will-it-stick.html + +http://hydraraptor.blogspot.com/2010/01/hot-metal-and-serendipity.html + +http://hydraraptor.blogspot.com/2010/01/new-year-new-plastic.html + +http://hydraraptor.blogspot.com/2010/01/hot-bed.html + +===Prusajr=== +A resistive wire heated plexiglass plate by prusajr: + +http://prusadjs.cz/ + +with articles at: + +http://prusadjs.cz/2010/01/heated-reprap-print-bed-mk2/ + +http://prusadjs.cz/2009/11/look-ma-no-warping-heated-reprap-print-bed/ + +===Zaggo=== +A resistor heated aluminum plate by Zaggo at Pleasant Software: + +http://pleasantsoftware.com/developer/3d/ + +with articles at: + +http://pleasantsoftware.com/developer/3d/2009/12/05/raftless/ + +http://pleasantsoftware.com/developer/3d/2009/11/15/living-in-times-of-warp-free-printing/ + +http://pleasantsoftware.com/developer/3d/2009/11/12/canned-heat/ + +==Examples== +The following examples chamber the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and chamber.py. + +> python chamber.py +This brings up the chamber dialog. + +> python chamber.py Screw Holder Bottom.stl +The chamber tool is parsing the file: +Screw Holder Bottom.stl +.. +The chamber tool has created the file: +Screw Holder Bottom_chamber.gcode + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text='', repository=None): + "Chamber the file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Chamber a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'chamber'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(ChamberRepository()) + if not repository.activateChamber.value: + return gcodeText + return ChamberSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return ChamberRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Chamber a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'chamber', shouldAnalyze) + + +class ChamberRepository: + "A class to handle the chamber settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.chamber.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Chamber', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Chamber') + self.activateChamber = settings.BooleanSetting().getFromValue('Activate Chamber', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Bed -', self ) + self.bedTemperature = settings.FloatSpin().getFromValue(20.0, 'Bed Temperature (Celcius):', self, 90.0, 60.0) + self.bedTemperatureBeginChangeHeight = settings.FloatSpin().getFromValue(-1.0, 'Bed Temperature Begin Change Height (mm):', self, 20.0, -1.0) + self.bedTemperatureEndChangeHeight = settings.FloatSpin().getFromValue(-1.0, 'Bed Temperature End Change Height (mm):', self, 40.0, -1.0) + self.bedTemperatureEnd = settings.FloatSpin().getFromValue(20.0, 'Bed Temperature End (Celcius):', self, 90.0, 20.0) + settings.LabelSeparator().getFromRepository(self) + self.chamberTemperature = settings.FloatSpin().getFromValue( 20.0, 'Chamber Temperature (Celcius):', self, 90.0, 30.0 ) + self.holdingForce = settings.FloatSpin().getFromValue( 0.0, 'Holding Force (bar):', self, 100.0, 0.0 ) + self.executeTitle = 'Chamber' + + def execute(self): + "Chamber button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + + +class ChamberSkein: + "A class to chamber a skein of extrusions." + def __init__(self): + 'Initialize.' + self.changeWidth = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.lineIndex = 0 + self.lines = None + self.oldBedTemperature = None + + def addBedTemperature(self, bedTemperature): + 'Add bed temperature if it is different from the old.' + if bedTemperature != self.oldBedTemperature: + self.distanceFeedRate.addParameter('M140', bedTemperature) + self.oldBedTemperature = bedTemperature + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the chamber gcode." + endAtLeastBegin = repository.bedTemperatureEndChangeHeight.value >= repository.bedTemperatureBeginChangeHeight.value + if endAtLeastBegin and repository.bedTemperatureBeginChangeHeight.value >= 0.0: + self.changeWidth = repository.bedTemperatureEndChangeHeight.value - repository.bedTemperatureBeginChangeHeight.value + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('chamber') + return + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the chamber skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()': + self.distanceFeedRate.addLine(line) + self.addBedTemperature(self.repository.bedTemperature.value) + self.distanceFeedRate.addParameter('M141', self.repository.chamberTemperature.value) # Set chamber temperature. + self.distanceFeedRate.addParameter('M142', self.repository.holdingForce.value) # Set holding pressure. + return + self.distanceFeedRate.addLine(line) + if firstWord == '(' and self.changeWidth != None: + z = float(splitLine[1]) + if z >= self.repository.bedTemperatureEndChangeHeight.value: + self.addBedTemperature(self.repository.bedTemperatureEnd.value) + return + if z <= self.repository.bedTemperatureBeginChangeHeight.value: + return + along = (z - self.repository.bedTemperatureBeginChangeHeight.value) / self.changeWidth + self.addBedTemperature(self.repository.bedTemperature.value * (1 - along) + self.repository.bedTemperatureEnd.value * along) + + +def main(): + "Display the chamber dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chop.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chop.py new file mode 100644 index 0000000..8b0e556 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/chop.py @@ -0,0 +1,222 @@ +""" +This page is in the table of contents. +Chop is a script to chop a shape into svg slice layers. + +==Settings== +===Add Layer Template to SVG=== +Default is on. + +When selected, the layer template will be added to the svg output, which adds javascript control boxes. So 'Add Layer Template to SVG' should be selected when the svg will be viewed in a browser. + +When off, no controls will be added, the svg output will only include the fabrication paths. So 'Add Layer Template to SVG' should be deselected when the svg will be used by other software, like Inkscape. + +===Add Extra Top Layer if Necessary=== +Default is on. + +When selected, chop will add an extra layer at the very top of the object if the top of the object is more than half the layer height above the first slice. This is so the cutting tool doesn't cut too deeply through the top of the object on its first pass. + +===Extra Decimal Places=== +Default is two. + +Defines the number of extra decimal places export will output compared to the number of decimal places in the layer height. The higher the 'Extra Decimal Places', the more significant figures the output numbers will have. + +===Import Coarseness=== +Default is one. + +When a triangle mesh has holes in it, the triangle mesh slicer switches over to a slow algorithm that spans gaps in the mesh. The higher the 'Import Coarseness' setting, the wider the gaps in the mesh it will span. An import coarseness of one means it will span gaps of the edge width. + +===Layer Height=== +Default is 0.4 mm. + +Defines the height of the layer, this is the most important chop setting. + +===Layers=== +Chop slices from top to bottom. To get only the bottom layer, set the "Layers From" to minus one. The 'Layers From' until 'Layers To' range is a python slice. + +====Layers From==== +Default is zero. + +Defines the index of the top layer that will be chopped. If the 'Layers From' is the default zero, the carving will start from the top layer. If the 'Layers From' index is negative, then the carving will start from the 'Layers From' index above the bottom layer. + +====Layers To==== +Default is a huge number, which will be limited to the highest index number. + +Defines the index of the bottom layer that will be chopped. If the 'Layers To' index is a huge number like the default, the carving will go to the bottom of the model. If the 'Layers To' index is negative, then the carving will go to the 'Layers To' index above the bottom layer. + +===Mesh Type=== +Default is 'Correct Mesh'. + +====Correct Mesh==== +When selected, the mesh will be accurately chopped, and if a hole is found, chop will switch over to the algorithm that spans gaps. + +====Unproven Mesh==== +When selected, chop will use the gap spanning algorithm from the start. The problem with the gap spanning algothm is that it will span gaps, even if there is not actually a gap in the model. + +===Perimeter Width=== +Default is 2 mm. + +Defines the width of the edge. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +The following examples chop the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and chop.py. + +> python chop.py +This brings up the chop dialog. + +> python chop.py Screw Holder Bottom.stl +The chop tool is parsing the file: +Screw Holder Bottom.stl +.. +The chop tool has created the file: +.. Screw Holder Bottom_chop.svg + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + "Get chopped text." + if fileName.endswith('.svg'): + gcodeText = archive.getTextIfEmpty(fileName, gcodeText) + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'chop'): + return gcodeText + carving = svg_writer.getCarving(fileName) + if carving == None: + return '' + if repository == None: + repository = ChopRepository() + settings.getReadRepository(repository) + return ChopSkein().getCarvedSVG( carving, fileName, repository ) + +def getNewRepository(): + 'Get new repository.' + return ChopRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Chop a GNU Triangulated Surface file. If no fileName is specified, chop the first GNU Triangulated Surface file in this folder." + startTime = time.time() + print('File ' + archive.getSummarizedFileName(fileName) + ' is being chopped.') + repository = ChopRepository() + settings.getReadRepository(repository) + chopGcode = getCraftedText( fileName, '', repository ) + if chopGcode == '': + return + suffixFileName = fileName[ : fileName.rfind('.') ] + '_chop.svg' + suffixDirectoryName = os.path.dirname(suffixFileName) + suffixReplacedBaseName = os.path.basename(suffixFileName).replace(' ', '_') + suffixFileName = os.path.join( suffixDirectoryName, suffixReplacedBaseName ) + archive.writeFileText( suffixFileName, chopGcode ) + print('The chopped file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + print('It took %s to chop the file.' % euclidean.getDurationString( time.time() - startTime ) ) + if shouldAnalyze: + settings.openSVGPage( suffixFileName, repository.svgViewer.value ) + + +class ChopRepository: + "A class to handle the chop settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.chop.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getTranslatorFileTypeTuples(), 'Open File to be Chopped', self, '') + self.addExtraTopLayerIfNecessary = settings.BooleanSetting().getFromValue('Add Extra Top Layer if Necessary', self, True ) + self.addLayerTemplateToSVG = settings.BooleanSetting().getFromValue('Add Layer Template to SVG', self, True) + self.edgeWidth = settings.FloatSpin().getFromValue( 0.4, 'Edge Width (mm):', self, 4.0, 2.0 ) + self.extraDecimalPlaces = settings.FloatSpin().getFromValue(0.0, 'Extra Decimal Places (float):', self, 3.0, 2.0) + self.importCoarseness = settings.FloatSpin().getFromValue( 0.5, 'Import Coarseness (ratio):', self, 2.0, 1.0 ) + self.layerHeight = settings.FloatSpin().getFromValue( 0.1, 'Layer Height (mm):', self, 1.0, 0.4 ) + self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) + self.layersTo = settings.IntSpin().getSingleIncrementFromValue( 0, 'Layers To (index):', self, 912345678, 912345678 ) + self.meshTypeLabel = settings.LabelDisplay().getFromName('Mesh Type: ', self, ) + importLatentStringVar = settings.LatentStringVar() + self.correctMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Correct Mesh', self, True ) + self.unprovenMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Unproven Mesh', self, False ) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Chop' + + def execute(self): + "Chop button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypes(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class ChopSkein: + "A class to chop a carving." + def addExtraTopLayerIfNecessary( self, carving, layerHeight, loopLayers ): + "Add extra top layer if necessary." + topRotatedBoundaryLayer = loopLayers[-1] + cuttingSafeHeight = topRotatedBoundaryLayer.z + 0.5001 * layerHeight + if cuttingSafeHeight > carving.getCarveCornerMaximum().z: + return + extraTopRotatedBoundaryLayer = topRotatedBoundaryLayer.getCopyAtZ( topRotatedBoundaryLayer.z + layerHeight ) + loopLayers.append( extraTopRotatedBoundaryLayer ) + + def getCarvedSVG( self, carving, fileName, repository ): + "Parse gnu triangulated surface text and store the chopped gcode." + layerHeight = repository.layerHeight.value + edgeWidth = repository.edgeWidth.value + carving.setCarveLayerHeight( layerHeight ) + importRadius = 0.5 * repository.importCoarseness.value * abs(edgeWidth) + carving.setCarveImportRadius(max(importRadius, 0.001 * layerHeight)) + carving.setCarveIsCorrectMesh( repository.correctMesh.value ) + loopLayers = carving.getCarveBoundaryLayers() + if len( loopLayers ) < 1: + print('Warning, there are no slices for the model, this could be because the model is too small for the Layer Height.') + return '' + if repository.addExtraTopLayerIfNecessary.value: + self.addExtraTopLayerIfNecessary( carving, layerHeight, loopLayers ) + loopLayers.reverse() + layerHeight = carving.getCarveLayerHeight() + decimalPlacesCarried = euclidean.getDecimalPlacesCarried(repository.extraDecimalPlaces.value, layerHeight) + svgWriter = svg_writer.SVGWriter( + repository.addLayerTemplateToSVG.value, + carving.getCarveCornerMaximum(), + carving.getCarveCornerMinimum(), + decimalPlacesCarried, + carving.getCarveLayerHeight(), + edgeWidth) + truncatedRotatedBoundaryLayers = svg_writer.getTruncatedRotatedBoundaryLayers(loopLayers, repository) + return svgWriter.getReplacedSVGTemplate( fileName, truncatedRotatedBoundaryLayers, 'chop', carving.getFabmetheusXML()) + + +def main(): + "Display the chop dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cleave.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cleave.py new file mode 100644 index 0000000..9ae1ef6 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cleave.py @@ -0,0 +1,204 @@ +""" +This page is in the table of contents. +Cleave is a script to cleave a shape into svg slice layers. + +==Settings== +===Add Layer Template to SVG=== +Default is on. + +When selected, the layer template will be added to the svg output, which adds javascript control boxes. So 'Add Layer Template to SVG' should be selected when the svg will be viewed in a browser. + +When off, no controls will be added, the svg output will only include the fabrication paths. So 'Add Layer Template to SVG' should be deselected when the svg will be used by other software, like Inkscape. + +===Extra Decimal Places=== +Default is two. + +Defines the number of extra decimal places export will output compared to the number of decimal places in the layer height. The higher the 'Extra Decimal Places', the more significant figures the output numbers will have. + +===Import Coarseness=== +Default is one. + +When a triangle mesh has holes in it, the triangle mesh slicer switches over to a slow algorithm that spans gaps in the mesh. The higher the 'Import Coarseness' setting, the wider the gaps in the mesh it will span. An import coarseness of one means it will span gaps of the edge width. + +===Layer Height=== +Default is 0.4 mm. + +Defines the height of the layer, this is the most important cleave setting. + +===Layers=== +Cleave slices from bottom to top. To get a single layer, set the "Layers From" to zero and the "Layers To" to one. The layer from until layer to range is a python slice. + +====Layers From==== +Default is zero. + +Defines the index of the bottom layer that will be cleaved. If the layer from is the default zero, the carving will start from the lowest layer. If the 'Layers From' index is negative, then the carving will start from the 'Layers From' index below the top layer. + +====Layers To==== +Default is a huge number, which will be limited to the highest index layer. + +Defines the index of the top layer that will be cleaved. If the 'Layers To' index is a huge number like the default, the carving will go to the top of the model. If the 'Layers To' index is negative, then the carving will go to the 'Layers To' index below the top layer. + +===Mesh Type=== +Default is 'Correct Mesh'. + +====Correct Mesh==== +When selected, the mesh will be accurately cleaved, and if a hole is found, cleave will switch over to the algorithm that spans gaps. + +====Unproven Mesh==== +When selected, cleave will use the gap spanning algorithm from the start. The problem with the gap spanning algothm is that it will span gaps, even if there is not actually a gap in the model. + +===Perimeter Width=== +Default is two millimeters. + +Defines the width of the edge. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +The following examples cleave the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and cleave.py. + +> python cleave.py +This brings up the cleave dialog. + +> python cleave.py Screw Holder Bottom.stl +The cleave tool is parsing the file: +Screw Holder Bottom.stl +.. +The cleave tool has created the file: +.. Screw Holder Bottom_cleave.svg + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + "Get cleaved text." + if fileName.endswith('.svg'): + gcodeText = archive.getTextIfEmpty(fileName, gcodeText) + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'cleave'): + return gcodeText + carving = svg_writer.getCarving(fileName) + if carving == None: + return '' + if repository == None: + repository = CleaveRepository() + settings.getReadRepository(repository) + return CleaveSkein().getCarvedSVG( carving, fileName, repository ) + +def getNewRepository(): + 'Get new repository.' + return CleaveRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Cleave a GNU Triangulated Surface file." + startTime = time.time() + print('File ' + archive.getSummarizedFileName(fileName) + ' is being cleaved.') + repository = CleaveRepository() + settings.getReadRepository(repository) + cleaveGcode = getCraftedText( fileName, '', repository ) + if cleaveGcode == '': + return + suffixFileName = fileName[ : fileName.rfind('.') ] + '_cleave.svg' + suffixDirectoryName = os.path.dirname(suffixFileName) + suffixReplacedBaseName = os.path.basename(suffixFileName).replace(' ', '_') + suffixFileName = os.path.join( suffixDirectoryName, suffixReplacedBaseName ) + archive.writeFileText( suffixFileName, cleaveGcode ) + print('The cleaved file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + print('It took %s to cleave the file.' % euclidean.getDurationString( time.time() - startTime ) ) + if shouldAnalyze: + settings.openSVGPage( suffixFileName, repository.svgViewer.value ) + + +class CleaveRepository: + "A class to handle the cleave settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.cleave.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getTranslatorFileTypeTuples(), 'Open File to be Cleaved', self, '') + self.addLayerTemplateToSVG = settings.BooleanSetting().getFromValue('Add Layer Template to SVG', self, True) + self.edgeWidth = settings.FloatSpin().getFromValue( 0.4, 'Edge Width (mm):', self, 4.0, 2.0 ) + self.extraDecimalPlaces = settings.FloatSpin().getFromValue(0.0, 'Extra Decimal Places (float):', self, 3.0, 2.0) + self.importCoarseness = settings.FloatSpin().getFromValue( 0.5, 'Import Coarseness (ratio):', self, 2.0, 1.0 ) + self.layerHeight = settings.FloatSpin().getFromValue( 0.1, 'Layer Height (mm):', self, 1.0, 0.4 ) + self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) + self.layersTo = settings.IntSpin().getSingleIncrementFromValue( 0, 'Layers To (index):', self, 912345678, 912345678 ) + self.meshTypeLabel = settings.LabelDisplay().getFromName('Mesh Type: ', self, ) + importLatentStringVar = settings.LatentStringVar() + self.correctMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Correct Mesh', self, True ) + self.unprovenMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Unproven Mesh', self, False ) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Cleave' + + def execute(self): + "Cleave button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypes(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class CleaveSkein: + "A class to cleave a carving." + def getCarvedSVG( self, carving, fileName, repository ): + "Parse gnu triangulated surface text and store the cleaved gcode." + edgeWidth = repository.edgeWidth.value + layerHeight = repository.layerHeight.value + carving.setCarveLayerHeight( layerHeight ) + importRadius = 0.5 * repository.importCoarseness.value * abs(edgeWidth) + carving.setCarveImportRadius(max(importRadius, 0.001 * layerHeight)) + carving.setCarveIsCorrectMesh( repository.correctMesh.value ) + loopLayers = carving.getCarveBoundaryLayers() + if len( loopLayers ) < 1: + print('Warning, there are no slices for the model, this could be because the model is too small for the Layer Height.') + return '' + layerThickness = carving.getCarveLayerHeight() + decimalPlacesCarried = euclidean.getDecimalPlacesCarried(repository.extraDecimalPlaces.value, layerHeight) + svgWriter = svg_writer.SVGWriter( + repository.addLayerTemplateToSVG.value, + carving.getCarveCornerMaximum(), + carving.getCarveCornerMinimum(), + decimalPlacesCarried, + carving.getCarveLayerHeight(), + edgeWidth) + truncatedRotatedBoundaryLayers = svg_writer.getTruncatedRotatedBoundaryLayers(loopLayers, repository) + return svgWriter.getReplacedSVGTemplate( fileName, truncatedRotatedBoundaryLayers, 'cleave', carving.getFabmetheusXML()) + + +def main(): + "Display the cleave dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/clip.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/clip.py new file mode 100644 index 0000000..0edbc21 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/clip.py @@ -0,0 +1,343 @@ +""" +This page is in the table of contents. +The clip plugin clips the loop ends to prevent bumps from forming, and connects loops. + +The clip manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Clip + +==Operation== +The default 'Activate Clip' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Clip Over Perimeter Width=== +Default is 0.2. + +Defines the ratio of the amount each end of the loop is clipped over the edge width. The total gap will therefore be twice the clip. If the ratio is too high loops will have a gap, if the ratio is too low there will be a bulge at the loop ends. + +This setting will affect the output of clip, and the output of the skin. In skin the half width edges will be clipped by according to this setting. + +===Maximum Connection Distance Over Perimeter Width=== +Default is ten. + +Defines the ratio of the maximum connection distance between loops over the edge width. + +Clip will attempt to connect loops that end close to each other, combining them into a spiral, so that the extruder does not stop and restart. This setting sets the maximum gap size to connect. This feature can reduce the amount of extra material or gaps formed at the loop end. + +Setting this to zero disables this feature, preventing the loops from being connected. + +==Examples== +The following examples clip the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and clip.py. + +> python clip.py +This brings up the clip dialog. + +> python clip.py Screw Holder Bottom.stl +The clip tool is parsing the file: +Screw Holder Bottom.stl +.. +The clip tool has created the file: +.. Screw Holder Bottom_clip.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text, repository=None): + "Clip a gcode linear move file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Clip a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'clip'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(ClipRepository()) + if not repository.activateClip.value: + return gcodeText + return ClipSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return ClipRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Clip a gcode linear move file. Chain clip the gcode if it is not already clipped." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'clip', shouldAnalyze) + + +class ClipRepository: + "A class to handle the clip settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.clip.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Clip', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Clip') + self.activateClip = settings.BooleanSetting().getFromValue('Activate Clip', self, False) + self.clipOverEdgeWidth = settings.FloatSpin().getFromValue(0.1, 'Clip Over Perimeter Width (ratio):', self, 0.8, 0.5) + self.maximumConnectionDistanceOverEdgeWidth = settings.FloatSpin().getFromValue( 1.0, 'Maximum Connection Distance Over Perimeter Width (ratio):', self, 20.0, 10.0) + self.executeTitle = 'Clip' + + def execute(self): + "Clip button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class ClipSkein: + "A class to clip a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.feedRateMinute = None + self.isEdge = False + self.isLoop = False + self.layerCount = settings.LayerCount() + self.loopPath = None + self.lineIndex = 0 + self.oldConnectionPoint = None + self.oldLocation = None + self.oldWiddershins = None + self.travelFeedRateMinute = None + + def addGcodeFromThreadZ( self, thread, z ): + "Add a gcode thread to the output." + if len(thread) > 0: + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.travelFeedRateMinute, thread[0], z ) + else: + print("zero length vertex positions array which was skipped over, this should never happen") + if len(thread) < 2: + print("thread of only one point in clip, this should never happen") + print(thread) + return + self.distanceFeedRate.addLine('M101') + for point in thread[1 :]: + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.feedRateMinute, point, z ) + + def addSegmentToPixelTables(self, location, oldLocation): + "Add the segment to the layer and mask table." + euclidean.addValueSegmentToPixelTable(oldLocation, location, self.layerPixelTable, None, self.layerPixelWidth) + + def addTailoredLoopPath(self, line): + "Add a clipped loop path." + if self.clipLength > 0.0: + removeTable = {} + euclidean.addLoopToPixelTable(self.loopPath.path, removeTable, self.layerPixelWidth) + euclidean.removePixelTableFromPixelTable( removeTable, self.layerPixelTable ) + self.loopPath.path = euclidean.getClippedSimplifiedLoopPath(self.clipLength, self.loopPath.path, self.edgeWidth) + euclidean.addLoopToPixelTable( self.loopPath.path, self.layerPixelTable, self.layerPixelWidth ) + if self.oldWiddershins == None: + self.addGcodeFromThreadZ( self.loopPath.path, self.loopPath.z ) + else: + if self.oldWiddershins != euclidean.isWiddershins( self.loopPath.path ): + self.loopPath.path.reverse() + for point in self.loopPath.path: + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.feedRateMinute, point, self.loopPath.z ) + if self.getNextThreadIsACloseLoop(self.loopPath.path): + self.oldConnectionPoint = self.loopPath.path[-1] + self.oldWiddershins = euclidean.isWiddershins(self.loopPath.path) + else: + self.oldConnectionPoint = None + self.oldWiddershins = None + self.distanceFeedRate.addLine(line) + self.loopPath = None + + def getConnectionIsCloseWithoutOverlap( self, location, path ): + "Determine if the connection is close enough and does not overlap another thread." + if len(path) < 1: + return False + locationComplex = location.dropAxis() + segment = locationComplex - path[-1] + segmentLength = abs(segment) + if segmentLength <= 0.0: + return True + if segmentLength > self.maximumConnectionDistance: + return False + segmentTable = {} + euclidean.addSegmentToPixelTable( path[-1], locationComplex, segmentTable, 2.0, 2.0, self.layerPixelWidth ) + if euclidean.isPixelTableIntersecting( self.layerPixelTable, segmentTable, {} ): + return False + euclidean.addValueSegmentToPixelTable( path[-1], locationComplex, self.layerPixelTable, None, self.layerPixelWidth ) + return True + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the clip gcode." + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getNextThreadIsACloseLoop(self, path): + "Determine if the next thread is a loop." + if self.oldLocation == None or self.maximumConnectionDistance <= 0.0: + return False + isEdge = False + isLoop = False + location = self.oldLocation + for afterIndex in xrange(self.lineIndex + 1, len(self.lines)): + line = self.lines[afterIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + elif firstWord == '(': + isLoop = True + elif firstWord == '(': + isEdge = True + elif firstWord == 'M101': + if isLoop != self.isLoop or isEdge != self.isEdge: + return False + return self.getConnectionIsCloseWithoutOverlap(location, path) + elif firstWord == '(': + return False + return False + + def isNextExtruderOn(self): + "Determine if there is an extruder on command before a move command." + for afterIndex in xrange(self.lineIndex + 1, len(self.lines)): + line = self.lines[afterIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1' or firstWord == 'M103': + return False + elif firstWord == 'M101': + return True + return False + + def linearMove(self, splitLine): + "Add to loop path if this is a loop or path." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + if self.isLoop or self.isEdge: + if self.isNextExtruderOn(): + self.loopPath = euclidean.PathZ(location.z) + if self.loopPath == None: + if self.extruderActive: + self.oldWiddershins = None + else: + if self.oldConnectionPoint != None: + self.addSegmentToPixelTables(self.oldConnectionPoint, location.dropAxis()) + self.oldConnectionPoint = None + self.loopPath.path.append(location.dropAxis()) + self.oldLocation = location + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('clip') + return + elif firstWord == '(': + self.distanceFeedRate.addTagBracketedLine('clipOverEdgeWidth', self.repository.clipOverEdgeWidth.value) + self.edgeWidth = float(splitLine[1]) + absoluteEdgeWidth = abs(self.edgeWidth) + self.clipLength = self.repository.clipOverEdgeWidth.value * self.edgeWidth + self.connectingStepLength = 0.5 * absoluteEdgeWidth + self.layerPixelWidth = 0.34321 * absoluteEdgeWidth + self.maximumConnectionDistance = self.repository.maximumConnectionDistanceOverEdgeWidth.value * absoluteEdgeWidth + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the clip skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + elif firstWord == '(': + self.setLayerPixelTable() + elif firstWord == '(': + self.isLoop = True + elif firstWord == '()': + self.isLoop = False + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + if self.loopPath != None: + self.addTailoredLoopPath(line) + return + elif firstWord == '(': + self.isEdge = True + elif firstWord == '()': + self.isEdge = False + if self.loopPath == None: + self.distanceFeedRate.addLine(line) + + def setLayerPixelTable(self): + "Set the layer pixel table." + self.layerCount.printProgressIncrement('clip') + boundaryLoop = None + extruderActive = False + self.lastInactiveLocation = None + self.layerPixelTable = {} + oldLocation = self.oldLocation + for afterIndex in xrange(self.lineIndex + 1, len(self.lines)): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(oldLocation, splitLine) + if extruderActive and oldLocation != None: + self.addSegmentToPixelTables(location.dropAxis(), oldLocation.dropAxis()) + if extruderActive: + if self.lastInactiveLocation != None: + self.addSegmentToPixelTables(self.lastInactiveLocation.dropAxis(), location.dropAxis()) + self.lastInactiveLocation = None + else: + self.lastInactiveLocation = location + oldLocation = location + elif firstWord == 'M101': + extruderActive = True + elif firstWord == 'M103': + extruderActive = False + elif firstWord == '()': + euclidean.addLoopToPixelTable(boundaryLoop, self.layerPixelTable, self.layerPixelWidth) + boundaryLoop = None + elif firstWord == '(': + if boundaryLoop == None: + boundaryLoop = [] + location = gcodec.getLocationFromSplitLine(None, splitLine) + boundaryLoop.append(location.dropAxis()) + elif firstWord == '()': + return + +def main(): + "Display the clip dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/coil.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/coil.py new file mode 100644 index 0000000..f49760f --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/coil.py @@ -0,0 +1,252 @@ +""" +This page is in the table of contents. +Coil is a script to coil wire or filament around an object. + +==Operation== +The default 'Activate Coil' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Minimum Tool Distance=== +Default is twenty millimeters. + +Defines the minimum distance between the wire dispenser and the object. The 'Minimum Tool Distance' should be set to the maximum radius of the wire dispenser, times at least 1.3 to get a reasonable safety margin. + +==Examples== +The following examples coil the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and coil.py. + +> python coil.py +This brings up the coil dialog. + +> python coil.py Screw Holder Bottom.stl +The coil tool is parsing the file: +Screw Holder Bottom.stl +.. +The coil tool has created the file: +Screw Holder Bottom_coil.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + "Coil the file or gcodeText." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + "Coil a gcode linear move gcodeText." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'coil'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( CoilRepository() ) + if not repository.activateCoil.value: + return gcodeText + return CoilSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return CoilRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Coil a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'coil', shouldAnalyze) + + +class CoilRepository: + "A class to handle the coil settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.coil.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Coil', self, '') + self.activateCoil = settings.BooleanSetting().getFromValue('Activate Coil', self, True ) + self.minimumToolDistance = settings.FloatSpin().getFromValue( 10.0, 'Minimum Tool Distance (millimeters):', self, 50.0, 20.0 ) + self.executeTitle = 'Coil' + + def execute(self): + "Coil button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + + +class CoilSkein: + "A class to coil a skein of extrusions." + def __init__(self): + self.boundaryLayers = [] + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = 0.6 + self.lineIndex = 0 + self.lines = None + self.oldLocationComplex = complex() + self.shutdownLines = [] + + def addCoilLayer( self, boundaryLayers, radius, z ): + "Add a coil layer." + self.distanceFeedRate.addLine('( %s )' % z ) # Indicate that a new layer is starting. + self.distanceFeedRate.addLine('()') + thread = [] + for boundaryLayerIndex in xrange(1, len(boundaryLayers) - 1): + boundaryLayer = boundaryLayers[boundaryLayerIndex] + boundaryLayerBegin = boundaryLayers[boundaryLayerIndex - 1] + boundaryLayerEnd = boundaryLayers[boundaryLayerIndex + 1] + beginLocation = Vector3(0.0, 0.0, 0.5 * (boundaryLayerBegin.z + boundaryLayer.z)) + outsetLoop = intercircle.getLargestInsetLoopFromLoop(boundaryLayer.loops[0], - radius) + self.addCoilToThread(beginLocation, 0.5 * (boundaryLayer.z + boundaryLayerEnd.z), outsetLoop, thread) + self.addGcodeFromThread(thread) + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLine('()') + + def addCoilLayers(self): + "Add the coil layers." + numberOfLayersFloat = round( self.edgeWidth / self.layerHeight ) + numberOfLayers = int( numberOfLayersFloat ) + halfLayerThickness = 0.5 * self.layerHeight + startOutset = self.repository.minimumToolDistance.value + halfLayerThickness + startZ = self.boundaryLayers[0].z + halfLayerThickness + zRange = self.boundaryLayers[-1].z - self.boundaryLayers[0].z + zIncrement = 0.0 + if zRange >= 0.0: + zIncrement = zRange / numberOfLayersFloat + for layerIndex in xrange( numberOfLayers ): + settings.printProgressByNumber(layerIndex, numberOfLayers, 'coil') + boundaryLayers = self.boundaryLayers + if layerIndex % 2 == 1: + boundaryLayers = self.boundaryReverseLayers + radius = startOutset + layerIndex * self.layerHeight + z = startZ + layerIndex * zIncrement + self.addCoilLayer( boundaryLayers, radius, z ) + + def addCoilToThread(self, beginLocation, endZ, loop, thread): + "Add a coil to the thread." + if len(loop) < 1: + return + loop = euclidean.getLoopStartingClosest(self.halfEdgeWidth, self.oldLocationComplex, loop) + length = euclidean.getLoopLength(loop) + if length <= 0.0: + return + oldPoint = loop[0] + pathLength = 0.0 + for point in loop[1 :]: + pathLength += abs(point - oldPoint) + along = pathLength / length + z = (1.0 - along) * beginLocation.z + along * endZ + location = Vector3(point.real, point.imag, z) + thread.append(location) + oldPoint = point + self.oldLocationComplex = loop[-1] + + def addGcodeFromThread( self, thread ): + "Add a thread to the output." + if len(thread) > 0: + firstLocation = thread[0] + self.distanceFeedRate.addGcodeMovementZ( firstLocation.dropAxis(), firstLocation.z ) + else: + print("zero length vertex positions array which was skipped over, this should never happen") + if len(thread) < 2: + print("thread of only one point in addGcodeFromThread in coil, this should never happen") + print(thread) + return + self.distanceFeedRate.addLine('M101') # Turn extruder on. + for location in thread[1 :]: + self.distanceFeedRate.addGcodeMovementZ( location.dropAxis(), location.z ) + self.distanceFeedRate.addLine('M103') # Turn extruder off. + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the coil gcode." + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.parseBoundaries() + self.parseUntilLayer() + self.addCoilLayers() + self.distanceFeedRate.addLines( self.shutdownLines ) + return self.distanceFeedRate.output.getvalue() + + def parseBoundaries(self): + "Parse the boundaries and add them to the boundary layers." + boundaryLoop = None + boundaryLayer = None + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if len( self.shutdownLines ) > 0: + self.shutdownLines.append(line) + if firstWord == '()': + boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if boundaryLoop == None: + boundaryLoop = [] + boundaryLayer.loops.append(boundaryLoop) + boundaryLoop.append(location.dropAxis()) + elif firstWord == '(': + boundaryLayer = euclidean.LoopLayer(float(splitLine[1])) + self.boundaryLayers.append(boundaryLayer) + elif firstWord == '()': + self.shutdownLines = [ line ] + for boundaryLayer in self.boundaryLayers: + if not euclidean.isWiddershins( boundaryLayer.loops[0] ): + boundaryLayer.loops[0].reverse() + self.boundaryReverseLayers = self.boundaryLayers[:] + self.boundaryReverseLayers.reverse() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('coil') + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.halfEdgeWidth = 0.5 * self.edgeWidth + self.distanceFeedRate.addLine(line) + + def parseUntilLayer(self): + "Parse until the layer line and add it to the coil skein." + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + return + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the coil dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py new file mode 100644 index 0000000..8ca5e2d --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py @@ -0,0 +1,500 @@ +""" +This page is in the table of contents. +Comb is a craft plugin to bend the extruder travel paths around holes in the slices, to avoid stringers. + +The comb manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Comb + +==Operation== +The default 'Activate Comb' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Running Jump Space=== +Default: 2 mm + +Defines the running jump space that is added before going from one island to another. If the running jump space is greater than zero, the departure from the island will also be brought closer to the arrival point on the next island so that the stringer between islands will be shorter. For an extruder with acceleration code, an extra space before leaving the island means that it will be going at high speed as it exits the island, which means the stringer between islands will be thinner. + +==Examples== +The following examples comb the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and comb.py. + +> python comb.py +This brings up the comb dialog. + +> python comb.py Screw Holder Bottom.stl +The comb tool is parsing the file: +Screw Holder Bottom.stl +.. +The comb tool has created the file: +.. Screw Holder Bottom_comb.gcode + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text, repository=None): + "Comb a gcode linear move text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Comb a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'comb'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(CombRepository()) + if not repository.activateComb.value: + return gcodeText + return CombSkein().getCraftedGcode(gcodeText, repository) + +def getJumpPoint(begin, end, loop, runningJumpSpace): + 'Get running jump point inside loop.' + segment = begin - end + segmentLength = abs(segment) + if segmentLength == 0.0: + return begin + segment /= segmentLength + distancePoint = DistancePoint(begin, loop, runningJumpSpace, segment) + if distancePoint.distance == runningJumpSpace: + return distancePoint.point + effectiveDistance = distancePoint.distance + jumpPoint = distancePoint.point + segmentLeft = complex(0.70710678118654757, -0.70710678118654757) + distancePoint = DistancePoint(begin, loop, runningJumpSpace, segmentLeft) + distancePoint.distance *= 0.5 + if distancePoint.distance > effectiveDistance: + effectiveDistance = distancePoint.distance + jumpPoint = distancePoint.point + segmentRight = complex(0.70710678118654757, 0.70710678118654757) + distancePoint = DistancePoint(begin, loop, runningJumpSpace, segmentRight) + distancePoint.distance *= 0.5 + if distancePoint.distance > effectiveDistance: + effectiveDistance = distancePoint.distance + jumpPoint = distancePoint.point + return jumpPoint + +def getJumpPointIfInside(boundary, otherPoint, edgeWidth, runningJumpSpace): + 'Get the jump point if it is inside the boundary, otherwise return None.' + insetBoundary = intercircle.getSimplifiedInsetFromClockwiseLoop(boundary, -edgeWidth) + closestJumpDistanceIndex = euclidean.getClosestDistanceIndexToLine(otherPoint, insetBoundary) + jumpIndex = (closestJumpDistanceIndex.index + 1) % len(insetBoundary) + jumpPoint = euclidean.getClosestPointOnSegment(insetBoundary[closestJumpDistanceIndex.index], insetBoundary[jumpIndex], otherPoint) + jumpPoint = getJumpPoint(jumpPoint, otherPoint, boundary, runningJumpSpace) + if euclidean.isPointInsideLoop(boundary, jumpPoint): + return jumpPoint + return None + +def getNewRepository(): + 'Get new repository.' + return CombRepository() + +def getPathsByIntersectedLoop(begin, end, loop): + 'Get both paths along the loop from the point closest to the begin to the point closest to the end.' + closestBeginDistanceIndex = euclidean.getClosestDistanceIndexToLine(begin, loop) + closestEndDistanceIndex = euclidean.getClosestDistanceIndexToLine(end, loop) + beginIndex = (closestBeginDistanceIndex.index + 1) % len(loop) + endIndex = (closestEndDistanceIndex.index + 1) % len(loop) + closestBegin = euclidean.getClosestPointOnSegment(loop[closestBeginDistanceIndex.index], loop[beginIndex], begin) + closestEnd = euclidean.getClosestPointOnSegment(loop[closestEndDistanceIndex.index], loop[endIndex], end) + clockwisePath = [closestBegin] + widdershinsPath = [closestBegin] + if closestBeginDistanceIndex.index != closestEndDistanceIndex.index: + widdershinsPath += euclidean.getAroundLoop(beginIndex, endIndex, loop) + clockwisePath += euclidean.getAroundLoop(endIndex, beginIndex, loop)[: : -1] + clockwisePath.append(closestEnd) + widdershinsPath.append(closestEnd) + return [clockwisePath, widdershinsPath] + +def writeOutput(fileName, shouldAnalyze=True): + "Comb a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'comb', shouldAnalyze) + + +class BoundarySegment: + 'A boundary and segment.' + def __init__(self, begin): + 'Initialize' + self.segment = [begin] + + def getSegment(self, boundarySegmentIndex, boundarySegments, edgeWidth, runningJumpSpace): + 'Get both paths along the loop from the point closest to the begin to the point closest to the end.' + negativeEdgeWidth = -edgeWidth + nextBoundarySegment = boundarySegments[boundarySegmentIndex + 1] + nextBegin = nextBoundarySegment.segment[0] + end = getJumpPointIfInside(self.boundary, nextBegin, edgeWidth, runningJumpSpace) + if end == None: + end = self.boundary.segment[1] + nextBegin = getJumpPointIfInside(nextBoundarySegment.boundary, end, edgeWidth, runningJumpSpace) + if nextBegin != None: + nextBoundarySegment.segment[0] = nextBegin + return (self.segment[0], end) + + +class CombRepository: + "A class to handle the comb settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.comb.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Comb', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Comb') + self.activateComb = settings.BooleanSetting().getFromValue('Activate Comb', self, True ) + self.runningJumpSpace = settings.FloatSpin().getFromValue(0.0, 'Running Jump Space (mm):', self, 5.0, 2.0) + self.executeTitle = 'Comb' + + def execute(self): + "Comb button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class CombSkein: + "A class to comb a skein of extrusions." + def __init__(self): + 'Initialize' +# self.betweenTable = {} + self.boundaryLoop = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.layer = None + self.layerCount = settings.LayerCount() + self.layerTable = {} + self.layerZ = None + self.lineIndex = 0 + self.lines = None + self.nextLayerZ = None + self.oldLocation = None + self.oldZ = None + self.operatingFeedRatePerMinute = None + self.travelFeedRateMinute = None + self.widdershinTable = {} + + def addGcodePathZ( self, feedRateMinute, path, z ): + "Add a gcode path, without modifying the extruder, to the output." + for point in path: + self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, point, z) + + def addIfTravel(self, splitLine): + "Add travel move around loops if the extruder is off." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if not self.extruderActive and self.oldLocation != None: + if len(self.getBoundaries()) > 0: + highestZ = max(location.z, self.oldLocation.z) + self.addGcodePathZ(self.travelFeedRateMinute, self.getAroundBetweenPath(self.oldLocation.dropAxis(), location.dropAxis()), highestZ) + self.oldLocation = location + + def addToLoop(self, location): + "Add a location to loop." + if self.layer == None: + if not self.oldZ in self.layerTable: + self.layerTable[self.oldZ] = [] + self.layer = self.layerTable[self.oldZ] + if self.boundaryLoop == None: + self.boundaryLoop = [] + self.layer.append(self.boundaryLoop) + self.boundaryLoop.append(location.dropAxis()) + + def getAroundBetweenLineSegment(self, begin, boundaries, end): + 'Get the path around the loops in the way of the original line segment.' + aroundBetweenLineSegment = [] + boundaries = self.getBoundaries() + points = [] + boundaryIndexes = self.getBoundaryIndexes(begin, boundaries, end, points) + boundaryIndexesIndex = 0 + while boundaryIndexesIndex < len(boundaryIndexes) - 1: + if boundaryIndexes[boundaryIndexesIndex + 1] == boundaryIndexes[boundaryIndexesIndex]: + loopFirst = boundaries[boundaryIndexes[boundaryIndexesIndex]] + pathBetween = self.getPathBetween(loopFirst, points[boundaryIndexesIndex : boundaryIndexesIndex + 4]) + begin = points[boundaryIndexesIndex] + end = points[boundaryIndexesIndex + 3] + pathBetween = self.getInsidePointsAlong(begin, pathBetween[0], points) + pathBetween + pathBetween += self.getInsidePointsAlong(end, pathBetween[-1], points) + aroundBetweenLineSegment += pathBetween + boundaryIndexesIndex += 2 + else: + boundaryIndexesIndex += 1 + return aroundBetweenLineSegment + + def getAroundBetweenPath(self, begin, end): + 'Get the path around the loops in the way of the original line segment.' + aroundBetweenPath = [] +# betweens = self.getBetweens() + boundaries = self.getBoundaries() + boundarySegments = self.getBoundarySegments(begin, boundaries, end) + for boundarySegmentIndex, boundarySegment in enumerate(boundarySegments): + segment = boundarySegment.segment + if boundarySegmentIndex < len(boundarySegments) - 1 and self.runningJumpSpace > 0.0: + segment = boundarySegment.getSegment(boundarySegmentIndex, boundarySegments, self.edgeWidth, self.runningJumpSpace) + aroundBetweenPath += self.getAroundBetweenLineSegment(segment[0], boundaries, segment[1]) + if boundarySegmentIndex < len(boundarySegments) - 1: + aroundBetweenPath.append(segment[1]) + aroundBetweenPath.append(boundarySegments[boundarySegmentIndex + 1].segment[0]) + for pointIndex in xrange(len(aroundBetweenPath) - 1, -1, -1): + pointBefore = begin + beforeIndex = pointIndex - 1 + if beforeIndex >= 0: + pointBefore = aroundBetweenPath[beforeIndex] + pointAfter = end + afterIndex = pointIndex + 1 + if afterIndex < len(aroundBetweenPath): + pointAfter = aroundBetweenPath[afterIndex] + if not euclidean.isLineIntersectingLoops(boundaries, pointBefore, pointAfter): + del aroundBetweenPath[pointIndex] + return aroundBetweenPath + +# def getBetweens(self): +# 'Get betweens for the layer.' +# if not self.layerZ in self.betweenTable: +# self.betweenTable[self.layerZ] = [] +# for boundary in self.getBoundaries(): +# self.betweenTable[self.layerZ] += intercircle.getInsetLoopsFromLoop(boundary, self.betweenInset) +# return self.betweenTable[self.layerZ] +# + def getBoundaries(self): + "Get boundaries for the layer." + if self.layerZ in self.layerTable: + return self.layerTable[self.layerZ] + return [] + + def getBoundaryIndexes(self, begin, boundaries, end, points): + 'Get boundary indexes and set the points in the way of the original line segment.' + boundaryIndexes = [] + points.append(begin) + switchX = [] + segment = euclidean.getNormalized(end - begin) + segmentYMirror = complex(segment.real, - segment.imag) + beginRotated = segmentYMirror * begin + endRotated = segmentYMirror * end + y = beginRotated.imag + for boundaryIndex in xrange(len(boundaries)): + boundary = boundaries[boundaryIndex] + boundaryRotated = euclidean.getRotatedComplexes(segmentYMirror, boundary) + euclidean.addXIntersectionIndexesFromLoopY(boundaryRotated, boundaryIndex, switchX, y) + switchX.sort() + maximumX = max(beginRotated.real, endRotated.real) + minimumX = min(beginRotated.real, endRotated.real) + for xIntersection in switchX: + if xIntersection.x > minimumX and xIntersection.x < maximumX: + point = segment * complex(xIntersection.x, y) + points.append(point) + boundaryIndexes.append(xIntersection.index) + points.append(end) + return boundaryIndexes + + def getBoundarySegments(self, begin, boundaries, end): + 'Get the path broken into boundary segments whenever a different boundary is crossed.' + boundarySegments = [] + boundarySegment = BoundarySegment(begin) + boundarySegments.append(boundarySegment) + points = [] + boundaryIndexes = self.getBoundaryIndexes(begin, boundaries, end, points) + boundaryIndexesIndex = 0 + while boundaryIndexesIndex < len(boundaryIndexes) - 1: + if boundaryIndexes[boundaryIndexesIndex + 1] != boundaryIndexes[boundaryIndexesIndex]: + boundarySegment.boundary = boundaries[boundaryIndexes[boundaryIndexesIndex]] + nextBoundary = boundaries[boundaryIndexes[boundaryIndexesIndex + 1]] + if euclidean.isWiddershins(boundarySegment.boundary) and euclidean.isWiddershins(nextBoundary): + boundarySegment.segment.append(points[boundaryIndexesIndex + 1]) + boundarySegment = BoundarySegment(points[boundaryIndexesIndex + 2]) + boundarySegment.boundary = nextBoundary + boundarySegments.append(boundarySegment) + boundaryIndexesIndex += 1 + boundaryIndexesIndex += 1 + boundarySegment.segment.append(points[-1]) + return boundarySegments + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the comb gcode." + self.runningJumpSpace = repository.runningJumpSpace.value + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[lineIndex] + self.parseBoundariesLayers(line) + for lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getInsidePointsAlong(self, begin, end, points): + 'Get the points along the segment if it is required to keep the path inside the widdershin boundaries.' + segment = end - begin + segmentLength = abs(segment) + if segmentLength < self.quadrupleEdgeWidth: + return [] + segmentHalfPerimeter = self.halfEdgeWidth / segmentLength * segment + justAfterBegin = begin + segmentHalfPerimeter + justBeforeEnd = end - segmentHalfPerimeter + widdershins = self.getWiddershins() + if not euclidean.isLineIntersectingLoops(widdershins, justAfterBegin, justBeforeEnd): + return [] + numberOfSteps = 10 + stepLength = (segmentLength - self.doubleEdgeWidth) / float(numberOfSteps) + for step in xrange(1, numberOfSteps + 1): + along = begin + stepLength * step + if not euclidean.isLineIntersectingLoops(widdershins, along, justBeforeEnd): + return [along] + return [] + + def getPathBetween(self, loop, points): + "Add a path between the edge and the fill." + paths = getPathsByIntersectedLoop(points[1], points[2], loop) + shortestPath = paths[int(euclidean.getPathLength(paths[1]) < euclidean.getPathLength(paths[0]))] + if len(shortestPath) < 2: + return shortestPath + if abs(points[1] - shortestPath[0]) > abs(points[1] - shortestPath[-1]): + shortestPath.reverse() + loopWiddershins = euclidean.isWiddershins(loop) + pathBetween = [] + for pointIndex in xrange(len(shortestPath)): + center = shortestPath[pointIndex] + centerPerpendicular = None + beginIndex = pointIndex - 1 + if beginIndex >= 0: + begin = shortestPath[beginIndex] + centerPerpendicular = intercircle.getWiddershinsByLength(center, begin, self.edgeWidth) + centerEnd = None + endIndex = pointIndex + 1 + if endIndex < len(shortestPath): + end = shortestPath[endIndex] + centerEnd = intercircle.getWiddershinsByLength(end, center, self.edgeWidth) + if centerPerpendicular == None: + centerPerpendicular = centerEnd + elif centerEnd != None: + centerPerpendicular = 0.5 * (centerPerpendicular + centerEnd) + between = None + if centerPerpendicular == None: + between = center + if between == None: + centerSideWiddershins = center + centerPerpendicular + if euclidean.isPointInsideLoop(loop, centerSideWiddershins) == loopWiddershins: + between = centerSideWiddershins + if between == None: + centerSideClockwise = center - centerPerpendicular + if euclidean.isPointInsideLoop(loop, centerSideClockwise) == loopWiddershins: + between = centerSideClockwise + if between == None: + between = center + pathBetween.append(between) + return pathBetween + + def getWiddershins(self): + 'Get widdershins for the layer.' + if self.layerZ in self.widdershinTable: + return self.widdershinTable[self.layerZ] + self.widdershinTable[self.layerZ] = [] + for boundary in self.getBoundaries(): + if euclidean.isWiddershins(boundary): + self.widdershinTable[self.layerZ].append(boundary) + return self.widdershinTable[self.layerZ] + + def parseBoundariesLayers(self, line): + "Parse a gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'M103': + self.boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.addToLoop(location) + elif firstWord == '(': + self.boundaryLoop = None + self.layer = None + self.oldZ = float(splitLine[1]) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('comb') + return + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) +# self.betweenInset = 0.7 * self.edgeWidth + self.doubleEdgeWidth = self.edgeWidth + self.edgeWidth + self.halfEdgeWidth = 0.5 * self.edgeWidth + self.quadrupleEdgeWidth = self.doubleEdgeWidth + self.doubleEdgeWidth + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the comb skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if self.distanceFeedRate.getIsAlteration(line): + return + if firstWord == 'G1': + self.addIfTravel(splitLine) + self.layerZ = self.nextLayerZ + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + elif firstWord == '(': + self.layerCount.printProgressIncrement('comb') + self.nextLayerZ = float(splitLine[1]) + if self.layerZ == None: + self.layerZ = self.nextLayerZ + self.distanceFeedRate.addLineCheckAlteration(line) + + +class DistancePoint: + 'A class to get the distance of the point along a segment inside a loop.' + def __init__(self, begin, loop, runningJumpSpace, segment): + 'Initialize' + self.distance = 0.0 + self.point = begin + steps = 10 + spaceOverSteps = runningJumpSpace / float(steps) + for numerator in xrange(1, steps + 1): + distance = float(numerator) * spaceOverSteps + point = begin + segment * distance + if euclidean.isPointInsideLoop(loop, point): + self.distance = distance + self.point = point + else: + return + + +def main(): + "Display the comb dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py new file mode 100644 index 0000000..d8572d3 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py @@ -0,0 +1,412 @@ +""" +This page is in the table of contents. +Cool is a craft tool to cool the shape. + +Cool works well with a stepper extruder, it does not work well with a DC motor extruder. + +If enabled, before each layer that takes less then "Minimum Layer Time" to print the tool head will orbit around the printed area for 'Minimum Layer Time' minus 'the time it takes to print the layer' before it starts printing the layer. This is great way to let layers with smaller area cool before you start printing on top of them (so you do not overheat the area). + +The cool manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Cool + +Allan Ecker aka The Masked Retriever's has written the "Skeinforge Quicktip: Cool" at: +http://blog.thingiverse.com/2009/07/28/skeinforge-quicktip-cool/ + +==Operation== +The default 'Activate Cool' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Bridge Cool=== +Default is one degree Celcius. + +If the layer is a bridge layer, then cool will lower the temperature by 'Bridge Cool' degrees Celcius. + +===Cool Type=== +Default is 'Slow Down'. + +====Orbit==== +When selected, cool will add orbits with the extruder off to give the layer time to cool, so that the next layer is not extruded on a molten base. The orbits will be around the largest island on that layer. Orbit should only be chosen if you can not upgrade to a stepper extruder. + +====Slow Down==== +When selected, cool will slow down the extruder so that it will take the minimum layer time to extrude the layer. DC motors do not operate properly at slow flow rates, so if you have a DC motor extruder, you should upgrade to a stepper extruder, but if you can't do that, you can try using the 'Orbit' option. + +===Maximum Cool=== +Default is 2 degrees Celcius. + +If it takes less time to extrude the layer than the minimum layer time, then cool will lower the temperature by the 'Maximum Cool' setting times the layer time over the minimum layer time. + +===Minimum Layer Time=== +Default is 60 seconds. + +Defines the minimum amount of time the extruder will spend on a layer, this is an important setting. + +===Minimum Orbital Radius=== +Default is 10 millimeters. + +When the orbit cool type is selected, if the area of the largest island is as large as the square of the "Minimum Orbital Radius" then the orbits will be just within the island. If the island is smaller, then the orbits will be in a square of the "Minimum Orbital Radius" around the center of the island. This is so that the hot extruder does not stay too close to small islands. + +===Name of Alteration Files=== +Cool looks for alteration files in the alterations folder in the .skeinforge folder in the home directory. Cool does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. The cool start and end text idea is from: +http://makerhahn.blogspot.com/2008/10/yay-minimug.html + +====Name of Cool End File==== +Default is cool_end.gcode. + +If there is a file with the name of the "Name of Cool End File" setting, it will be added to the end of the orbits. + +====Name of Cool Start File==== +Default is cool_start.gcode. + +If there is a file with the name of the "Name of Cool Start File" setting, it will be added to the start of the orbits. + +===Orbital Outset=== +Default is 2 millimeters. + +When the orbit cool type is selected, the orbits will be outset around the largest island by 'Orbital Outset' millimeters. If 'Orbital Outset' is negative, the orbits will be inset instead. + +===Turn Fan On at Beginning=== +Default is on. + +When selected, cool will turn the fan on at the beginning of the fabrication by adding the M106 command. + +===Turn Fan Off at Ending=== +Default is on. + +When selected, cool will turn the fan off at the ending of the fabrication by adding the M107 command. + +==Examples== +The following examples cool the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and cool.py. + +> python cool.py +This brings up the cool dialog. + +> python cool.py Screw Holder Bottom.stl +The cool tool is parsing the file: +Screw Holder Bottom.stl +.. +The cool tool has created the file: +.. Screw Holder Bottom_cool.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text, repository=None): + 'Cool a gcode linear move text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Cool a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'cool'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(CoolRepository()) + if not repository.activateCool.value: + return gcodeText + return CoolSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return CoolRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Cool a gcode linear move file. Chain cool the gcode if it is not already cooled.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'cool', shouldAnalyze) + + +class CoolRepository: + 'A class to handle the cool settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.cool.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Cool', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute( + 'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Cool') + self.activateCool = settings.BooleanSetting().getFromValue('Activate Cool', self, True) + self.bridgeCool = settings.FloatSpin().getFromValue(0.0, 'Bridge Cool (Celcius):', self, 10.0, 1.0) + self.coolType = settings.MenuButtonDisplay().getFromName('Cool Type:', self) + self.orbit = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Orbit', self, False) + self.slowDown = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Slow Down', self, True) + self.maximumCool = settings.FloatSpin().getFromValue(0.0, 'Maximum Cool (Celcius):', self, 10.0, 2.0) + self.minimumLayerTime = settings.FloatSpin().getFromValue(0.0, 'Minimum Layer Time (seconds):', self, 120.0, 10.0) + self.minimumOrbitalRadius = settings.FloatSpin().getFromValue( + 0.0, 'Minimum Orbital Radius (millimeters):', self, 20.0, 10.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Name of Alteration Files -', self ) + self.nameOfCoolEndFile = settings.StringSetting().getFromValue('Name of Cool End File:', self, 'cool_end.gcode') + self.nameOfCoolStartFile = settings.StringSetting().getFromValue('Name of Cool Start File:', self, 'cool_start.gcode') + settings.LabelSeparator().getFromRepository(self) + self.orbitalOutset = settings.FloatSpin().getFromValue(1.0, 'Orbital Outset (millimeters):', self, 5.0, 2.0) + self.turnFanOnAtBeginning = settings.BooleanSetting().getFromValue('Turn Fan On at Beginning', self, True) + self.turnFanOffAtEnding = settings.BooleanSetting().getFromValue('Turn Fan Off at Ending', self, True) + self.executeTitle = 'Cool' + + def execute(self): + 'Cool button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( + self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class CoolSkein: + 'A class to cool a skein of extrusions.' + def __init__(self): + self.boundaryLayer = None + self.coolTemperature = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 960.0 + self.highestZ = 1.0 + self.isBridgeLayer = False + self.isExtruderActive = False + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.lines = None + self.multiplier = 1.0 + self.oldFlowRate = None + self.oldFlowRateString = None + self.oldLocation = None + self.oldTemperature = None + + def addCoolOrbits(self, remainingOrbitTime): + 'Add the minimum radius cool orbits.' + if len(self.boundaryLayer.loops) < 1: + return + insetBoundaryLoops = self.boundaryLayer.loops + if abs(self.repository.orbitalOutset.value) > 0.1 * abs(self.edgeWidth): + insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(self.boundaryLayer.loops, -self.repository.orbitalOutset.value) + if len(insetBoundaryLoops) < 1: + insetBoundaryLoops = self.boundaryLayer.loops + largestLoop = euclidean.getLargestLoop(insetBoundaryLoops) + loopArea = euclidean.getAreaLoopAbsolute(largestLoop) + if loopArea < self.minimumArea: + center = 0.5 * (euclidean.getMaximumByComplexPath(largestLoop) + euclidean.getMinimumByComplexPath(largestLoop)) + centerXBounded = max(center.real, self.boundingRectangle.cornerMinimum.real) + centerXBounded = min(centerXBounded, self.boundingRectangle.cornerMaximum.real) + centerYBounded = max(center.imag, self.boundingRectangle.cornerMinimum.imag) + centerYBounded = min(centerYBounded, self.boundingRectangle.cornerMaximum.imag) + center = complex(centerXBounded, centerYBounded) + maximumCorner = center + self.halfCorner + minimumCorner = center - self.halfCorner + largestLoop = euclidean.getSquareLoopWiddershins(minimumCorner, maximumCorner) + pointComplex = euclidean.getXYComplexFromVector3(self.oldLocation) + if pointComplex != None: + largestLoop = euclidean.getLoopStartingClosest(self.edgeWidth, pointComplex, largestLoop) + intercircle.addOrbitsIfLarge( + self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, remainingOrbitTime, self.highestZ) + + def addCoolTemperature(self, remainingOrbitTime): + 'Parse a gcode line and add it to the cool skein.' + layerCool = self.repository.maximumCool.value * remainingOrbitTime / self.repository.minimumLayerTime.value + if self.isBridgeLayer: + layerCool = max(self.repository.bridgeCool.value, layerCool) + if self.oldTemperature != None and layerCool != 0.0: + self.coolTemperature = self.oldTemperature - layerCool + self.addTemperature(self.coolTemperature) + + def addFlowRate(self, flowRate): + 'Add a multipled line of flow rate if different.' + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + + def addGcodeFromFeedRateMovementZ(self, feedRateMinute, point, z): + 'Add a movement to the output.' + self.distanceFeedRate.addLine(self.distanceFeedRate.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z)) + + def addOrbitsIfNecessary(self, remainingOrbitTime): + 'Parse a gcode line and add it to the cool skein.' + if remainingOrbitTime > 0.0 and self.boundaryLayer != None: + self.addCoolOrbits(remainingOrbitTime) + + def addTemperature(self, temperature): + 'Add a line of temperature.' + self.distanceFeedRate.addLine('M104 S' + euclidean.getRoundedToThreePlaces(temperature)) + + def getCoolMove(self, line, location, splitLine): + 'Get cool line according to time spent on layer.' + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + return self.distanceFeedRate.getLineWithFeedRate(self.multiplier * self.feedRateMinute, line, splitLine) + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the cool gcode.' + self.repository = repository + self.coolEndLines = settings.getAlterationFileLines(repository.nameOfCoolEndFile.value) + self.coolStartLines = settings.getAlterationFileLines(repository.nameOfCoolStartFile.value) + self.halfCorner = complex(repository.minimumOrbitalRadius.value, repository.minimumOrbitalRadius.value) + self.lines = archive.getTextLines(gcodeText) + self.minimumArea = 4.0 * repository.minimumOrbitalRadius.value * repository.minimumOrbitalRadius.value + self.parseInitialization() + self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines(self.lines[self.lineIndex :], 0.5 * self.edgeWidth) + margin = 0.2 * self.edgeWidth + halfCornerMargin = self.halfCorner + complex(margin, margin) + self.boundingRectangle.cornerMaximum -= halfCornerMargin + self.boundingRectangle.cornerMinimum += halfCornerMargin + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + if repository.turnFanOffAtEnding.value: + self.distanceFeedRate.addLine('M107') + return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) + + def getLayerTime(self): + 'Get the time the extruder spends on the layer.' + feedRateMinute = self.feedRateMinute + layerTime = 0.0 + lastThreadLocation = self.oldLocation + for lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(lastThreadLocation, splitLine) + feedRateMinute = gcodec.getFeedRateMinute(feedRateMinute, splitLine) + if lastThreadLocation != None: + feedRateSecond = feedRateMinute / 60.0 + layerTime += location.distance(lastThreadLocation) / feedRateSecond + lastThreadLocation = location + elif firstWord == '(': + self.isBridgeLayer = True + elif firstWord == '()': + return layerTime + return layerTime + + def getLayerTimeActive(self): + 'Get the time the extruder spends on the layer while active.' + feedRateMinute = self.feedRateMinute + isExtruderActive = self.isExtruderActive + layerTime = 0.0 + lastThreadLocation = self.oldLocation + for lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(lastThreadLocation, splitLine) + feedRateMinute = gcodec.getFeedRateMinute(feedRateMinute, splitLine) + if lastThreadLocation != None and isExtruderActive: + feedRateSecond = feedRateMinute / 60.0 + layerTime += location.distance(lastThreadLocation) / feedRateSecond + lastThreadLocation = location + elif firstWord == 'M101': + isExtruderActive = True + elif firstWord == 'M103': + isExtruderActive = False + elif firstWord == '(': + self.isBridgeLayer = True + elif firstWord == '()': + return layerTime + return layerTime + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == 'M108': + self.oldFlowRate = float(splitLine[1][1 :]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + if self.repository.turnFanOnAtBeginning.value: + self.distanceFeedRate.addLine('M106') + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('cool') + return + elif firstWord == '(': + self.oldFlowRate = float(splitLine[1]) + elif firstWord == '(': + self.orbitalFeedRatePerSecond = float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the cool skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.highestZ = max(location.z, self.highestZ) + if self.isExtruderActive: + line = self.getCoolMove(line, location, splitLine) + self.oldLocation = location + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + elif firstWord == 'M104': + self.oldTemperature = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + elif firstWord == 'M108': + self.oldFlowRate = float(splitLine[1][1 :]) + self.addFlowRate(self.multiplier * self.oldFlowRate) + return + elif firstWord == '(': + self.boundaryLoop.append(gcodec.getLocationFromSplitLine(None, splitLine).dropAxis()) + elif firstWord == '(': + self.layerCount.printProgressIncrement('cool') + self.distanceFeedRate.addLine(line) + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.coolStartLines) + layerTime = self.getLayerTime() + remainingOrbitTime = max(self.repository.minimumLayerTime.value - layerTime, 0.0) + self.addCoolTemperature(remainingOrbitTime) + if self.repository.orbit.value: + self.addOrbitsIfNecessary(remainingOrbitTime) + else: + self.setMultiplier(remainingOrbitTime) + if self.oldFlowRate != None: + self.addFlowRate(self.multiplier * self.oldFlowRate) + z = float(splitLine[1]) + self.boundaryLayer = euclidean.LoopLayer(z) + self.highestZ = max(z, self.highestZ) + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.coolEndLines) + return + elif firstWord == '()': + self.isBridgeLayer = False + self.multiplier = 1.0 + if self.coolTemperature != None: + self.addTemperature(self.oldTemperature) + self.coolTemperature = None + if self.oldFlowRate != None: + self.addFlowRate(self.oldFlowRate) + elif firstWord == '()': + self.boundaryLoop = [] + self.boundaryLayer.loops.append(self.boundaryLoop) + self.distanceFeedRate.addLine(line) + + def setMultiplier(self, remainingOrbitTime): + 'Set the feed and flow rate multiplier.' + layerTimeActive = self.getLayerTimeActive() + self.multiplier = min(1.0, layerTimeActive / (remainingOrbitTime + layerTimeActive)) + + + +def main(): + 'Display the cool dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dimension.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dimension.py new file mode 100644 index 0000000..6eee347 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dimension.py @@ -0,0 +1,409 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Dimension adds Adrian's extruder distance E value so firmware does not have to calculate it on it's own and can set the extruder speed in relation to the distance that needs to be extruded. Some printers don't support this. Extruder distance is described at: + +http://blog.reprap.org/2009/05/4d-printing.html + +and in Erik de Bruijn's conversion script page at: + +http://objects.reprap.org/wiki/3D-to-5D-Gcode.php + +The dimension manual page is at: + +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension + +Nophead wrote an excellent article on how to set the filament parameters: + +http://hydraraptor.blogspot.com/2011/03/spot-on-flow-rate.html + +==Operation== +The default 'Activate Dimension' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Extrusion Distance Format Choice=== +Default is 'Absolute Extrusion Distance' because in Adrian's description the distance is absolute. In future, because the relative distances are smaller than the cumulative absolute distances, hopefully the firmware will be able to use relative distance. + +====Absolute Extrusion Distance==== +When selected, the extrusion distance output will be the total extrusion distance to that gcode line. + +====Relative Extrusion Distance==== +When selected, the extrusion distance output will be the extrusion distance from the last gcode line. + +===Extruder Retraction Speed=== +Default is 13.3 mm/s. + +Defines the extruder retraction feed rate. A high value will allow the retraction operation to complete before much material oozes out. If your extruder can handle it, this value should be much larger than your feed rate. + +As an example, I have a feed rate of 48 mm/s and a 'Extruder Retraction Speed' of 150 mm/s. + +===Filament=== +====Filament Diameter==== +Default is 2.8 millimeters. + +Defines the filament diameter. + +====Filament Packing Density==== +Default is 0.85. This is for ABS. + +Defines the effective filament packing density. + +The default value is so low for ABS because ABS is relatively soft and with a pinch wheel extruder the teeth of the pinch dig in farther, so it sees a smaller effective diameter. With a hard plastic like PLA the teeth of the pinch wheel don't dig in as far, so it sees a larger effective diameter, so feeds faster, so for PLA the value should be around 0.97. This is with Wade's hobbed bolt. The effect is less significant with larger pinch wheels. + +Overall, you'll have to find the optimal filament packing density by experiment. + +===Maximum E Value before Reset=== +Default: 91234.0 + +Defines the maximum E value before it is reset with the 'G92 E0' command line. The reason it is reset only after the maximum E value is reached is because at least one firmware takes time to reset. The problem with waiting until the E value is high before resetting is that more characters are sent. So if your firmware takes a lot of time to reset, set this parameter to a high value, if it doesn't set this parameter to a low value or even zero. + +===Minimum Travel for Retraction=== +Default: 1.0 millimeter + +Defines the minimum distance that the extruder head has to travel from the end of one thread to the beginning of another, in order to trigger the extruder retraction. Setting this to a high value means the extruder will retract only occasionally, setting it to a low value means the extruder will retract most of the time. + +===Retract Within Island=== +Default is off. + +When selected, retraction will work even when the next thread is within the same island. If it is not selected, retraction will only work when crossing a boundary. + +===Retraction Distance=== +Default is zero. + +Defines the amount the extruder retracts (sucks back) the extruded filament whenever an extruder stop is commanded. Using this seems to help prevent stringing. e.g. If set to 10 the extruder reverses the distance required to pull back 10mm of filament. In fact this does not actually happen but if you set this distance by trial and error you can get to a point where there is very little ooze from the extruder when it stops which is not normally the case. + +===Restart Extra Distance=== +Default is zero. + +Defines the restart extra distance when the thread restarts. The restart distance will be the retraction distance plus the restart extra distance. + +If this is greater than zero when the extruder starts this distance is added to the retract value giving extra filament. It can be a negative value in which case it is subtracted from the retraction distance. On some Repstrap machines a negative value can stop the build up of plastic that can occur at the start of edges. + +==Examples== +The following examples dimension the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and dimension.py. + +> python dimension.py +This brings up the dimension dialog. + +> python dimension.py Screw Holder Bottom.stl +The dimension tool is parsing the file: +Screw Holder Bottom.stl +.. +The dimension tool has created the file: +.. Screw Holder Bottom_dimension.gcode + +""" + +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from datetime import date +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + 'Dimension a gcode file or text.' + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Dimension a gcode text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'dimension'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( DimensionRepository() ) + if not repository.activateDimension.value: + return gcodeText + return DimensionSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return DimensionRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Dimension a gcode file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'dimension', shouldAnalyze) + + +class DimensionRepository: + 'A class to handle the dimension settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dimension.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dimension', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension') + self.activateDimension = settings.BooleanSetting().getFromValue('Activate Dimension', self, True ) + extrusionDistanceFormatLatentStringVar = settings.LatentStringVar() + self.extrusionDistanceFormatChoiceLabel = settings.LabelDisplay().getFromName('Extrusion Distance Format Choice: ', self ) + settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Absolute Extrusion Distance', self, True ) + self.relativeExtrusionDistance = settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Relative Extrusion Distance', self, False ) + self.extruderRetractionSpeed = settings.FloatSpin().getFromValue( 4.0, 'Extruder Retraction Speed (mm/s):', self, 34.0, 13.3 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Filament -', self ) + self.filamentDiameter = settings.FloatSpin().getFromValue(1.0, 'Filament Diameter (mm):', self, 6.0, 2.89) + self.filamentPackingDensity = settings.FloatSpin().getFromValue(0.7, 'Filament Packing Density (ratio):', self, 1.0, 1.0) + settings.LabelSeparator().getFromRepository(self) + self.maximumEValueBeforeReset = settings.FloatSpin().getFromValue(0.0, 'Maximum E Value before Reset (float):', self, 999999.9, 91234.0) + self.minimumTravelForRetraction = settings.FloatSpin().getFromValue(0.0, 'Minimum Travel for Retraction (millimeters):', self, 2.0, 1.0) + self.retractWithinIsland = settings.BooleanSetting().getFromValue('Retract Within Island', self, False) + self.retractionDistance = settings.FloatSpin().getFromValue( 0.0, 'Retraction Distance (millimeters):', self, 100.0, 0.0 ) + self.restartExtraDistance = settings.FloatSpin().getFromValue( 0.0, 'Restart Extra Distance (millimeters):', self, 100.0, 0.0 ) + self.executeTitle = 'Dimension' + + def execute(self): + 'Dimension button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class DimensionSkein: + 'A class to dimension a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.absoluteDistanceMode = True + self.boundaryLayers = [] + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = None + self.isExtruderActive = False + self.layerIndex = -1 + self.lineIndex = 0 + self.maximumZFeedRatePerSecond = None + self.oldLocation = None + self.operatingFlowRate = None + self.retractionRatio = 1.0 + self.totalExtrusionDistance = 0.0 + self.travelFeedRatePerSecond = None + self.zDistanceRatio = 5.0 + + def addLinearMoveExtrusionDistanceLine(self, extrusionDistance): + 'Get the extrusion distance string from the extrusion distance.' + if self.repository.extruderRetractionSpeed.value != 0.0: + self.distanceFeedRate.output.write('G1 F%s\n' % self.extruderRetractionSpeedMinuteString) + self.distanceFeedRate.output.write('G1%s\n' % self.getExtrusionDistanceStringFromExtrusionDistance(extrusionDistance)) + self.distanceFeedRate.output.write('G1 F%s\n' % self.distanceFeedRate.getRounded(self.feedRateMinute)) + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the dimension gcode.' + self.repository = repository + filamentRadius = 0.5 * repository.filamentDiameter.value + filamentPackingArea = math.pi * filamentRadius * filamentRadius * repository.filamentPackingDensity.value + self.minimumTravelForRetraction = self.repository.minimumTravelForRetraction.value + self.doubleMinimumTravelForRetraction = self.minimumTravelForRetraction + self.minimumTravelForRetraction + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + if not self.repository.retractWithinIsland.value: + self.parseBoundaries() + self.flowScaleSixty = 60.0 * self.layerHeight * self.edgeWidth / filamentPackingArea + self.restartDistance = self.repository.retractionDistance.value + self.repository.restartExtraDistance.value + self.extruderRetractionSpeedMinuteString = self.distanceFeedRate.getRounded(60.0 * self.repository.extruderRetractionSpeed.value) + if self.maximumZFeedRatePerSecond != None and self.travelFeedRatePerSecond != None: + self.zDistanceRatio = self.travelFeedRatePerSecond / self.maximumZFeedRatePerSecond + for lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine( lineIndex ) + return self.distanceFeedRate.output.getvalue() + + def getDimensionedArcMovement(self, line, splitLine): + 'Get a dimensioned arc movement.' + if self.oldLocation == None: + return line + relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation += relativeLocation + distance = gcodec.getArcDistance(relativeLocation, splitLine) + return line + self.getExtrusionDistanceString(distance, splitLine) + + def getDimensionedLinearMovement( self, line, splitLine ): + 'Get a dimensioned linear movement.' + distance = 0.0 + if self.absoluteDistanceMode: + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.oldLocation != None: + distance = abs( location - self.oldLocation ) + self.oldLocation = location + else: + if self.oldLocation == None: + print('Warning: There was no absolute location when the G91 command was parsed, so the absolute location will be set to the origin.') + self.oldLocation = Vector3() + location = gcodec.getLocationFromSplitLine(None, splitLine) + distance = abs( location ) + self.oldLocation += location + return line + self.getExtrusionDistanceString( distance, splitLine ) + + def getDistanceToNextThread(self, lineIndex): + 'Get the travel distance to the next thread.' + if self.oldLocation == None: + return None + isActive = False + location = self.oldLocation + for afterIndex in xrange(lineIndex + 1, len(self.lines)): + line = self.lines[afterIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + if isActive: + if not self.repository.retractWithinIsland.value: + locationEnclosureIndex = self.getSmallestEnclosureIndex(location.dropAxis()) + if locationEnclosureIndex != self.getSmallestEnclosureIndex(self.oldLocation.dropAxis()): + return None + locationMinusOld = location - self.oldLocation + xyTravel = abs(locationMinusOld.dropAxis()) + zTravelMultiplied = locationMinusOld.z * self.zDistanceRatio + return math.sqrt(xyTravel * xyTravel + zTravelMultiplied * zTravelMultiplied) + location = gcodec.getLocationFromSplitLine(location, splitLine) + elif firstWord == 'M101': + isActive = True + elif firstWord == 'M103': + isActive = False + return None + + def getExtrusionDistanceString( self, distance, splitLine ): + 'Get the extrusion distance string.' + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + if not self.isExtruderActive: + return '' + if distance == 0.0: + return '' + if distance < 0.0: + print('Warning, the distance is less than zero in getExtrusionDistanceString in dimension; so there will not be an E value') + print(distance) + print(splitLine) + return '' + if self.operatingFlowRate == None: + return self.getExtrusionDistanceStringFromExtrusionDistance(self.flowScaleSixty / 60.0 * distance) + else: + scaledFlowRate = self.flowRate * self.flowScaleSixty + return self.getExtrusionDistanceStringFromExtrusionDistance(scaledFlowRate / self.feedRateMinute * distance) + + def getExtrusionDistanceStringFromExtrusionDistance(self, extrusionDistance): + 'Get the extrusion distance string from the extrusion distance.' + if self.repository.relativeExtrusionDistance.value: + return ' E' + self.distanceFeedRate.getRounded(extrusionDistance) + self.totalExtrusionDistance += extrusionDistance + return ' E' + self.distanceFeedRate.getRounded(self.totalExtrusionDistance) + + def getRetractionRatio(self, lineIndex): + 'Get the retraction ratio.' + distanceToNextThread = self.getDistanceToNextThread(lineIndex) + if distanceToNextThread == None: + return 1.0 + if distanceToNextThread >= self.doubleMinimumTravelForRetraction: + return 1.0 + if distanceToNextThread <= self.minimumTravelForRetraction: + return 0.0 + return (distanceToNextThread - self.minimumTravelForRetraction) / self.minimumTravelForRetraction + + def getSmallestEnclosureIndex(self, point): + 'Get the index of the smallest boundary loop which encloses the point.' + boundaryLayer = self.boundaryLayers[self.layerIndex] + for loopIndex, loop in enumerate(boundaryLayer.loops): + if euclidean.isPointInsideLoop(loop, point): + return loopIndex + return None + + def parseBoundaries(self): + 'Parse the boundaries and add them to the boundary layers.' + boundaryLoop = None + boundaryLayer = None + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '()': + boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if boundaryLoop == None: + boundaryLoop = [] + boundaryLayer.loops.append(boundaryLoop) + boundaryLoop.append(location.dropAxis()) + elif firstWord == '(': + boundaryLayer = euclidean.LoopLayer(float(splitLine[1])) + self.boundaryLayers.append(boundaryLayer) + for boundaryLayer in self.boundaryLayers: + triangle_mesh.sortLoopsInOrderOfArea(False, boundaryLayer.loops) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('dimension') + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.maximumZFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.maximumZFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.feedRateMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.operatingFlowRate = float(splitLine[1]) + self.flowRate = self.operatingFlowRate + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + elif firstWord == '(': + self.travelFeedRatePerSecond = float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine( self, lineIndex ): + 'Parse a gcode line and add it to the dimension skein.' + line = self.lines[lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G2' or firstWord == 'G3': + line = self.getDimensionedArcMovement( line, splitLine ) + if firstWord == 'G1': + line = self.getDimensionedLinearMovement( line, splitLine ) + if firstWord == 'G90': + self.absoluteDistanceMode = True + elif firstWord == 'G91': + self.absoluteDistanceMode = False + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'dimension') + elif firstWord == 'M101': + self.addLinearMoveExtrusionDistanceLine(self.restartDistance * self.retractionRatio) + if self.totalExtrusionDistance > self.repository.maximumEValueBeforeReset.value: + if not self.repository.relativeExtrusionDistance.value: + self.distanceFeedRate.addLine('G92 E0') + self.totalExtrusionDistance = 0.0 + self.isExtruderActive = True + elif firstWord == 'M103': + self.retractionRatio = self.getRetractionRatio(lineIndex) + self.addLinearMoveExtrusionDistanceLine(-self.repository.retractionDistance.value * self.retractionRatio) + self.isExtruderActive = False + elif firstWord == 'M108': + self.flowRate = float( splitLine[1][1 :] ) + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the dimension dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/drill.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/drill.py new file mode 100644 index 0000000..6efeec6 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/drill.py @@ -0,0 +1,258 @@ +""" +This page is in the table of contents. +Drill is a script to drill down small holes. + +==Operation== +The default 'Activate Drill' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Drilling Margin=== +The drill script will move the tool from the top of the hole plus the 'Drilling Margin on Top', to the bottom of the hole minus the 'Drilling Margin on Bottom'. + +===Drilling Margin on Top=== +Default is three millimeters. + +===Drilling Margin on Bottom=== +Default is one millimeter. + +==Examples== +The following examples drill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and drill.py. + +> python drill.py +This brings up the drill dialog. + +> python drill.py Screw Holder Bottom.stl +The drill tool is parsing the file: +Screw Holder Bottom.stl +.. +The drill tool has created the file: +.. Screw Holder Bottom_drill.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +def getCraftedText( fileName, text, repository=None): + "Drill a gcode linear move file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Drill a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'drill'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( DrillRepository() ) + if not repository.activateDrill.value: + return gcodeText + return DrillSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return DrillRepository() + +def getPolygonCenter( polygon ): + "Get the centroid of a polygon." + pointSum = complex() + areaSum = 0.0 + for pointIndex in xrange( len( polygon ) ): + pointBegin = polygon[pointIndex] + pointEnd = polygon[ (pointIndex + 1) % len( polygon ) ] + area = pointBegin.real * pointEnd.imag - pointBegin.imag * pointEnd.real + areaSum += area + pointSum += complex( pointBegin.real + pointEnd.real, pointBegin.imag + pointEnd.imag ) * area + return pointSum / 3.0 / areaSum + +def writeOutput(fileName, shouldAnalyze=True): + "Drill a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'drill', shouldAnalyze) + + +class ThreadLayer: + "A layer of loops and paths." + def __init__( self, z ): + "Thread layer constructor." + self.points = [] + self.z = z + + def __repr__(self): + "Get the string representation of this thread layer." + return '%s, %s' % ( self.z, self.points ) + + +class DrillRepository: + "A class to handle the drill settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.drill.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Drill', self, '') + self.activateDrill = settings.BooleanSetting().getFromValue('Activate Drill', self, True ) + self.drillingMarginOnBottom = settings.FloatSpin().getFromValue( 0.0, 'Drilling Margin on Bottom (millimeters):', self, 5.0, 1.0 ) + self.drillingMarginOnTop = settings.FloatSpin().getFromValue( 0.0, 'Drilling Margin on Top (millimeters):', self, 20.0, 3.0 ) + self.executeTitle = 'Drill' + + def execute(self): + "Drill button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class DrillSkein: + "A class to drill a skein of extrusions." + def __init__(self): + self.boundary = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.halfLayerThickness = 0.4 + self.isDrilled = False + self.lineIndex = 0 + self.lines = None + self.maximumDistance = 0.06 + self.oldLocation = None + self.threadLayer = None + self.threadLayers = [] + + def addDrillHoles(self): + "Parse a gcode line." + self.isDrilled = True + if len( self.threadLayers ) < 1: + return + topThreadLayer = self.threadLayers[0] + drillPoints = topThreadLayer.points + for drillPoint in drillPoints: + zTop = topThreadLayer.z + self.halfLayerThickness + self.repository.drillingMarginOnTop.value + drillingCenterDepth = self.getDrillingCenterDepth( topThreadLayer.z, drillPoint ) + zBottom = drillingCenterDepth - self.halfLayerThickness - self.repository.drillingMarginOnBottom.value + self.addGcodeFromVerticalThread( drillPoint, zTop, zBottom ) + + def addGcodeFromVerticalThread( self, point, zBegin, zEnd ): + "Add a thread to the output." + self.distanceFeedRate.addGcodeMovementZ( point, zBegin ) + self.distanceFeedRate.addLine('M101') # Turn extruder on. + self.distanceFeedRate.addGcodeMovementZ( point, zEnd ) + self.distanceFeedRate.addLine('M103') # Turn extruder off. + + def addThreadLayerIfNone(self): + "Add a thread layer if it is none." + if self.threadLayer != None: + return + self.threadLayer = ThreadLayer( self.layerZ ) + self.threadLayers.append( self.threadLayer ) + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the drill gcode." + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseNestedRing(line) + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getDrillingCenterDepth( self, drillingCenterDepth, drillPoint ): + "Get the drilling center depth." + for threadLayer in self.threadLayers[1 :]: + if self.isPointClose( drillPoint, threadLayer.points ): + drillingCenterDepth = threadLayer.z + else: + return drillingCenterDepth + return drillingCenterDepth + + def isPointClose( self, drillPoint, points ): + "Determine if a point on the thread layer is close." + for point in points: + if abs( point - drillPoint ) < self.maximumDistance: + return True + return False + + def linearMove( self, splitLine ): + "Add a linear move to the loop." + self.addThreadLayerIfNone() + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.extruderActive: + self.boundary = None + self.oldLocation = location + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('drill') + return + elif firstWord == '(': + self.halfLayerThickness = 0.5 * float(splitLine[1]) + elif firstWord == '(': + self.maximumDistance = 0.1 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + self.distanceFeedRate.addLine(line) + if firstWord == '(': + if not self.isDrilled: + self.addDrillHoles() + + def parseNestedRing(self, line): + "Parse a nested ring." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + if firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if self.boundary == None: + self.boundary = [] + self.boundary.append(location.dropAxis()) + elif firstWord == '(': + self.layerZ = float(splitLine[1]) + self.threadLayer = None + elif firstWord == '()': + self.addThreadLayerIfNone() + elif firstWord == '()': + if self.boundary != None: + self.threadLayer.points.append( getPolygonCenter( self.boundary ) ) + self.boundary = None + + +def main(): + "Display the drill dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dwindle.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dwindle.py new file mode 100644 index 0000000..05c1b59 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/dwindle.py @@ -0,0 +1,270 @@ +""" +This page is in the table of contents. +Dwindle is a plugin to smooth the surface dwindle of an object by replacing the edge surface with a surface printed at a fraction of the carve +height. This gives the impression that the object was carved at a much thinner height giving a high-quality finish, but still prints +in a relatively short time. The latest process has some similarities with a description at: + +The dwindle manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle + +==Operation== +The default 'Activate Dwindle' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +====Vertical Divisions==== +Default: 2 + +Defines the number of times the dwindle infill and edges are divided vertically. + +==Examples== +The following examples dwindle the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and dwindle.py. + +> python dwindle.py +This brings up the dwindle dialog. + +> python dwindle.py Screw Holder Bottom.stl +The dwindle tool is parsing the file: +Screw Holder Bottom.stl +.. +The dwindle tool has created the file: +.. Screw Holder Bottom_dwindle.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique aht yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, gcodeText, repository=None): + 'Dwindle a gcode linear move text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, gcodeText), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Dwindle a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'dwindle'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(DwindleRepository()) + if not repository.activateDwindle.value: + return gcodeText + return DwindleSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return DwindleRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Dwindle a gcode linear move file. Chain dwindle the gcode if it is not already dwindle.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'dwindle', shouldAnalyze) + + +class DwindleRepository: + 'A class to handle the dwindle settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dwindle.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dwindle', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle') + self.activateDwindle = settings.BooleanSetting().getFromValue('Activate Dwindle', self, False) + settings.LabelSeparator().getFromRepository(self) + self.endRateMultiplier = settings.FloatSpin().getFromValue(0.4, 'End Rate Multiplier (ratio):', self, 0.8, 0.5) + self.pentUpVolume = settings.FloatSpin().getFromValue(0.1, 'Pent Up Volume (cubic millimeters):', self, 1.0, 0.4) + self.slowdownSteps = settings.IntSpin().getFromValue(2, 'Slowdown Steps (positive integer):', self, 10, 3) + self.slowdownVolume = settings.FloatSpin().getFromValue(0.4, 'Slowdown Volume (cubic millimeters):', self, 4.0, 2.0) + self.executeTitle = 'Dwindle' + + def execute(self): + 'Dwindle button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class DwindleSkein: + 'A class to dwindle a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 959.0 + self.isActive = False + self.layerIndex = -1 + self.lineIndex = 0 + self.lines = None + self.oldFlowRate = None + self.oldLocation = None + self.threadSections = [] + + def addThread(self): + 'Add the thread sections to the gcode.' + if len(self.threadSections) == 0: + return + area = self.area + dwindlePortion = 0.0 + endRateMultiplier = self.repository.endRateMultiplier.value + halfOverSteps = self.halfOverSteps + oneOverSteps = self.oneOverSteps + currentPentUpVolume = self.repository.pentUpVolume.value * self.oldFlowRate / self.operatingFlowRate + slowdownFlowRateMultiplier = 1.0 - (currentPentUpVolume / self.repository.slowdownVolume.value) + operatingFeedRateMinute = self.operatingFeedRateMinute + slowdownVolume = self.repository.slowdownVolume.value + for threadSectionIndex in xrange(len(self.threadSections) - 1, -1, -1): + threadSection = self.threadSections[threadSectionIndex] + dwindlePortion = threadSection.getDwindlePortion(area, dwindlePortion, operatingFeedRateMinute, self.operatingFlowRate, slowdownVolume) + for threadSection in self.threadSections: + threadSection.addGcodeThreadSection(self.distanceFeedRate, endRateMultiplier, halfOverSteps, oneOverSteps, slowdownFlowRateMultiplier) + self.distanceFeedRate.addFlowRateLine(self.oldFlowRate) + self.threadSections = [] + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the dwindle gcode.' + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.parseInitialization() + self.area = self.infillWidth * self.layerHeight + self.oneOverSteps = 1.0 / float(repository.slowdownSteps.value) + self.halfOverSteps = 0.5 * self.oneOverSteps + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('dwindle') + return + elif firstWord == '(': + self.infillWidth = float(splitLine[1]) + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.operatingFeedRateMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.operatingFlowRate = float(splitLine[1]) + self.oldFlowRate = self.operatingFlowRate + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the dwindle skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.isActive: + self.threadSections.append(ThreadSection(self.feedRateMinute, self.oldFlowRate, location, self.oldLocation)) + self.oldLocation = location + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'dwindle') + elif firstWord == 'M101': + self.isActive = True + elif firstWord == 'M103': + self.isActive = False + self.addThread() + elif firstWord == 'M108': + self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + if len(self.threadSections) == 0: + self.distanceFeedRate.addLine(line) + + +class ThreadSection: + 'A class to handle a volumetric section of a thread.' + def __init__(self, feedRateMinute, flowRate, location, oldLocation): + 'Initialize.' + self.feedRateMinute = feedRateMinute + self.flowRate = flowRate + self.location = location + self.oldLocation = oldLocation + + def addGcodeMovementByRate(self, distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier): + 'Add gcode movement by rate multiplier.' + flowRate = self.flowRate + rateMultiplier = rateMultiplier + endRateMultiplier * (1.0 - rateMultiplier) + if rateMultiplier < 1.0: + flowRate *= slowdownFlowRateMultiplier + distanceFeedRate.addFlowRateLine(flowRate * rateMultiplier) + distanceFeedRate.addGcodeMovementZWithFeedRateVector3(self.feedRateMinute * rateMultiplier, location) + + def addGcodeThreadSection(self, distanceFeedRate, endRateMultiplier, halfOverSteps, oneOverSteps, slowdownFlowRateMultiplier): + 'Add gcode thread section.' + if self.dwindlePortionEnd > 1.0 - halfOverSteps: + distanceFeedRate.addFlowRateLine(self.flowRate) + distanceFeedRate.addGcodeMovementZWithFeedRateVector3(self.feedRateMinute, self.location) + return + dwindleDifference = self.dwindlePortionBegin - self.dwindlePortionEnd + if self.dwindlePortionBegin < 1.0 and dwindleDifference > oneOverSteps: + numberOfStepsFloat = math.ceil(dwindleDifference / oneOverSteps) + numberOfSteps = int(numberOfStepsFloat) + for stepIndex in xrange(numberOfSteps): + alongBetween = (float(stepIndex) + 0.5) / numberOfStepsFloat + location = self.getLocation(float(stepIndex + 1) / numberOfStepsFloat) + rateMultiplier = self.dwindlePortionEnd * alongBetween + self.dwindlePortionBegin * (1.0 - alongBetween) + self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier) + return + if self.dwindlePortionBegin > 1.0 and self.dwindlePortionEnd < 1.0: + alongDwindle = 0.0 + if self.dwindlePortionBegin > 1.0 + halfOverSteps: + alongDwindle = (self.dwindlePortionBegin - 1.0) / dwindleDifference + self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, self.getLocation(alongDwindle), 1.0, slowdownFlowRateMultiplier) + alongDwindlePortion = self.dwindlePortionEnd * alongDwindle + self.dwindlePortionBegin * (1.0 - alongDwindle) + alongDwindleDifference = alongDwindlePortion - self.dwindlePortionEnd + numberOfStepsFloat = math.ceil(alongDwindleDifference / oneOverSteps) + numberOfSteps = int(numberOfStepsFloat) + for stepIndex in xrange(numberOfSteps): + alongBetween = (float(stepIndex) + 0.5) / numberOfStepsFloat + alongDwindleLocation = float(stepIndex + 1) / numberOfStepsFloat + location = self.getLocation(alongDwindleLocation + alongDwindle * (1.0 - alongDwindleLocation)) + rateMultiplier = self.dwindlePortionEnd * alongBetween + alongDwindlePortion * (1.0 - alongBetween) + self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, location, rateMultiplier, slowdownFlowRateMultiplier) + return + rateMultiplier = min(0.5 * (self.dwindlePortionBegin + self.dwindlePortionEnd), 1.0) + self.addGcodeMovementByRate(distanceFeedRate, endRateMultiplier, self.location, rateMultiplier, slowdownFlowRateMultiplier) + + def getDwindlePortion(self, area, dwindlePortion, operatingFeedRateMinute, operatingFlowRate, slowdownVolume): + 'Get cumulative dwindle portion.' + self.dwindlePortionEnd = dwindlePortion + distance = abs(self.oldLocation - self.location) + volume = area * distance + self.dwindlePortionBegin = dwindlePortion + volume / slowdownVolume + return self.dwindlePortionBegin + + def getLocation(self, along): + 'Get location along way.' + return self.location * along + self.oldLocation * (1.0 - along) + + +def main(): + 'Display the dwindle dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export.py new file mode 100644 index 0000000..d601612 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export.py @@ -0,0 +1,436 @@ +""" +This page is in the table of contents. +Export is a craft tool to pick an export plugin, add information to the file name, and delete comments. + +The export manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Export + +==Operation== +The default 'Activate Export' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Add Descriptive Extension=== +Default is off. + +When selected, key profile values will be added as an extension to the gcode file. For example: +test.04hx06w_03fill_2cx2r_33EL.gcode + +would mean: + +* . (Carve section.) +* 04h = 'Layer Height (mm):' 0.4 +* x +* 06w = 0.6 width i.e. 0.4 times 'Edge Width over Height (ratio):' 1.5 +* _ (Fill section.) +* 03fill = 'Infill Solidity (ratio):' 0.3 +* _ (Multiply section; if there is one column and one row then this section is not shown.) +* 2c = 'Number of Columns (integer):' 2 +* x +* 2r = 'Number of Rows (integer):' 2. +* _ (Speed section.) +* 33EL = 'Feed Rate (mm/s):' 33.0 and 'Flow Rate Setting (float):' 33.0. If either value has a positive value after the decimal place then this is also shown, but if it is zero it is hidden. Also, if the values differ (which they shouldn't with 5D volumetrics) then each should be displayed separately. For example, 35.2E30L = 'Feed Rate (mm/s):' 35.2 and 'Flow Rate Setting (float):' 30.0. + +===Add Profile Extension=== +Default is off. + +When selected, the current profile will be added to the file extension. For example: +test.my_profile_name.gcode + +===Add Timestamp Extension=== +Default is off. + +When selected, the current date and time is added as an extension in format YYYYmmdd_HHMMSS (so it is sortable if one has many files). For example: +test.my_profile_name.20110613_220113.gcode + +===Also Send Output To=== +Default is empty. + +Defines the output name for sending to a file or pipe. A common choice is stdout to print the output in the shell screen. Another common choice is stderr. With the empty default, nothing will be done. If the value is anything else, the output will be written to that file name. + +===Analyze Gcode=== +Default is on. + +When selected, the penultimate gcode will be sent to the analyze plugins to be analyzed and viewed. + +===Comment Choice=== +Default is 'Delete All Comments'. + +====Do Not Delete Comments==== +When selected, export will not delete comments. Crafting comments slow down the processing in many firmware types, which leads to pauses and therefore a lower quality print. + +====Delete Crafting Comments==== +When selected, export will delete the time consuming crafting comments, but leave the initialization comments. Since the crafting comments are deleted, there are no pauses during extrusion. The remaining initialization comments provide some useful information for the analyze tools. + +====Delete All Comments==== +When selected, export will delete all comments. The comments are not necessary to run a fabricator. Some printers do not support comments at all so the safest way is choose this option. + +===Export Operations=== +Export presents the user with a choice of the export plugins in the export_plugins folder. The chosen plugin will then modify the gcode or translate it into another format. There is also the "Do Not Change Output" choice, which will not change the output. An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function. + +===File Extension=== +Default is gcode. + +Defines the file extension added to the name of the output file. The output file will be named as originalname_export.extension so if you are processing XYZ.stl the output will by default be XYZ_export.gcode + +===Name of Replace File=== +Default is replace.csv. + +When export is exporting the code, if there is a tab separated file with the name of the "Name of Replace File" setting, it will replace the string in the first column by its replacement in the second column. If there is nothing in the second column, the first column string will be deleted, if this leads to an empty line, the line will be deleted. If there are replacement columns after the second, they will be added as extra lines of text. There is an example file replace_example.csv to demonstrate the tab separated format, which can be edited in a text editor or a spreadsheet. + +Export looks for the alteration file in the alterations folder in the .skeinforge folder in the home directory. Export does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. + +===Save Penultimate Gcode=== +Default is off. + +When selected, export will save the gcode file with the suffix '_penultimate.gcode' just before it is exported. This is useful because the code after it is exported could be in a form which the viewers can not display well. + +==Examples== +The following examples export the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and export.py. + +> python export.py +This brings up the export dialog. + +> python export.py Screw Holder Bottom.stl +The export tool is parsing the file: +Screw Holder Bottom.stl +.. +The export tool has created the file: +.. Screw Holder Bottom_export.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_analyze +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__credits__ = 'Gary Hodgson ' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedTextFromText(gcodeText, repository=None): + 'Export a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'export'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(ExportRepository()) + if not repository.activateExport.value: + return gcodeText + return ExportSkein().getCraftedGcode(repository, gcodeText) + +def getDescriptionCarve(lines): + 'Get the description for carve.' + descriptionCarve = '' + layerThicknessString = getSettingString(lines, 'carve', 'Layer Height') + if layerThicknessString != None: + descriptionCarve += layerThicknessString.replace('.', '') + 'h' + edgeWidthString = getSettingString(lines, 'carve', 'Edge Width over Height') + if edgeWidthString != None: + descriptionCarve += 'x%sw' % str(float(edgeWidthString) * float(layerThicknessString)).replace('.', '') + return descriptionCarve + +def getDescriptionFill(lines): + 'Get the description for fill.' + activateFillString = getSettingString(lines, 'fill', 'Activate Fill') + if activateFillString == None or activateFillString == 'False': + return '' + infillSolidityString = getSettingString(lines, 'fill', 'Infill Solidity') + return '_' + infillSolidityString.replace('.', '') + 'fill' + +def getDescriptionMultiply(lines): + 'Get the description for multiply.' + activateMultiplyString = getSettingString(lines, 'multiply', 'Activate Multiply') + if activateMultiplyString == None or activateMultiplyString == 'False': + return '' + columnsString = getSettingString(lines, 'multiply', 'Number of Columns') + rowsString = getSettingString(lines, 'multiply', 'Number of Rows') + if columnsString == '1' and rowsString == '1': + return '' + return '_%scx%sr' % (columnsString, rowsString) + +def getDescriptionSpeed(lines): + 'Get the description for speed.' + activateSpeedString = getSettingString(lines, 'speed', 'Activate Speed') + if activateSpeedString == None or activateSpeedString == 'False': + return '' + feedRateString = getSettingString(lines, 'speed', 'Feed Rate') + flowRateString = getSettingString(lines, 'speed', 'Flow Rate') + if feedRateString == flowRateString: + return '_%sEL' % feedRateString.replace('.0', '') + return '_%sE%sL' % (feedRateString.replace('.0', ''), flowRateString.replace('.0', '')) + +def getDescriptiveExtension(gcodeText): + 'Get the descriptive extension.' + lines = archive.getTextLines(gcodeText) + return '.' + getDescriptionCarve(lines) + getDescriptionFill(lines) + getDescriptionMultiply(lines) + getDescriptionSpeed(lines) + +def getDistanceGcode(exportText): + 'Get gcode lines with distance variable added, this is for if ever there is distance code.' + lines = archive.getTextLines(exportText) + oldLocation = None + for line in lines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = None + if len(splitLine) > 0: + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(oldLocation, splitLine) + if oldLocation != None: + distance = location.distance(oldLocation) + oldLocation = location + return exportText + +def getFirstValue(gcodeText, word): + 'Get the value from the first line which starts with the given word.' + for line in archive.getTextLines(gcodeText): + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if gcodec.getFirstWord(splitLine) == word: + return splitLine[1] + return '' + +def getNewRepository(): + 'Get new repository.' + return ExportRepository() + +def getReplaceableExportGcode(nameOfReplaceFile, replaceableExportGcode): + 'Get text with strings replaced according to replace.csv file.' + replaceLines = settings.getAlterationLines(nameOfReplaceFile) + if len(replaceLines) < 1: + return replaceableExportGcode + for replaceLine in replaceLines: + splitLine = replaceLine.replace('\\n', '\t').split('\t') + if len(splitLine) > 0: + replaceableExportGcode = replaceableExportGcode.replace(splitLine[0], '\n'.join(splitLine[1 :])) + output = cStringIO.StringIO() + gcodec.addLinesToCString(output, archive.getTextLines(replaceableExportGcode)) + return output.getvalue() + +def getSelectedPluginModule( plugins ): + 'Get the selected plugin module.' + for plugin in plugins: + if plugin.value: + return archive.getModuleWithDirectoryPath( plugin.directoryPath, plugin.name ) + return None + +def getSettingString(lines, procedureName, settingNameStart): + 'Get the setting value from the lines, return None if there is no setting starting with that name.' + settingNameStart = settingNameStart.replace(' ', '_') + for line in lines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = None + if len(splitLine) > 0: + firstWord = splitLine[0] + if firstWord == '(': + if len(splitLine) > 4: + if splitLine[1] == procedureName and splitLine[2].startswith(settingNameStart): + return splitLine[3] + elif firstWord == '()': + return None + return None + +def sendOutputTo(outputTo, text): + 'Send output to a file or a standard output.' + if outputTo.endswith('stderr'): + sys.stderr.write(text) + sys.stderr.write('\n') + sys.stderr.flush() + return + if outputTo.endswith('stdout'): + sys.stdout.write(text) + sys.stdout.write('\n') + sys.stdout.flush() + return + archive.writeFileText(outputTo, text) + +def writeOutput(fileName, shouldAnalyze=True): + 'Export a gcode linear move file.' + if fileName == '': + return None + repository = ExportRepository() + settings.getReadRepository(repository) + startTime = time.time() + print('File ' + archive.getSummarizedFileName(fileName) + ' is being chain exported.') + fileNameSuffix = fileName[: fileName.rfind('.')] + if repository.addExportSuffix.value: + fileNameSuffix += '_export' + gcodeText = gcodec.getGcodeFileText(fileName, '') + procedures = skeinforge_craft.getProcedures('export', gcodeText) + gcodeText = skeinforge_craft.getChainTextFromProcedures(fileName, procedures[: -1], gcodeText) + if gcodeText == '': + return None + if repository.addProfileExtension.value: + fileNameSuffix += '.' + getFirstValue(gcodeText, '(') + if repository.addDescriptiveExtension.value: + fileNameSuffix += getDescriptiveExtension(gcodeText) + if repository.addTimestampExtension.value: + fileNameSuffix += '.' + getFirstValue(gcodeText, '(') + fileNameSuffix += '.' + repository.fileExtension.value + fileNamePenultimate = fileName[: fileName.rfind('.')] + '_penultimate.gcode' + filePenultimateWritten = False + if repository.savePenultimateGcode.value: + archive.writeFileText(fileNamePenultimate, gcodeText) + filePenultimateWritten = True + print('The penultimate file is saved as ' + archive.getSummarizedFileName(fileNamePenultimate)) + exportGcode = getCraftedTextFromText(gcodeText, repository) + window = None + if shouldAnalyze and repository.analyzeGcode.value: + window = skeinforge_analyze.writeOutput(fileName, fileNamePenultimate, fileNameSuffix, filePenultimateWritten, gcodeText) + replaceableExportGcode = None + selectedPluginModule = getSelectedPluginModule(repository.exportPlugins) + if selectedPluginModule == None: + replaceableExportGcode = exportGcode + else: + if selectedPluginModule.globalIsReplaceable: + replaceableExportGcode = selectedPluginModule.getOutput(exportGcode) + else: + selectedPluginModule.writeOutput(fileNameSuffix, exportGcode) + if replaceableExportGcode != None: + replaceableExportGcode = getReplaceableExportGcode(repository.nameOfReplaceFile.value, replaceableExportGcode) + archive.writeFileText( fileNameSuffix, replaceableExportGcode ) + print('The exported file is saved as ' + archive.getSummarizedFileName(fileNameSuffix)) + if repository.alsoSendOutputTo.value != '': + if replaceableExportGcode == None: + replaceableExportGcode = selectedPluginModule.getOutput(exportGcode) + sendOutputTo(repository.alsoSendOutputTo.value, replaceableExportGcode) + print('It took %s to export the file.' % euclidean.getDurationString(time.time() - startTime)) + return window + + +class ExportRepository: + 'A class to handle the export settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Export', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Export') + self.activateExport = settings.BooleanSetting().getFromValue('Activate Export', self, True) + self.addDescriptiveExtension = settings.BooleanSetting().getFromValue('Add Descriptive Extension', self, False) + self.addExportSuffix = settings.BooleanSetting().getFromValue('Add Export Suffix', self, True) + self.addProfileExtension = settings.BooleanSetting().getFromValue('Add Profile Extension', self, False) + self.addTimestampExtension = settings.BooleanSetting().getFromValue('Add Timestamp Extension', self, False) + self.alsoSendOutputTo = settings.StringSetting().getFromValue('Also Send Output To:', self, '') + self.analyzeGcode = settings.BooleanSetting().getFromValue('Analyze Gcode', self, True) + self.commentChoice = settings.MenuButtonDisplay().getFromName('Comment Choice:', self) + self.doNotDeleteComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Do Not Delete Comments', self, False) + self.deleteCraftingComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete Crafting Comments', self, False) + self.deleteAllComments = settings.MenuRadio().getFromMenuButtonDisplay(self.commentChoice, 'Delete All Comments', self, True) + exportPluginsFolderPath = archive.getAbsoluteFrozenFolderPath(archive.getCraftPluginsDirectoryPath('export.py'), 'export_plugins') + exportStaticDirectoryPath = os.path.join(exportPluginsFolderPath, 'static_plugins') + exportPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportPluginsFolderPath) + exportStaticPluginFileNames = archive.getPluginFileNamesFromDirectoryPath(exportStaticDirectoryPath) + self.exportLabel = settings.LabelDisplay().getFromName('Export Operations: ', self) + self.exportPlugins = [] + exportLatentStringVar = settings.LatentStringVar() + self.doNotChangeOutput = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, 'Do Not Change Output', self, False) + self.doNotChangeOutput.directoryPath = None + allExportPluginFileNames = exportPluginFileNames + exportStaticPluginFileNames + for exportPluginFileName in allExportPluginFileNames: + exportPlugin = None + default = False + if exportPluginFileName == "gcode_small": + default = True + if exportPluginFileName in exportPluginFileNames: + path = os.path.join(exportPluginsFolderPath, exportPluginFileName) + exportPlugin = settings.RadioCapitalizedButton().getFromPath(exportLatentStringVar, exportPluginFileName, path, self, default) + exportPlugin.directoryPath = exportPluginsFolderPath + else: + exportPlugin = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, exportPluginFileName, self, default) + exportPlugin.directoryPath = exportStaticDirectoryPath + self.exportPlugins.append(exportPlugin) + self.fileExtension = settings.StringSetting().getFromValue('File Extension:', self, 'gcode') + self.nameOfReplaceFile = settings.StringSetting().getFromValue('Name of Replace File:', self, 'replace.csv') + self.savePenultimateGcode = settings.BooleanSetting().getFromValue('Save Penultimate Gcode', self, False) + self.executeTitle = 'Export' + + def execute(self): + 'Export button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class ExportSkein: + 'A class to export a skein of extrusions.' + def __init__(self): + self.crafting = False + self.decimalPlacesExported = 2 + self.output = cStringIO.StringIO() + + def addLine(self, line): + 'Add a line of text and a newline to the output.' + if line != '': + self.output.write(line + '\n') + + def getCraftedGcode( self, repository, gcodeText ): + 'Parse gcode text and store the export gcode.' + self.repository = repository + lines = archive.getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + return self.output.getvalue() + + def getLineWithTruncatedNumber(self, character, line, splitLine): + 'Get a line with the number after the character truncated.' + numberString = gcodec.getStringFromCharacterSplitLine(character, splitLine) + if numberString == None: + return line + roundedNumberString = euclidean.getRoundedToPlacesString(self.decimalPlacesExported, float(numberString)) + return gcodec.getLineWithValueString(character, line, splitLine, roundedNumberString) + + def parseLine(self, line): + 'Parse a gcode line.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()': + self.crafting = False + elif firstWord == '(': + self.decimalPlacesExported = int(splitLine[1]) - 1 + if self.repository.deleteAllComments.value or (self.repository.deleteCraftingComments.value and self.crafting): + if firstWord[0] == '(': + return + else: + line = line.split(';')[0].split('(')[0].strip() + if firstWord == '()': + self.crafting = True + if firstWord == '()': + self.addLine(gcodec.getTagBracketedProcedure('export')) + if firstWord != 'G1' and firstWord != 'G2' and firstWord != 'G3' : + self.addLine(line) + return + line = self.getLineWithTruncatedNumber('X', line, splitLine) + line = self.getLineWithTruncatedNumber('Y', line, splitLine) + line = self.getLineWithTruncatedNumber('Z', line, splitLine) + line = self.getLineWithTruncatedNumber('I', line, splitLine) + line = self.getLineWithTruncatedNumber('J', line, splitLine) + line = self.getLineWithTruncatedNumber('R', line, splitLine) + self.addLine(line) + + +def main(): + 'Display the export dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/__init__.py new file mode 100644 index 0000000..58ec332 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 4 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/binary_16_byte.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/binary_16_byte.py new file mode 100644 index 0000000..67604fe --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/binary_16_byte.py @@ -0,0 +1,231 @@ +""" +This page is in the table of contents. +Binary 16 byte is an export plugin to convert gcode into 16 byte binary segments. + +An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function. It is meant to be run from the export tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getOutput function of this script takes a gcode text and returns that text converted into 16 byte segments. The writeOutput function of this script takes a gcode text and writes that in a binary format converted into 16 byte segments. + +This plugin is just a starter to make a real binary converter. + +==Settings== +===Feed Rate Step Length=== +Default is 0.1 millimeters/second. + +Defines the feed rate step length. + +===File Extension=== +Default is bin. + +Defines the file extension suffix. + +===Offset=== +====X Offset==== +Default is zero. + +Defines the X Offset. + +====Y Offset==== +Default is zero. + +Defines the Y Offset. + +====Z Offset==== +Default is zero. + +Defines the Z Offset. + +===Step Length=== +====X Step Length==== +Default is 0.1 millimeters. + +Defines the X axis step length. + +====Y Step Length==== +Default is 0.1 millimeters. + +Defines the Y axis step length. + +====Z Step Length==== +Default is 0.01 millimeters. + +Defines the Z axis step length. + +==Record structure== +BinArray(0) = AscW(Inst_Code_Letter) +BinArray(1) = cInst_Code + +X Data +sInt32_to_Hbytes(iXdim_1) +BinArray(2) = lsb 'short lsb +BinArray(3) = msb 'short msb + +Y Data +sInt32_to_Hbytes(iYdim_2) +BinArray(4) = lsb 'short lsb +BinArray(5) = msb 'short msb + +Z Data +sInt32_to_Hbytes(iZdim_3) +BinArray(6) = lsb 'short lsb +BinArray(7) = msb 'short msb + +I Data +sInt32_to_Hbytes(iIdim_4) +BinArray(8) = lsb 'short lsb +BinArray(9) = msb 'short msb + +J Data +sInt32_to_Hbytes(iJdim_5) +BinArray(10) = lsb 'short lsb +BinArray(11) = msb 'short msb + +BinArray(12) = FP_Char +sInt32_to_Hbytes(iFP_Num) +BinArray(13) = lsb 'short lsb + +BinArray(14) = bActiveFlags + +BinArray(15) = AscW("#")End of record filler + +Byte 14 is worth a few extra notes, this byte is used to define which of the axes are active, its used to get round the problem of say a line of code with no mention of z. This would be put into the file as z = 0 as the space for this data is reserved, if we did nothing, this would instruct the machine to go to z = 0. If we use the active flag to define the z axis as inactive the z = 0 is ignored and the value set to the last saved value of z, i.e it does not move. If the z data is actually set to z = 0 then the axis would be set to active and the move takes place. + +""" + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +from struct import Struct +import cStringIO +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +# This is true if the output is text and false if it is binary." +globalIsReplaceable = False + + +def getIntegerFlagFromCharacterSplitLine(character, splitLine): + "Get the integer flag after the first occurence of the character in the split line." + lineFromCharacter = gcodec.getStringFromCharacterSplitLine(character, splitLine) + if lineFromCharacter == None: + return 0 + return 1 + +def getIntegerFromCharacterLengthLineOffset( character, offset, splitLine, stepLength ): + "Get the integer after the first occurence of the character in the split line." + lineFromCharacter = gcodec.getStringFromCharacterSplitLine(character, splitLine) + if lineFromCharacter == None: + return 0 + floatValue = ( float( lineFromCharacter ) + offset ) / stepLength + return int( round( floatValue ) ) + +def getNewRepository(): + 'Get new repository.' + return Binary16ByteRepository() + +def getOutput( gcodeText, binary16ByteRepository = None ): + 'Get the exported version of a gcode file.' + if gcodeText == '': + return '' + if binary16ByteRepository == None: + binary16ByteRepository = Binary16ByteRepository() + settings.getReadRepository( binary16ByteRepository ) + return Binary16ByteSkein().getCraftedGcode( gcodeText, binary16ByteRepository ) + +def writeOutput( fileName, gcodeText = ''): + "Write the exported version of a gcode file." + binary16ByteRepository = Binary16ByteRepository() + settings.getReadRepository( binary16ByteRepository ) + gcodeText = gcodec.getGcodeFileText(fileName, gcodeText) + skeinOutput = getOutput( gcodeText, binary16ByteRepository ) + suffixFileName = fileName[ : fileName.rfind('.') ] + '.' + binary16ByteRepository.fileExtension.value + archive.writeFileText( suffixFileName, skeinOutput ) + print('The converted file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + + +class Binary16ByteRepository: + "A class to handle the export settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + #Set the default settings. + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export_plugins.binary_16_byte.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File to be Converted to Binary 16 Byte', self, '') + self.feedRateStepLength = settings.FloatSpin().getFromValue( 0.0, 'Feed Rate Step Length (millimeters/second)', self, 1.0, 0.1 ) + self.fileExtension = settings.StringSetting().getFromValue('File Extension:', self, 'bin') + settings.LabelDisplay().getFromName('Offset:', self ) + self.xOffset = settings.FloatSpin().getFromValue( - 100.0, 'X Offset (millimeters)', self, 100.0, 0.0 ) + self.yOffset = settings.FloatSpin().getFromValue( -100.0, 'Y Offset (millimeters)', self, 100.0, 0.0 ) + self.zOffset = settings.FloatSpin().getFromValue( - 10.0, 'Z Offset (millimeters)', self, 10.0, 0.0 ) + settings.LabelDisplay().getFromName('Step Length:', self ) + self.xStepLength = settings.FloatSpin().getFromValue( 0.0, 'X Step Length (millimeters)', self, 1.0, 0.1 ) + self.yStepLength = settings.FloatSpin().getFromValue( 0.0, 'Y Step Length (millimeters)', self, 1.0, 0.1 ) + self.zStepLength = settings.FloatSpin().getFromValue( 0.0, 'Z Step Length (millimeters)', self, 0.2, 0.01 ) + self.executeTitle = 'Convert to Binary 16 Byte' + + def execute(self): + "Convert to binary 16 byte button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, ['.gcode'], self.fileNameInput.wasCancelled ) + for fileName in fileNames: + writeOutput(fileName) + + +class Binary16ByteSkein: + "A class to convert gcode into 16 byte binary segments." + def __init__(self): + self.output = cStringIO.StringIO() + + def getCraftedGcode( self, gcodeText, binary16ByteRepository ): + "Parse gcode text and store the gcode." + self.binary16ByteRepository = binary16ByteRepository + lines = archive.getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + return self.output.getvalue() + + def parseLine(self, line): + "Parse a gcode line." + binary16ByteRepository = self.binary16ByteRepository + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if len(firstWord) < 1: + return + firstLetter = firstWord[0] + if firstLetter == '(': + return + feedRateInteger = getIntegerFromCharacterLengthLineOffset('F', 0.0, splitLine, binary16ByteRepository.feedRateStepLength.value ) + iInteger = getIntegerFromCharacterLengthLineOffset('I', 0.0, splitLine, binary16ByteRepository.xStepLength.value ) + jInteger = getIntegerFromCharacterLengthLineOffset('J', 0.0, splitLine, binary16ByteRepository.yStepLength.value ) + xInteger = getIntegerFromCharacterLengthLineOffset('X', binary16ByteRepository.xOffset.value, splitLine, binary16ByteRepository.xStepLength.value ) + yInteger = getIntegerFromCharacterLengthLineOffset('Y', binary16ByteRepository.yOffset.value, splitLine, binary16ByteRepository.yStepLength.value ) + zInteger = getIntegerFromCharacterLengthLineOffset('Z', binary16ByteRepository.zOffset.value, splitLine, binary16ByteRepository.zStepLength.value ) + sixteenByteStruct = Struct('cBhhhhhhBc') + flagInteger = getIntegerFlagFromCharacterSplitLine('X', splitLine ) + flagInteger += 2 * getIntegerFlagFromCharacterSplitLine('Y', splitLine ) + flagInteger += 4 * getIntegerFlagFromCharacterSplitLine('Z', splitLine ) + flagInteger += 8 * getIntegerFlagFromCharacterSplitLine('I', splitLine ) + flagInteger += 16 * getIntegerFlagFromCharacterSplitLine('J', splitLine ) + flagInteger += 32 * getIntegerFlagFromCharacterSplitLine('F', splitLine ) + packedString = sixteenByteStruct.pack( firstLetter, int( firstWord[1 :] ), xInteger, yInteger, zInteger, iInteger, jInteger, feedRateInteger, flagInteger, '#') + self.output.write( packedString ) + + +def main(): + "Display the export dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_step.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_step.py new file mode 100644 index 0000000..f0abbdf --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_step.py @@ -0,0 +1,242 @@ +""" +This page is in the table of contents. +Gcode step is an export plugin to convert gcode from float position to number of steps. + +An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function. It is meant to be run from the export tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getOutput function of this script takes a gcode text and returns it with the positions converted into number of steps. The writeOutput function of this script takes a gcode text and writes that with the positions converted into number of steps. + +==Settings== +===Add Feed Rate Even When Unchanging=== +Default is on. + +When selected, the feed rate will be added even when it did not change from the previous line. + +===Add Space Between Words=== +Default is on. + +When selected, a space will be added between each gcode word. + +===Add Z Even When Unchanging=== +Default is on. + +When selected, the z word will be added even when it did not change. + +===Feed Rate Step Length=== +Default is 0.1 millimeters/second. + +Defines the feed rate step length. + +===Offset=== +====X Offset==== +Default is zero. + +Defines the X Offset. + +====Y Offset==== +Default is zero. + +Defines the Y Offset. + +====Z Offset==== +Default is zero. + +Defines the Z Offset. + +===Step Length=== +====E Step Length==== +Default is 0.1 millimeters. + +Defines the E extrusion distance step length. + +===Radius Rate Step Length=== +Default is 0.1 millimeters/second. + +Defines the radius step length. + +====X Step Length==== +Default is 0.1 millimeters. + +Defines the X axis step length. + +====Y Step Length==== +Default is 0.1 millimeters. + +Defines the Y axis step length. + +====Z Step Length==== +Default is 0.01 millimeters. + +Defines the Z axis step length. + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +from struct import Struct +import cStringIO +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +# This is true if the output is text and false if it is binary. +globalIsReplaceable = True + + +def getCharacterIntegerString(character, offset, splitLine, stepLength): + 'Get a character and integer string.' + floatValue = getFloatFromCharacterSplitLine(character, splitLine) + if floatValue == None: + return '' + floatValue += offset + integerValue = int(round(float(floatValue / stepLength))) + return character + str(integerValue) + +def getFloatFromCharacterSplitLine(character, splitLine): + 'Get the float after the first occurence of the character in the split line.' + lineFromCharacter = gcodec.getStringFromCharacterSplitLine(character, splitLine) + if lineFromCharacter == None: + return None + return float(lineFromCharacter) + +def getNewRepository(): + 'Get new repository.' + return GcodeStepRepository() + +def getOutput(gcodeText, repository=None): + 'Get the exported version of a gcode file.' + if gcodeText == '': + return '' + if repository == None: + repository = GcodeStepRepository() + settings.getReadRepository(repository) + return GcodeStepSkein().getCraftedGcode(repository, gcodeText) + +def writeOutput( fileName, gcodeText = ''): + 'Write the exported version of a gcode file.' + gcodeText = gcodec.getGcodeFileText(fileName, gcodeText) + repository = GcodeStepRepository() + settings.getReadRepository(repository) + output = getOutput(gcodeText, repository) + suffixFileName = fileName[: fileName.rfind('.')] + '_gcode_step.gcode' + archive.writeFileText(suffixFileName, output) + print('The converted file is saved as ' + archive.getSummarizedFileName(suffixFileName)) + + +class GcodeStepRepository: + 'A class to handle the export settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export_plugins.gcode_step.html', self) + self.addFeedRateEvenWhenUnchanging = settings.BooleanSetting().getFromValue('Add Feed Rate Even When Unchanging', self, True) + self.addSpaceBetweenWords = settings.BooleanSetting().getFromValue('Add Space Between Words', self, True) + self.addZEvenWhenUnchanging = settings.BooleanSetting().getFromValue('Add Z Even When Unchanging', self, True) + self.fileNameInput = settings.FileNameInput().getFromFileName([('Gcode text files', '*.gcode')], 'Open File to be Converted to Gcode Step', self, '') + self.feedRateStepLength = settings.FloatSpin().getFromValue(0.0, 'Feed Rate Step Length (millimeters/second)', self, 1.0, 0.1) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Offset -', self ) + self.xOffset = settings.FloatSpin().getFromValue(-100.0, 'X Offset (millimeters)', self, 100.0, 0.0) + self.yOffset = settings.FloatSpin().getFromValue(-100.0, 'Y Offset (millimeters)', self, 100.0, 0.0) + self.zOffset = settings.FloatSpin().getFromValue(-10.0, 'Z Offset (millimeters)', self, 10.0, 0.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Step Length -', self ) + self.eStepLength = settings.FloatSpin().getFromValue(0.0, 'E Step Length (float)', self, 1.0, 0.1) + self.radiusStepLength = settings.FloatSpin().getFromValue(0.0, 'Radius Step Length (millimeters)', self, 1.0, 0.1) + self.xStepLength = settings.FloatSpin().getFromValue(0.0, 'X Step Length (millimeters)', self, 1.0, 0.1) + self.yStepLength = settings.FloatSpin().getFromValue(0.0, 'Y Step Length (millimeters)', self, 1.0, 0.1) + self.zStepLength = settings.FloatSpin().getFromValue(0.0, 'Z Step Length (millimeters)', self, 0.2, 0.01) + self.executeTitle = 'Convert to Gcode Step' + + def execute(self): + 'Convert to gcode step button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, ['.gcode'], self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class GcodeStepSkein: + 'A class to convert gcode into 16 byte binary segments.' + def __init__(self): + self.oldFeedRateString = None + self.oldZString = None + self.output = cStringIO.StringIO() + + def addCharacterInteger(self, character, lineStringIO, offset, splitLine, stepLength): + 'Add a character and integer to line string.' + characterIntegerString = getCharacterIntegerString(character, offset, splitLine, stepLength) + self.addStringToLine(lineStringIO, characterIntegerString) + + def addLine(self, line): + 'Add a line of text and a newline to the output.' + self.output.write(line + '\n') + + def addStringToLine(self, lineStringIO, wordString): + 'Add a character and integer to line string.' + if wordString == '': + return + if self.repository.addSpaceBetweenWords.value: + lineStringIO.write(' ') + lineStringIO.write(wordString) + + def getCraftedGcode(self, repository, gcodeText): + 'Parse gcode text and store the gcode.' + self.repository = repository + lines = archive.getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + return self.output.getvalue() + + def parseLine(self, line): + 'Parse a gcode line.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if len(firstWord) < 1: + return + firstLetter = firstWord[0] + if firstLetter == '(': + return + if firstWord != 'G1' and firstWord != 'G2' and firstWord != 'G3': + self.addLine(line) + return + lineStringIO = cStringIO.StringIO() + lineStringIO.write(firstWord) + self.addCharacterInteger('I', lineStringIO, 0.0, splitLine, self.repository.xStepLength.value) + self.addCharacterInteger('J', lineStringIO, 0.0, splitLine, self.repository.yStepLength.value) + self.addCharacterInteger('R', lineStringIO, 0.0, splitLine, self.repository.radiusStepLength.value) + self.addCharacterInteger('X', lineStringIO, self.repository.xOffset.value, splitLine, self.repository.xStepLength.value) + self.addCharacterInteger('Y', lineStringIO, self.repository.yOffset.value, splitLine, self.repository.yStepLength.value) + zString = getCharacterIntegerString('Z', self.repository.zOffset.value, splitLine, self.repository.zStepLength.value) + feedRateString = getCharacterIntegerString('F', 0.0, splitLine, self.repository.feedRateStepLength.value) + if zString != '': + if zString != self.oldZString or self.repository.addZEvenWhenUnchanging.value: + self.addStringToLine(lineStringIO, zString) + if feedRateString != '': + if feedRateString != self.oldFeedRateString or self.repository.addFeedRateEvenWhenUnchanging.value: + self.addStringToLine(lineStringIO, feedRateString) + self.addCharacterInteger('E', lineStringIO, 0.0, splitLine, self.repository.eStepLength.value) + self.addLine(lineStringIO.getvalue()) + self.oldFeedRateString = feedRateString + self.oldZString = zString + + +def main(): + 'Display the export dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_time_segment.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_time_segment.py new file mode 100644 index 0000000..4a7e756 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/gcode_time_segment.py @@ -0,0 +1,245 @@ +""" +This page is in the table of contents. +Gcode time segment is an export plugin to convert gcode from float position to number of steps. + +An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function. It is meant to be run from the export tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getOutput function of this script takes a gcode text and returns it with the positions converted into number of steps and time. The writeOutput function of this script takes a gcode text and writes that with the positions converted into number of steps and time. + +==Settings== +===Add Space Between Words=== +Default is on. + +When selected, a space will be added between each gcode word. + +===Offset=== +====X Offset==== +Default is zero. + +Defines the X Offset. + +====Y Offset==== +Default is zero. + +Defines the Y Offset. + +====Z Offset==== +Default is zero. + +Defines the Z Offset. + +===Step=== +===Extrusion Step=== +Default is 0.01 mm. + +Defines the radius step length. + +===Time Step=== +Default is 1 microsecond(mcs). + +Defines the time step duration. + +====X Step==== +Default is 0.1 mm. + +Defines the X axis step length. + +====Y Step==== +Default is 0.1 mm. + +Defines the Y axis step length. + +====Z Step==== +Default is 0.01 mm. + +Defines the Z axis step length. + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +from struct import Struct +import cStringIO +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +# This is true if the output is text and false if it is binary." +globalIsReplaceable = True + + +def getCharacterIntegerString( character, offset, splitLine, step ): + "Get a character and integer string." + floatValue = getFloatFromCharacterSplitLine(character, splitLine) + if floatValue == None: + return None + floatValue += offset + integerValue = int(round(float(floatValue / step))) + return character + str( integerValue ) + +def getFloatFromCharacterSplitLine(character, splitLine): + "Get the float after the first occurence of the character in the split line." + lineFromCharacter = gcodec.getStringFromCharacterSplitLine(character, splitLine) + if lineFromCharacter == None: + return None + return float(lineFromCharacter) + +def getNewRepository(): + 'Get new repository.' + return GcodeTimeSegmentRepository() + +def getOutput(gcodeText, repository=None): + 'Get the exported version of a gcode file.' + if gcodeText == '': + return '' + if repository == None: + repository = GcodeTimeSegmentRepository() + settings.getReadRepository(repository) + return GcodeTimeSegmentSkein().getCraftedGcode(gcodeText, repository) + +def writeOutput( fileName, gcodeText = ''): + "Write the exported version of a gcode file." + gcodeText = gcodec.getGcodeFileText(fileName, gcodeText) + repository = GcodeTimeSegmentRepository() + settings.getReadRepository(repository) + output = getOutput(gcodeText, repository) + suffixFileName = fileName[ : fileName.rfind('.') ] + '_gcode_time_segment.gcode' + archive.writeFileText( suffixFileName, output ) + print('The converted file is saved as ' + archive.getSummarizedFileName(suffixFileName) ) + + +class GcodeTimeSegmentRepository: + "A class to handle the export settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.export_plugins.gcode_time.html', self) + self.addSpaceBetweenWords = settings.BooleanSetting().getFromValue('Add Space Between Words', self, True ) + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File to be Converted to Gcode Time', self, '') + self.initialTime = settings.FloatSpin().getFromValue(0.0, 'Initial Time (s)', self, 20.0, 10.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Offset -', self ) + self.xOffset = settings.FloatSpin().getFromValue( - 100.0, 'X Offset (mm)', self, 100.0, 0.0 ) + self.yOffset = settings.FloatSpin().getFromValue( -100.0, 'Y Offset (mm)', self, 100.0, 0.0 ) + self.zOffset = settings.FloatSpin().getFromValue( - 10.0, 'Z Offset (mm)', self, 10.0, 0.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Step -', self ) + self.extrusionStep = settings.FloatSpin().getFromValue(0.0, 'Extrusion Step (mm)', self, 0.2, 0.01) + self.timeStep = settings.FloatSpin().getFromValue(0.0, 'Time Step (mcs)', self, 2000.0, 1000.0) + self.xStep = settings.FloatSpin().getFromValue(0.0, 'X Step (mm)', self, 1.0, 0.1) + self.yStep = settings.FloatSpin().getFromValue(0.0, 'Y Step (mm)', self, 1.0, 0.1) + self.zStep = settings.FloatSpin().getFromValue(0.0, 'Z Step (mm)', self, 0.2, 0.01) + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Convert to Gcode Time' + + def execute(self): + "Convert to gcode step button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, ['.gcode'], self.fileNameInput.wasCancelled ) + for fileName in fileNames: + writeOutput(fileName) + + +class GcodeTimeSegmentSkein: + "A class to convert gcode into time segments." + def __init__(self): + 'Initialize.' + self.feedRateMinute = None + self.isExtruderActive = False + self.oldFeedRateString = None + self.oldLocation = None + self.oldZString = None + self.operatingFlowRate = None + self.output = cStringIO.StringIO() + + def addCharacterInteger(self, character, lineStringIO, offset, splitLine, step): + "Add a character and integer to line string." + characterIntegerString = getCharacterIntegerString(character, offset, splitLine, step) + self.addStringToLine(lineStringIO, characterIntegerString) + + def addLine(self, line): + "Add a line of text and a newline to the output." + self.output.write(line + '\n') + + def addStringToLine( self, lineStringIO, wordString ): + "Add a character and integer to line string." + if wordString == None: + return + if self.repository.addSpaceBetweenWords.value: + lineStringIO.write(' ') + lineStringIO.write( wordString ) + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the gcode." + self.repository = repository + lines = archive.getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + return self.output.getvalue() + + def parseLine(self, line): + "Parse a gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if len(firstWord) < 1: + return + firstLetter = firstWord[0] + if firstWord == '(': + self.feedRateMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.operatingFlowRate = float(splitLine[1]) + self.flowRate = self.operatingFlowRate + if firstLetter == '(': + return + if firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + elif firstWord == 'M108': + self.flowRate = float(splitLine[1][1 :]) + if firstWord != 'G1' and firstWord != 'G2' and firstWord != 'G3': + self.addLine(line) + return + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + lineStringIO = cStringIO.StringIO() + lineStringIO.write(firstWord) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.addCharacterInteger('X', lineStringIO, self.repository.xOffset.value, splitLine, self.repository.xStep.value ) + self.addCharacterInteger('Y', lineStringIO, self.repository.yOffset.value, splitLine, self.repository.yStep.value ) + zString = getCharacterIntegerString('Z', self.repository.zOffset.value, splitLine, self.repository.zStep.value ) + if zString == None: + zString = self.oldZString + self.addStringToLine(lineStringIO, zString) + duration = self.repository.initialTime.value + if self.oldLocation != None: + distance = abs(location - self.oldLocation) + duration = 60.0 / self.feedRateMinute * distance + extrusionDistance = 0.0 + if self.isExtruderActive: + extrusionDistance = self.flowRate * duration + self.addStringToLine(lineStringIO, 'E%s' % int(round(extrusionDistance / self.repository.extrusionStep.value))) + self.addStringToLine(lineStringIO, 'D%s' % int(round(duration * 1000000.0 / self.repository.timeStep.value))) + self.addLine(lineStringIO.getvalue()) + self.oldLocation = location + self.oldZString = zString + + +def main(): + "Display the export dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/__init__.py new file mode 100644 index 0000000..b83e941 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 5 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py new file mode 100644 index 0000000..3443777 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/export_plugins/static_plugins/gcode_small.py @@ -0,0 +1,110 @@ +""" +This page is in the table of contents. +Gcode_small is an export plugin to remove the comments and the redundant z and feed rate parameters from a gcode file. + +An export plugin is a script in the export_plugins folder which has the getOutput function, the globalIsReplaceable variable and if it's output is not replaceable, the writeOutput function. It is meant to be run from the export tool. To ensure that the plugin works on platforms which do not handle file capitalization properly, give the plugin a lower case name. + +The getOutput function of this script takes a gcode text and returns that text without comments and redundant z and feed rate parameters. The writeOutput function of this script takes a gcode text and writes that text without comments and redundant z and feed rate parameters to a file. + +Many of the functions in this script are copied from gcodec in skeinforge_utilities. They are copied rather than imported so developers making new plugins do not have to learn about gcodec, the code here is all they need to learn. + +""" + +from __future__ import absolute_import +import cStringIO +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +# This is true if the output is text and false if it is binary." +globalIsReplaceable = True + + +def getIndexOfStartingWithSecond(letter, splitLine): + "Get index of the first occurence of the given letter in the split line, starting with the second word. Return - 1 if letter is not found" + for wordIndex in xrange( 1, len(splitLine) ): + word = splitLine[ wordIndex ] + firstLetter = word[0] + if firstLetter == letter: + return wordIndex + return - 1 + +def getOutput(gcodeText): + 'Get the exported version of a gcode file.' + return GcodeSmallSkein().getCraftedGcode(gcodeText) + +def getSplitLineBeforeBracketSemicolon(line): + "Get the split line before a bracket or semicolon." + bracketSemicolonIndex = min( line.find(';'), line.find('(') ) + if bracketSemicolonIndex < 0: + return line.split() + return line[ : bracketSemicolonIndex ].split() + +def getStringFromCharacterSplitLine(character, splitLine): + "Get the string after the first occurence of the character in the split line." + indexOfCharacter = getIndexOfStartingWithSecond(character, splitLine) + if indexOfCharacter < 0: + return None + return splitLine[indexOfCharacter][1 :] + +def getSummarizedFileName(fileName): + "Get the fileName basename if the file is in the current working directory, otherwise return the original full name." + if os.getcwd() == os.path.dirname(fileName): + return os.path.basename(fileName) + return fileName + +def getTextLines(text): + "Get the all the lines of text of a text." + return text.replace('\r', '\n').split('\n') + + +class GcodeSmallSkein: + "A class to remove redundant z and feed rate parameters from a skein of extrusions." + def __init__(self): + self.lastFeedRateString = None + self.lastZString = None + self.output = cStringIO.StringIO() + + def getCraftedGcode( self, gcodeText ): + "Parse gcode text and store the gcode." + lines = getTextLines(gcodeText) + for line in lines: + self.parseLine(line) + return self.output.getvalue() + + def parseLine(self, line): + "Parse a gcode line." + splitLine = getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if len(firstWord) < 1: + return + if firstWord[0] == '(': + return + if firstWord != 'G1': + self.output.write(line + '\n') + return + eString = getStringFromCharacterSplitLine('E', splitLine ) + xString = getStringFromCharacterSplitLine('X', splitLine ) + yString = getStringFromCharacterSplitLine('Y', splitLine ) + zString = getStringFromCharacterSplitLine('Z', splitLine ) + feedRateString = getStringFromCharacterSplitLine('F', splitLine ) + self.output.write('G1') + if xString != None: + self.output.write(' X' + xString ) + if yString != None: + self.output.write(' Y' + yString ) + if zString != None and zString != self.lastZString: + self.output.write(' Z' + zString ) + if feedRateString != None and feedRateString != self.lastFeedRateString: + self.output.write(' F' + feedRateString ) + if eString != None: + self.output.write(' E' + eString ) + self.lastFeedRateString = feedRateString + self.lastZString = zString + self.output.write('\n') diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/feed.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/feed.py new file mode 100644 index 0000000..042da50 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/feed.py @@ -0,0 +1,183 @@ +""" +This page is in the table of contents. +The feed script sets the maximum feed rate, operating feed rate & travel feed rate. + +==Operation== +The default 'Activate Feed' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Feed Rate=== +Default is 16 millimeters/second. + +Defines the feed rate for the shape. + +===Maximum Z Drill Feed Rate=== +Default is 0.1 millimeters/second. + +If your firmware limits the z feed rate, you do not need to set this setting. + +Defines the maximum feed that the tool head will move in the z direction while the tool is on. + +===Maximum Z Feed Rate=== +Default is one millimeter per second. + +Defines the maximum speed that the tool head will move in the z direction. + +===Travel Feed Rate=== +Default is 16 millimeters/second. + +Defines the feed rate when the cutter is off. The travel feed rate could be set as high as the cutter can be moved, it does not have to be limited by the maximum cutter rate. + +==Examples== +The following examples feed the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and feed.py. + +> python feed.py +This brings up the feed dialog. + +> python feed.py Screw Holder Bottom.stl +The feed tool is parsing the file: +Screw Holder Bottom.stl +.. +The feed tool has created the file: +.. Screw Holder Bottom_feed.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, gcodeText='', repository=None): + "Feed the file or text." + return getCraftedTextFromText( archive.getTextIfEmpty( fileName, gcodeText ), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + "Feed a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'feed'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(FeedRepository()) + if not repository.activateFeed.value: + return gcodeText + return FeedSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return FeedRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Feed a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'feed', shouldAnalyze) + + +class FeedRepository: + "A class to handle the feed settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.feed.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Feed', self, '') + self.activateFeed = settings.BooleanSetting().getFromValue('Activate Feed', self, True) + self.feedRatePerSecond = settings.FloatSpin().getFromValue(2.0, 'Feed Rate (mm/s):', self, 50.0, 16.0) + self.maximumZDrillFeedRatePerSecond = settings.FloatSpin().getFromValue(0.02, 'Maximum Z Drill Feed Rate (mm/s):', self, 0.5, 0.1) + self.maximumZFeedRatePerSecond = settings.FloatSpin().getFromValue(0.5, 'Maximum Z Feed Rate (mm/s):', self, 10.0, 1.0) + self.travelFeedRatePerSecond = settings.FloatSpin().getFromValue(2.0, 'Travel Feed Rate (mm/s):', self, 50.0, 16.0) + self.executeTitle = 'Feed' + + def execute(self): + "Feed button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class FeedSkein: + "A class to feed a skein of cuttings." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRatePerSecond = 16.0 + self.isExtruderActive = False + self.lineIndex = 0 + self.lines = None + self.oldFlowrateString = None + self.oldLocation = None + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the feed gcode." + self.repository = repository + self.feedRatePerSecond = repository.feedRatePerSecond.value + self.travelFeedRateMinute = 60.0 * self.repository.travelFeedRatePerSecond.value + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getFeededLine(self, line, splitLine): + "Get gcode line with feed rate." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation = location + feedRateMinute = 60.0 * self.feedRatePerSecond + if not self.isExtruderActive: + feedRateMinute = self.travelFeedRateMinute + return self.distanceFeedRate.getLineWithFeedRate(feedRateMinute, line, splitLine) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('feed') + return + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + self.distanceFeedRate.addTagBracketedLine('maximumZDrillFeedRatePerSecond', self.repository.maximumZDrillFeedRatePerSecond.value) + self.distanceFeedRate.addTagBracketedLine('maximumZFeedRatePerSecond', self.repository.maximumZFeedRatePerSecond.value ) + self.distanceFeedRate.addTagBracketedLine('operatingFeedRatePerSecond', self.feedRatePerSecond) + self.distanceFeedRate.addTagBracketedLine('travelFeedRatePerSecond', self.repository.travelFeedRatePerSecond.value) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the feed skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + line = self.getFeededLine(line, splitLine) + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the feed dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py new file mode 100644 index 0000000..75ea0f2 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py @@ -0,0 +1,1371 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Fill is a script to fill the edges of a gcode file. + +The fill manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill + +Allan Ecker aka The Masked Retriever has written the "Skeinforge Quicktip: Fill" at: +http://blog.thingiverse.com/2009/07/21/mysteries-of-skeinforge-fill/ + +==Operation== +The default 'Activate Fill' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Diaphragm=== +The diaphragm is a solid group of layers, at regular intervals. It can be used with a sparse infill to give the object watertight, horizontal compartments and/or a higher shear strength. + +====Diaphragm Period==== +Default is one hundred. + +Defines the number of layers between diaphrams. + +====Diaphragm Thickness==== +Default is zero, because the diaphragm feature is rarely used. + +Defines the number of layers the diaphram is composed of. + +===Extra Shells=== +The shells interior edge loops. Adding extra shells makes the object stronger & heavier. + +====Extra Shells on Alternating Solid Layers==== +Default is two. + +Defines the number of extra shells, on the alternating solid layers. + +====Extra Shells on Base==== +Default is one. + +Defines the number of extra shells on the bottom, base layer and every even solid layer after that. Setting this to a different value than the "Extra Shells on Alternating Solid Layers" means the infill pattern will alternate, creating a strong interleaved bond even if the edge loop shrinks. + +====Extra Shells on Sparse Layer==== +Default is one. + +Defines the number of extra shells on the sparse layers. The solid layers are those at the top & bottom, and wherever the object has a plateau or overhang, the sparse layers are the layers in between. + +===Grid=== +====Grid Circle Separation over Perimeter Width==== +Default is 0.2. + +Defines the ratio of the amount the grid circle is inset over the edge width, the default is zero. With a value of zero the circles will touch, with a value of one two threads could be fitted between the circles. + +====Grid Extra Overlap==== +Default is 0.1. + +Defines the amount of extra overlap added when extruding the grid to compensate for the fact that when the first thread going through a grid point is extruded, since there is nothing there yet for it to connect to it will shrink extra. + +====Grid Junction Separation over Octogon Radius At End==== +Default is zero. + +Defines the ratio of the amount the grid square is increased in each direction over the extrusion width at the end. With a value of one or so the grid pattern will have large squares to go with the octogons. + +====Grid Junction Separation over Octogon Radius At Middle==== +Default is zero. + +Defines the increase at the middle. If this value is different than the value at the end, the grid would have an accordion pattern, which would give it a higher shear strength. + +====Grid Junction Separation Band Height==== +Default is ten. + +Defines the height of the bands of the accordion pattern. + +===Infill=== +====Infill Pattern==== +Default is 'Line', since it is quicker to generate and does not add extra movements for the extruder. The grid pattern has extra diagonal lines, so when choosing a grid option, set the infill solidity to 0.2 or less so that there is not too much plastic and the grid generation time, which increases with the third power of solidity, will be reasonable. + +=====Grid Circular===== +When selected, the infill will be a grid of separated circles. Because the circles are separated, the pattern is weak, it only provides support for the top layer threads and some strength in the z direction. The flip side is that this infill does not warp the object, the object will get warped only by the walls. + +Because this pattern turns the extruder on and off often, it is best to use a stepper motor extruder. + +=====Grid Hexagonal===== +When selected, the infill will be a hexagonal grid. Because the grid is made with threads rather than with molding or milling, only a partial hexagon is possible, so the rectangular grid pattern is stronger. + +=====Grid Rectangular===== +When selected, the infill will be a funky octogon square honeycomb like pattern which gives the object extra strength. + +=====Line===== +When selected, the infill will be made up of lines. + +====Infill Begin Rotation==== +Default is forty five degrees, giving a diagonal infill. + +Defines the amount the infill direction of the base and every second layer thereafter is rotated. + +====Infill Odd Layer Extra Rotation==== +Default is ninety degrees, making the odd layer infill perpendicular to the base layer. + +Defines the extra amount the infill direction of the odd layers is rotated compared to the base layer. + +====Infill Begin Rotation Repeat==== +Default is one, giving alternating cross hatching. + +Defines the number of layers that the infill begin rotation will repeat. With a value higher than one, the infill will go in one direction more often, giving the object more strength in one direction and less in the other, this is useful for beams and cantilevers. + +====Infill Perimeter Overlap==== +Default is 0.15. + +Defines the amount the infill overlaps the edge over the average of the edge and infill width. The higher the value the more the infill will overlap the edge, and the thicker join between the infill and the edge. If the value is too high, the join will be so thick that the nozzle will run plow through the join below making a mess, also when it is above 0.45 fill may not be able to create infill correctly. If you want to stretch the infill a lot, set 'Path Stretch over Perimeter Width' in stretch to a high value. + +====Infill Solidity==== +Default is 0.2. + +Defines the solidity of the infill, this is the most important setting in fill. A value of one means the infill lines will be right beside each other, resulting in a solid, strong, heavy shape which takes a long time to extrude. A low value means the infill will be sparse, the interior will be mosty empty space, the object will be weak, light and quick to build. + +====Infill Width over Thickness==== +Default is 1.5. + +Defines the ratio of the infill width over the layer height. The higher the value the wider apart the infill will be and therefore the sparser the infill will be. + +===Solid Surface Thickness=== +Default is three. + +Defines the number of solid layers that are at the bottom, top, plateaus and overhang. With a value of zero, the entire object will be composed of a sparse infill, and water could flow right through it. With a value of one, water will leak slowly through the surface and with a value of three, the object could be watertight. The higher the solid surface thickness, the stronger and heavier the object will be. + +===Start From Choice=== +Default is 'Lower Left'. + +Defines where each layer starts from. + +====Lower Left==== +When selected the layer will start from the lower left corner. This is to extrude in round robin fashion so that the first extrusion will be deposited on the coolest part of the last layer. The reason for this is described at: +http://hydraraptor.blogspot.com/2010/12/round-robin.html + +====Nearest==== +When selected the layer will start from the closest point to the end of the last layer. This leads to less stringing, but the first extrusion will be deposited on the hottest part of the last layer which leads to melting problems. So this option is deprecated, eventually this option will be removed and the layers will always start from the lower left. + +===Surrounding Angle=== +Default: 60 degrees + +Defines the angle that the surrounding layers around the infill are expanded. + +To decide whether or not the infill should be sparse or solid, fill looks at the 'Solid Surface Thickness' surrounding layers above and below the infill. If any of the expanded layers above or below the infill do not cover the infill, then the infill will be solid in that region. The layers are expanded by the height difference times the tangent of the surrounding angle, which is from the vertical. For example, if the model is a wedge with a wall angle less than the surrounding angle, the interior layers (those which are not on the bottom or top) will be sparse. If the wall angle is greater than the surrounding angle, the interior layers will be solid. + +The time required to examine the surrounding layers increases with the surrounding angle, so the surrounding angle is limited to eighty degrees, regardless of the input value. + +If you have an organic shape with gently sloping surfaces; if the surrounding angle is set too high, then too many layers will be sparse. If the surrounding angle is too low, then too many layers will be solid and the extruder may end up plowing through previous layers: +http://hydraraptor.blogspot.com/2008/08/bearing-fruit.html + +===Thread Sequence Choice=== +The 'Thread Sequence Choice' is the sequence in which the threads will be extruded on the second and higher layers. There are three kinds of thread, the edge threads on the outside of the object, the loop threads aka inner shell threads, and the interior infill threads. The first layer thread sequence is 'Perimeter > Loops > Infill'. + +The default choice is 'Perimeter > Loops > Infill', which the default stretch parameters are based on. If you change from the default sequence choice setting of edge, then loops, then infill, the optimal stretch thread parameters would also be different. In general, if the infill is extruded first, the infill would have to be stretched more so that even after the filament shrinkage, it would still be long enough to connect to the loop or edge. The six sequence combinations follow below. + +====Infill > Loops > Perimeter==== +====Infill > Perimeter > Loops==== +====Loops > Infill > Perimeter==== +====Loops > Perimeter > Infill==== +====Perimeter > Infill > Loops==== +====Perimeter > Loops > Infill==== + +==Examples== +The following examples fill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and fill.py. + +> python fill.py +This brings up the fill dialog. + +> python fill.py Screw Holder Bottom.stl +The fill tool is parsing the file: +Screw Holder Bottom.stl +.. +The fill tool has created the file: +.. Screw Holder Bottom_fill.gcode + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/28/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + + +def addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, gridSearchRadius, isBothOrNone, isDoubleJunction, isJunctionWide, paths, pixelTable, width ): + 'Add the path around the grid point.' + closestPathIndex = None + aroundIntersectionPaths = [] + for aroundIndex in xrange( len(arounds) ): + loop = arounds[ aroundIndex ] + for pointIndex in xrange(len(loop)): + pointFirst = loop[pointIndex] + pointSecond = loop[(pointIndex + 1) % len(loop)] + yIntersection = euclidean.getYIntersectionIfExists( pointFirst, pointSecond, gridPoint.real ) + addYIntersectionPathToList( aroundIndex, pointIndex, gridPoint.imag, yIntersection, aroundIntersectionPaths ) + if len( aroundIntersectionPaths ) < 2: + print('Warning, aroundIntersectionPaths is less than 2 in fill.') + print(aroundIntersectionPaths) + print(gridPoint) + return + yCloseToCenterArounds = getClosestOppositeIntersectionPaths(aroundIntersectionPaths) + if len(yCloseToCenterArounds) < 2: + return + segmentFirstY = min( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y ) + segmentSecondY = max( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y ) + yIntersectionPaths = [] + gridPixel = euclidean.getStepKeyFromPoint( gridPoint / width ) + segmentFirstPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentFirstY ) / width ) + segmentSecondPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentSecondY ) / width ) + pathIndexTable = {} + addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ) + addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ) + for pathIndex in pathIndexTable.keys(): + path = paths[ pathIndex ] + for pointIndex in xrange( len(path) - 1 ): + pointFirst = path[pointIndex] + pointSecond = path[pointIndex + 1] + yIntersection = getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, pointFirst, pointSecond, gridPoint.real ) + addYIntersectionPathToList( pathIndex, pointIndex, gridPoint.imag, yIntersection, yIntersectionPaths ) + if len( yIntersectionPaths ) < 1: + return + yCloseToCenterPaths = [] + if isDoubleJunction: + yCloseToCenterPaths = getClosestOppositeIntersectionPaths( yIntersectionPaths ) + else: + yIntersectionPaths.sort( compareDistanceFromCenter ) + yCloseToCenterPaths = [ yIntersectionPaths[0] ] + for yCloseToCenterPath in yCloseToCenterPaths: + setIsOutside( yCloseToCenterPath, aroundIntersectionPaths ) + if len( yCloseToCenterPaths ) < 2: + yCloseToCenterPaths[0].gridPoint = gridPoint + insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yCloseToCenterPaths[0], width ) + return + plusMinusSign = getPlusMinusSign( yCloseToCenterPaths[1].y - yCloseToCenterPaths[0].y ) + yCloseToCenterPaths[0].gridPoint = complex( gridPoint.real, gridPoint.imag - plusMinusSign * gridPointInsetY ) + yCloseToCenterPaths[1].gridPoint = complex( gridPoint.real, gridPoint.imag + plusMinusSign * gridPointInsetY ) + yCloseToCenterPaths.sort( comparePointIndexDescending ) + insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, yCloseToCenterPaths[0], yCloseToCenterPaths[1], isBothOrNone, isJunctionWide, paths, pixelTable, width ) + +def addInfillBoundary(infillBoundary, nestedRings): + 'Add infill boundary to the nested ring that contains it.' + infillPoint = infillBoundary[0] + for nestedRing in nestedRings: + if euclidean.isPointInsideLoop(nestedRing.boundary, infillPoint): + nestedRing.infillBoundaries.append(infillBoundary) + return + +def addLoop(infillWidth, infillPaths, loop, rotationPlaneAngle): + 'Add simplified path to fill.' + simplifiedLoop = euclidean.getSimplifiedLoop(loop, infillWidth) + if len(simplifiedLoop) < 2: + return + simplifiedLoop.append(simplifiedLoop[0]) + planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedLoop) + infillPaths.append(planeRotated) + +def addPath(infillWidth, infillPaths, path, rotationPlaneAngle): + 'Add simplified path to fill.' + simplifiedPath = euclidean.getSimplifiedPath(path, infillWidth) + if len(simplifiedPath) < 2: + return + planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedPath) + infillPaths.append(planeRotated) + +def addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ): + 'Add the path index of the closest segment found toward the second segment.' + for yStep in xrange( gridPixel[1], segmentFirstPixel[1] - 1, - 1 ): + if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ): + return + +def addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ): + 'Add the path index of the closest segment found toward the second segment.' + for yStep in xrange( gridPixel[1], segmentSecondPixel[1] + 1 ): + if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ): + return + +def addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ): + 'Add a point to a path and the pixel table.' + pointIndexMinusOne = pointIndex - 1 + if pointIndex < len(path) and pointIndexMinusOne >= 0: + segmentTable = {} + begin = path[ pointIndexMinusOne ] + end = path[pointIndex] + euclidean.addValueSegmentToPixelTable( begin, end, segmentTable, pathIndex, width ) + euclidean.removePixelTableFromPixelTable( segmentTable, pixelTable ) + if pointIndexMinusOne >= 0: + begin = path[ pointIndexMinusOne ] + euclidean.addValueSegmentToPixelTable( begin, point, pixelTable, pathIndex, width ) + if pointIndex < len(path): + end = path[pointIndex] + euclidean.addValueSegmentToPixelTable( point, end, pixelTable, pathIndex, width ) + path.insert( pointIndex, point ) + +def addPointOnPathIfFree( path, pathIndex, pixelTable, point, pointIndex, width ): + 'Add the closest point to a path, if the point added to a path is free.' + if isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ): + addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ) + +def addSparseEndpoints(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, solidSurfaceThickness, surroundingXIntersections): + 'Add sparse endpoints.' + segments = horizontalSegmentsDictionary[horizontalSegmentsDictionaryKey] + for segment in segments: + addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections) + +def addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections): + 'Add sparse endpoints from a segment.' + if infillSolidity > 0.0: + if int(round(round(float(horizontalSegmentsDictionaryKey) * infillSolidity) / infillSolidity)) == horizontalSegmentsDictionaryKey: + endpoints += segment + return + if abs(segment[0].point - segment[1].point) < doubleInfillWidth: + endpoints += segment + return + if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey - 1, segment): + endpoints += segment + return + if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey + 1, segment): + endpoints += segment + return + if solidSurfaceThickness == 0: + removedEndpoints += segment + return + if isSegmentCompletelyInAnIntersection(segment, surroundingXIntersections): + removedEndpoints += segment + return + endpoints += segment + +def addYIntersectionPathToList( pathIndex, pointIndex, y, yIntersection, yIntersectionPaths ): + 'Add the y intersection path to the y intersection paths.' + if yIntersection == None: + return + yIntersectionPath = YIntersectionPath( pathIndex, pointIndex, yIntersection ) + yIntersectionPath.yMinusCenter = yIntersection - y + yIntersectionPaths.append( yIntersectionPath ) + +def compareDistanceFromCenter(self, other): + 'Get comparison in order to sort y intersections in ascending order of distance from the center.' + distanceFromCenter = abs( self.yMinusCenter ) + distanceFromCenterOther = abs( other.yMinusCenter ) + if distanceFromCenter > distanceFromCenterOther: + return 1 + if distanceFromCenter < distanceFromCenterOther: + return - 1 + return 0 + +def comparePointIndexDescending(self, other): + 'Get comparison in order to sort y intersections in descending order of point index.' + if self.pointIndex > other.pointIndex: + return - 1 + if self.pointIndex < other.pointIndex: + return 1 + return 0 + +def createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded): + 'Create extra fill loops.' + for innerNestedRing in nestedRing.innerNestedRings: + createFillForSurroundings(innerNestedRing.innerNestedRings, radius, radiusAround, shouldExtraLoopsBeAdded) + allFillLoops = intercircle.getInsetSeparateLoopsFromAroundLoops(nestedRing.getLoopsToBeFilled(), radius, max(1.4 * radius, radiusAround)) + if len(allFillLoops) < 1: + return + if shouldExtraLoopsBeAdded: + nestedRing.extraLoops += allFillLoops + nestedRing.penultimateFillLoops = nestedRing.lastFillLoops + nestedRing.lastFillLoops = allFillLoops + +def createFillForSurroundings(nestedRings, radius, radiusAround, shouldExtraLoopsBeAdded): + 'Create extra fill loops for nested rings.' + for nestedRing in nestedRings: + createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded) + +def getAdditionalLength( path, point, pointIndex ): + 'Get the additional length added by inserting a point into a path.' + if pointIndex == 0: + return abs( point - path[0] ) + if pointIndex == len(path): + return abs( point - path[-1] ) + return abs( point - path[pointIndex - 1] ) + abs( point - path[pointIndex] ) - abs( path[pointIndex] - path[pointIndex - 1] ) + +def getClosestOppositeIntersectionPaths( yIntersectionPaths ): + 'Get the close to center paths, starting with the first and an additional opposite if it exists.' + yIntersectionPaths.sort( compareDistanceFromCenter ) + beforeFirst = yIntersectionPaths[0].yMinusCenter < 0.0 + yCloseToCenterPaths = [ yIntersectionPaths[0] ] + for yIntersectionPath in yIntersectionPaths[1 :]: + beforeSecond = yIntersectionPath.yMinusCenter < 0.0 + if beforeFirst != beforeSecond: + yCloseToCenterPaths.append( yIntersectionPath ) + return yCloseToCenterPaths + return yCloseToCenterPaths + +def getCraftedText( fileName, gcodeText = '', repository=None): + 'Fill the inset file or gcode text.' + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Fill the inset gcode text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fill'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( FillRepository() ) + if not repository.activateFill.value: + return gcodeText + return FillSkein().getCraftedGcode( repository, gcodeText ) + +def getKeyIsInPixelTableAddValue( key, pathIndexTable, pixelTable ): + 'Determine if the key is in the pixel table, and if it is and if the value is not None add it to the path index table.' + if key in pixelTable: + value = pixelTable[key] + if value != None: + pathIndexTable[value] = None + return True + return False + +def getLowerLeftCorner(nestedRings): + 'Get the lower left corner from the nestedRings.' + lowerLeftCorner = Vector3() + lowestRealPlusImaginary = 987654321.0 + for nestedRing in nestedRings: + for point in nestedRing.boundary: + realPlusImaginary = point.real + point.imag + if realPlusImaginary < lowestRealPlusImaginary: + lowestRealPlusImaginary = realPlusImaginary + lowerLeftCorner.setToXYZ(point.real, point.imag, nestedRing.z) + return lowerLeftCorner + +def getNewRepository(): + 'Get new repository.' + return FillRepository() + +def getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ): + 'Get the points around the grid point that is junction wide that do not intersect.' + pointIndexPlusOne = yIntersectionPath.getPointIndexPlusOne() + path = yIntersectionPath.getPath(paths) + begin = path[ yIntersectionPath.pointIndex ] + end = path[ pointIndexPlusOne ] + plusMinusSign = getPlusMinusSign( end.real - begin.real ) + if isJunctionWide: + gridPointXFirst = complex( yIntersectionPath.gridPoint.real - plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag ) + gridPointXSecond = complex( yIntersectionPath.gridPoint.real + plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag ) + if isAddedPointOnPathFree( path, pixelTable, gridPointXSecond, pointIndexPlusOne, width ): + if isAddedPointOnPathFree( path, pixelTable, gridPointXFirst, pointIndexPlusOne, width ): + return [ gridPointXSecond, gridPointXFirst ] + if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ): + return [ gridPointXSecond, yIntersectionPath.gridPoint ] + return [ gridPointXSecond ] + if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ): + return [ yIntersectionPath.gridPoint ] + return [] + +def getPlusMinusSign(number): + 'Get one if the number is zero or positive else negative one.' + if number >= 0.0: + return 1.0 + return - 1.0 + +def getWithLeastLength( path, point ): + 'Insert a point into a path, at the index at which the path would be shortest.' + if len(path) < 1: + return 0 + shortestPointIndex = None + shortestAdditionalLength = 999999999987654321.0 + for pointIndex in xrange( len(path) + 1 ): + additionalLength = getAdditionalLength( path, point, pointIndex ) + if additionalLength < shortestAdditionalLength: + shortestAdditionalLength = additionalLength + shortestPointIndex = pointIndex + return shortestPointIndex + +def getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, beginComplex, endComplex, x ): + 'Get the y intersection inside the y segment if it does, else none.' + yIntersection = euclidean.getYIntersectionIfExists( beginComplex, endComplex, x ) + if yIntersection == None: + return None + if yIntersection < min( segmentFirstY, segmentSecondY ): + return None + if yIntersection <= max( segmentFirstY, segmentSecondY ): + return yIntersection + return None + +def insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yIntersectionPath, width ): + 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.' + linePath = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ) + insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ) + +def insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, intersectionPathFirst, intersectionPathSecond, isBothOrNone, isJunctionWide, paths, pixelTable, width ): + 'Insert a pair of points around a pair of grid points.' + gridPointLineFirst = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width ) + if len( gridPointLineFirst ) < 1: + if isBothOrNone: + return + intersectionPathSecond.gridPoint = gridPoint + insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, intersectionPathSecond, width ) + return + gridPointLineSecond = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathSecond, width ) + if len( gridPointLineSecond ) > 0: + insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width ) + insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineSecond, paths, pixelTable, intersectionPathSecond, width ) + return + if isBothOrNone: + return + originalGridPointFirst = intersectionPathFirst.gridPoint + intersectionPathFirst.gridPoint = gridPoint + gridPointLineFirstCenter = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width ) + if len( gridPointLineFirstCenter ) > 0: + insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirstCenter, paths, pixelTable, intersectionPathFirst, width ) + return + intersectionPathFirst.gridPoint = originalGridPointFirst + insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width ) + +def insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ): + 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.' + if len( linePath ) < 1: + return + if gridPoint in gridPoints: + gridPoints.remove( gridPoint ) + intersectionBeginPoint = None + moreThanInset = 2.1 * gridPointInsetX + path = yIntersectionPath.getPath(paths) + begin = path[ yIntersectionPath.pointIndex ] + end = path[ yIntersectionPath.getPointIndexPlusOne() ] + if yIntersectionPath.isOutside: + distanceX = end.real - begin.real + if abs( distanceX ) > 2.1 * moreThanInset: + intersectionBeginXDistance = yIntersectionPath.gridPoint.real - begin.real + endIntersectionXDistance = end.real - yIntersectionPath.gridPoint.real + intersectionPoint = begin * endIntersectionXDistance / distanceX + end * intersectionBeginXDistance / distanceX + distanceYAbsoluteInset = max( abs( yIntersectionPath.gridPoint.imag - intersectionPoint.imag ), moreThanInset ) + intersectionEndSegment = end - intersectionPoint + intersectionEndSegmentLength = abs( intersectionEndSegment ) + if intersectionEndSegmentLength > 1.1 * distanceYAbsoluteInset: + intersectionEndPoint = intersectionPoint + intersectionEndSegment * distanceYAbsoluteInset / intersectionEndSegmentLength + path.insert( yIntersectionPath.getPointIndexPlusOne(), intersectionEndPoint ) + intersectionBeginSegment = begin - intersectionPoint + intersectionBeginSegmentLength = abs( intersectionBeginSegment ) + if intersectionBeginSegmentLength > 1.1 * distanceYAbsoluteInset: + intersectionBeginPoint = intersectionPoint + intersectionBeginSegment * distanceYAbsoluteInset / intersectionBeginSegmentLength + for point in linePath: + addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, point, yIntersectionPath.getPointIndexPlusOne(), width ) + if intersectionBeginPoint != None: + addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, intersectionBeginPoint, yIntersectionPath.getPointIndexPlusOne(), width ) + +def isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ): + 'Determine if the point added to a path is intersecting the pixel table or the path.' + if pointIndex > 0 and pointIndex < len(path): + if isSharpCorner( ( path[pointIndex - 1] ), point, ( path[pointIndex] ) ): + return False + pointIndexMinusOne = pointIndex - 1 + if pointIndexMinusOne >= 0: + maskTable = {} + begin = path[ pointIndexMinusOne ] + if pointIndex < len(path): + end = path[pointIndex] + euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width ) + segmentTable = {} + euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width ) + if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ): + return False + if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndexMinusOne ): + return False + if pointIndex < len(path): + maskTable = {} + begin = path[pointIndex] + if pointIndexMinusOne >= 0: + end = path[ pointIndexMinusOne ] + euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width ) + segmentTable = {} + euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width ) + if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ): + return False + if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ): + return False + return True + +def isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ): + 'Determine if the point added to a path is intersecting the path by checking line intersection.' + segment = point - begin + segmentLength = abs(segment) + if segmentLength <= 0.0: + return False + normalizedSegment = segment / segmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointRotated = segmentYMirror * point + beginRotated = segmentYMirror * begin + if euclidean.isXSegmentIntersectingPath( path[ max( 0, pointIndex - 20 ) : pointIndex ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ): + return True + return euclidean.isXSegmentIntersectingPath( path[ pointIndex + 1 : pointIndex + 21 ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ) + +def isIntersectingLoopsPaths( loops, paths, pointBegin, pointEnd ): + 'Determine if the segment between the first and second point is intersecting the loop list.' + normalizedSegment = pointEnd.dropAxis() - pointBegin.dropAxis() + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength == 0.0: + return False + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointBegin ) + pointEndRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointEnd ) + if euclidean.isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ): + return True + return euclidean.isXSegmentIntersectingPaths( paths, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ) + +def isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, width): + 'Add the closest removed endpoint to the path, with minimal twisting.' + closestDistanceSquared = 999999999987654321.0 + closestPathIndex = None + for pathIndex in xrange(len(paths)): + path = paths[ pathIndex ] + for pointIndex in xrange(len(path)): + point = path[pointIndex] + distanceSquared = abs(point - removedEndpointPoint) + if distanceSquared < closestDistanceSquared: + closestDistanceSquared = distanceSquared + closestPathIndex = pathIndex + if closestPathIndex == None: + return + if closestDistanceSquared < 0.8 * layerInfillWidth * layerInfillWidth: + return + closestPath = paths[closestPathIndex] + closestPointIndex = getWithLeastLength(closestPath, removedEndpointPoint) + if isAddedPointOnPathFree(closestPath, pixelTable, removedEndpointPoint, closestPointIndex, width): + addPointOnPath(closestPath, closestPathIndex, pixelTable, removedEndpointPoint, closestPointIndex, width) + return True + return isSidePointAdded(pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width) + +def isSegmentAround(aroundSegmentsDictionary, aroundSegmentsDictionaryKey, segment): + 'Determine if there is another segment around.' + if aroundSegmentsDictionaryKey not in aroundSegmentsDictionary: + return False + for aroundSegment in aroundSegmentsDictionary[aroundSegmentsDictionaryKey]: + endpoint = aroundSegment[0] + if isSegmentInX(segment, endpoint.point.real, endpoint.otherEndpoint.point.real): + return True + return False + +def isSegmentCompletelyInAnIntersection( segment, xIntersections ): + 'Add sparse endpoints from a segment.' + for xIntersectionIndex in xrange( 0, len( xIntersections ), 2 ): + surroundingXFirst = xIntersections[ xIntersectionIndex ] + surroundingXSecond = xIntersections[ xIntersectionIndex + 1 ] + if euclidean.isSegmentCompletelyInX( segment, surroundingXFirst, surroundingXSecond ): + return True + return False + +def isSegmentInX( segment, xFirst, xSecond ): + 'Determine if the segment overlaps within x.' + segmentFirstX = segment[0].point.real + segmentSecondX = segment[1].point.real + if min( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ): + return False + return max( segmentFirstX, segmentSecondX ) > min( xFirst, xSecond ) + +def isSharpCorner( beginComplex, centerComplex, endComplex ): + 'Determine if the three complex points form a sharp corner.' + centerBeginComplex = beginComplex - centerComplex + centerEndComplex = endComplex - centerComplex + centerBeginLength = abs( centerBeginComplex ) + centerEndLength = abs( centerEndComplex ) + if centerBeginLength <= 0.0 or centerEndLength <= 0.0: + return False + centerBeginComplex /= centerBeginLength + centerEndComplex /= centerEndLength + return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) > 0.9 + +def isSidePointAdded( pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width ): + 'Add side point along with the closest removed endpoint to the path, with minimal twisting.' + if closestPointIndex <= 0 or closestPointIndex >= len( closestPath ): + return False + pointBegin = closestPath[ closestPointIndex - 1 ] + pointEnd = closestPath[ closestPointIndex ] + removedEndpointPoint = removedEndpointPoint + closest = pointBegin + farthest = pointEnd + removedMinusClosest = removedEndpointPoint - pointBegin + removedMinusClosestLength = abs( removedMinusClosest ) + if removedMinusClosestLength <= 0.0: + return False + removedMinusOther = removedEndpointPoint - pointEnd + removedMinusOtherLength = abs( removedMinusOther ) + if removedMinusOtherLength <= 0.0: + return False + insertPointAfter = None + insertPointBefore = None + if removedMinusOtherLength < removedMinusClosestLength: + closest = pointEnd + farthest = pointBegin + removedMinusClosest = removedMinusOther + removedMinusClosestLength = removedMinusOtherLength + insertPointBefore = removedEndpointPoint + else: + insertPointAfter = removedEndpointPoint + removedMinusClosestNormalized = removedMinusClosest / removedMinusClosestLength + perpendicular = removedMinusClosestNormalized * complex( 0.0, layerInfillWidth ) + sidePoint = removedEndpointPoint + perpendicular + #extra check in case the line to the side point somehow slips by the line to the perpendicular + sidePointOther = removedEndpointPoint - perpendicular + if abs( sidePoint - farthest ) > abs( sidePointOther - farthest ): + perpendicular = - perpendicular + sidePoint = sidePointOther + maskTable = {} + closestSegmentTable = {} + toPerpendicularTable = {} + euclidean.addValueSegmentToPixelTable( pointBegin, pointEnd, maskTable, None, width ) + euclidean.addValueSegmentToPixelTable( closest, removedEndpointPoint, closestSegmentTable, None, width ) + euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width ) + if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ): + sidePoint = removedEndpointPoint - perpendicular + toPerpendicularTable = {} + euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width ) + if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ): + return False + if insertPointBefore != None: + addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointBefore, closestPointIndex, width ) + addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, sidePoint, closestPointIndex, width ) + if insertPointAfter != None: + addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointAfter, closestPointIndex, width ) + return True + +def removeEndpoints(layerInfillWidth, paths, pixelTable, removedEndpoints, aroundWidth): + 'Remove endpoints which are added to the path.' + for removedEndpointIndex in xrange(len(removedEndpoints) -1, -1, -1): + removedEndpoint = removedEndpoints[removedEndpointIndex] + removedEndpointPoint = removedEndpoint.point + if isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, aroundWidth): + removedEndpoints.remove(removedEndpoint ) + +def setIsOutside( yCloseToCenterPath, yIntersectionPaths ): + 'Determine if the yCloseToCenterPath is outside.' + beforeClose = yCloseToCenterPath.yMinusCenter < 0.0 + for yIntersectionPath in yIntersectionPaths: + if yIntersectionPath != yCloseToCenterPath: + beforePath = yIntersectionPath.yMinusCenter < 0.0 + if beforeClose == beforePath: + yCloseToCenterPath.isOutside = False + return + yCloseToCenterPath.isOutside = True + +def writeOutput(fileName, shouldAnalyze=True): + 'Fill an inset gcode file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fill', shouldAnalyze) + + +class FillRepository: + 'A class to handle the fill settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fill.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Fill', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill') + self.activateFill = settings.BooleanSetting().getFromValue('Activate Fill', self, True) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Diaphragm -', self ) + self.diaphragmPeriod = settings.IntSpin().getFromValue( 20, 'Diaphragm Period (layers):', self, 200, 100 ) + self.diaphragmThickness = settings.IntSpin().getFromValue( 0, 'Diaphragm Thickness (layers):', self, 5, 0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Extra Shells -', self ) + self.extraShellsAlternatingSolidLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Alternating Solid Layer (layers):', self, 3, 2 ) + self.extraShellsBase = settings.IntSpin().getFromValue( 0, 'Extra Shells on Base (layers):', self, 3, 1 ) + self.extraShellsSparseLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Sparse Layer (layers):', self, 3, 1 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Grid -', self ) + self.gridCircleSeparationOverEdgeWidth = settings.FloatSpin().getFromValue(0.0, 'Grid Circle Separation over Perimeter Width (ratio):', self, 1.0, 0.2) + self.gridExtraOverlap = settings.FloatSpin().getFromValue( 0.0, 'Grid Extra Overlap (ratio):', self, 0.5, 0.1 ) + self.gridJunctionSeparationBandHeight = settings.IntSpin().getFromValue( 0, 'Grid Junction Separation Band Height (layers):', self, 20, 10 ) + self.gridJunctionSeparationOverOctogonRadiusAtEnd = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At End (ratio):', self, 0.8, 0.0 ) + self.gridJunctionSeparationOverOctogonRadiusAtMiddle = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At Middle (ratio):', self, 0.8, 0.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Infill -', self ) + self.infillBeginRotation = settings.FloatSpin().getFromValue( 0.0, 'Infill Begin Rotation (degrees):', self, 90.0, 45.0 ) + self.infillBeginRotationRepeat = settings.IntSpin().getFromValue( 0, 'Infill Begin Rotation Repeat (layers):', self, 3, 1 ) + self.infillOddLayerExtraRotation = settings.FloatSpin().getFromValue( 30.0, 'Infill Odd Layer Extra Rotation (degrees):', self, 90.0, 90.0 ) + self.infillPatternLabel = settings.LabelDisplay().getFromName('Infill Pattern:', self ) + infillLatentStringVar = settings.LatentStringVar() + self.infillPatternGridCircular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Circular', self, False ) + self.infillPatternGridHexagonal = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Hexagonal', self, False ) + self.infillPatternGridRectangular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Rectangular', self, False ) + self.infillPatternLine = settings.Radio().getFromRadio( infillLatentStringVar, 'Line', self, True ) + self.infillPerimeterOverlap = settings.FloatSpin().getFromValue( 0.0, 'Infill Perimeter Overlap (ratio):', self, 0.4, 0.15 ) + self.infillSolidity = settings.FloatSpin().getFromValue( 0.04, 'Infill Solidity (ratio):', self, 0.3, 0.2 ) + self.infillWidth = settings.FloatSpin().getFromValue( 0.1, 'Infill Width:', self, 1.7, 0.4 ) + settings.LabelSeparator().getFromRepository(self) + self.solidSurfaceThickness = settings.IntSpin().getFromValue(0, 'Solid Surface Thickness (layers):', self, 5, 3) + self.startFromChoice = settings.MenuButtonDisplay().getFromName('Start From Choice:', self) + self.startFromLowerLeft = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Lower Left', self, True) + self.startFromNearest = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Nearest', self, False) + self.surroundingAngle = settings.FloatSpin().getFromValue(30.0, 'Surrounding Angle (degrees):', self, 80.0, 60.0) + self.threadSequenceChoice = settings.MenuButtonDisplay().getFromName('Thread Sequence Choice:', self) + self.threadSequenceInfillLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Loops > Perimeter', self, False) + self.threadSequenceInfillPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Perimeter > Loops', self, False) + self.threadSequenceLoopsInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Infill > Perimeter', self, False) + self.threadSequenceLoopsPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Perimeter > Infill', self, True) + self.threadSequencePerimeterInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Infill > Loops', self, False) + self.threadSequencePerimeterLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Loops > Infill', self, False) + self.executeTitle = 'Fill' + + def execute(self): + 'Fill button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class FillSkein: + 'A class to fill a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = None + self.extruderActive = False + self.fillInset = 0.18 + self.isEdge = False + self.lastExtraShells = - 1 + self.lineIndex = 0 + self.oldLocation = None + self.oldOrderedLocation = None + self.rotatedLayer = None + self.rotatedLayers = [] + self.shutdownLineIndex = sys.maxint + self.nestedRing = None + self.thread = None + + def addFill(self, layerIndex): + 'Add fill to the carve layer.' +# if layerIndex > 2: +# return + settings.printProgressByNumber(layerIndex, len(self.rotatedLayers), 'fill') + arounds = [] + endpoints = [] + extraShells = self.repository.extraShellsSparseLayer.value + infillPaths = [] + layerFillInset = self.fillInset + layerInfillSolidity = self.infillSolidity + layerRemainder = layerIndex % int(round(self.repository.diaphragmPeriod.value)) + layerRotation = self.getLayerRotation(layerIndex) + pixelTable = {} + reverseRotation = complex(layerRotation.real, - layerRotation.imag) + rotatedLayer = self.rotatedLayers[layerIndex] + self.isDoubleJunction = True + self.isJunctionWide = True + surroundingCarves = [] + self.distanceFeedRate.addLine('( %s )' % rotatedLayer.z) + if layerRemainder >= int(round(self.repository.diaphragmThickness.value)): + for surroundingIndex in xrange(1, self.solidSurfaceThickness + 1): + self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves) + self.addRotatedCarve(layerIndex, surroundingIndex, reverseRotation, surroundingCarves) + if len(surroundingCarves) < self.doubleSolidSurfaceThickness: + extraShells = self.repository.extraShellsAlternatingSolidLayer.value + if self.lastExtraShells != self.repository.extraShellsBase.value: + extraShells = self.repository.extraShellsBase.value + if rotatedLayer.rotation != None: + extraShells = 0 + self.distanceFeedRate.addLine('( %s )' % layerRotation) + self.distanceFeedRate.addLine('( %s )' % layerRotation) + aroundWidth = 0.34321 * self.infillWidth + doubleInfillWidth = 2.0 * self.infillWidth + gridPointInsetX = 0.5 * self.fillInset + self.lastExtraShells = extraShells + if self.repository.infillPatternGridHexagonal.value: + infillBeginRotationPolar = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation) + if abs(euclidean.getDotProduct(layerRotation, infillBeginRotationPolar)) < math.sqrt( 0.5): + layerInfillSolidity *= 0.5 + self.isDoubleJunction = False + else: + self.isJunctionWide = False + nestedRings = euclidean.getOrderedNestedRings(rotatedLayer.nestedRings) + radiusAround = 0.5 * min(self.infillWidth, self.edgeWidth) + createFillForSurroundings(nestedRings, self.edgeMinusHalfInfillWidth, radiusAround, False) + for extraShellIndex in xrange(extraShells): + createFillForSurroundings(nestedRings, self.infillWidth, radiusAround, True) + fillLoops = euclidean.getFillOfSurroundings(nestedRings, None) + rotatedLoops = euclidean.getRotatedComplexLists(reverseRotation, fillLoops) + infillDictionary = triangle_mesh.getInfillDictionary(arounds, aroundWidth, self.fillInset, self.infillWidth, pixelTable, rotatedLoops) + if len(arounds) < 1: + self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer) + return + self.horizontalSegmentsDictionary = {} + for infillDictionaryKey in infillDictionary.keys(): + xIntersections = infillDictionary[infillDictionaryKey] + xIntersections.sort() + y = infillDictionaryKey * self.infillWidth + self.horizontalSegmentsDictionary[infillDictionaryKey] = euclidean.getSegmentsFromXIntersections(xIntersections, y) + self.surroundingXIntersectionsDictionary = {} + gridCircular = False + removedEndpoints = [] + if len(surroundingCarves) >= self.doubleSolidSurfaceThickness: + if self.repository.infillPatternGridCircular.value and self.repository.infillSolidity.value > 0.0: + gridCircular = True + layerInfillSolidity = 0.0 + xSurroundingIntersectionsDictionaries = [infillDictionary] + for surroundingCarve in surroundingCarves: + xSurroundingIntersectionsDictionary = {} + euclidean.addXIntersectionsFromLoopsForTable(surroundingCarve, xSurroundingIntersectionsDictionary, self.infillWidth) + xSurroundingIntersectionsDictionaries.append(xSurroundingIntersectionsDictionary) + self.surroundingXIntersectionsDictionary = euclidean.getIntersectionOfXIntersectionsTables(xSurroundingIntersectionsDictionaries) + for horizontalSegmentsDictionaryKey in self.horizontalSegmentsDictionary.keys(): + if horizontalSegmentsDictionaryKey in self.surroundingXIntersectionsDictionary: + surroundingXIntersections = self.surroundingXIntersectionsDictionary[horizontalSegmentsDictionaryKey] + else: + surroundingXIntersections = [] + addSparseEndpoints(doubleInfillWidth, endpoints, self.horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, layerInfillSolidity, removedEndpoints, self.solidSurfaceThickness, surroundingXIntersections) + else: + for segments in self.horizontalSegmentsDictionary.values(): + for segment in segments: + endpoints += segment + paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, aroundWidth) + if gridCircular: + startAngle = euclidean.globalGoldenAngle * float(layerIndex) + for gridPoint in self.getGridPoints(fillLoops, reverseRotation): + self.addGridCircle(gridPoint, infillPaths, layerRotation, pixelTable, rotatedLoops, layerRotation, aroundWidth) + else: + if self.isGridToBeExtruded(): + self.addGrid( + arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, aroundWidth) + oldRemovedEndpointLength = len(removedEndpoints) + 1 + while oldRemovedEndpointLength - len(removedEndpoints) > 0: + oldRemovedEndpointLength = len(removedEndpoints) + removeEndpoints(self.infillWidth, paths, pixelTable, removedEndpoints, aroundWidth) + paths = euclidean.getConnectedPaths(paths, pixelTable, aroundWidth) + for path in paths: + addPath(self.infillWidth, infillPaths, path, layerRotation) + euclidean.transferPathsToNestedRings(nestedRings, infillPaths) + for fillLoop in fillLoops: + addInfillBoundary(fillLoop, nestedRings) + self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer) + + def addGcodeFromThreadZ( self, thread, z ): + 'Add a gcode thread to the output.' + self.distanceFeedRate.addGcodeFromThreadZ( thread, z ) + + def addGrid(self, arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, width): + 'Add the grid to the infill layer.' + if len(surroundingCarves) < self.doubleSolidSurfaceThickness: + return + explodedPaths = [] + pathGroups = [] + for path in paths: + pathIndexBegin = len( explodedPaths ) + for pointIndex in xrange( len(path) - 1 ): + pathSegment = [ path[pointIndex], path[pointIndex + 1] ] + explodedPaths.append( pathSegment ) + pathGroups.append( ( pathIndexBegin, len( explodedPaths ) ) ) + for pathIndex in xrange( len( explodedPaths ) ): + explodedPath = explodedPaths[ pathIndex ] + euclidean.addPathToPixelTable( explodedPath, pixelTable, pathIndex, width ) + gridPoints = self.getGridPoints(fillLoops, reverseRotation) + gridPointInsetY = gridPointInsetX * ( 1.0 - self.repository.gridExtraOverlap.value ) + if self.repository.infillPatternGridRectangular.value: + gridBandHeight = self.repository.gridJunctionSeparationBandHeight.value + gridLayerRemainder = ( layerIndex - self.solidSurfaceThickness ) % gridBandHeight + halfBandHeight = 0.5 * float( gridBandHeight ) + halfBandHeightFloor = math.floor( halfBandHeight ) + fromMiddle = math.floor( abs( gridLayerRemainder - halfBandHeight ) ) + fromEnd = halfBandHeightFloor - fromMiddle + gridJunctionSeparation = self.gridJunctionEnd * fromMiddle + self.gridJunctionMiddle * fromEnd + gridJunctionSeparation /= halfBandHeightFloor + gridPointInsetX += gridJunctionSeparation + gridPointInsetY += gridJunctionSeparation + oldGridPointLength = len( gridPoints ) + 1 + while oldGridPointLength - len( gridPoints ) > 0: + oldGridPointLength = len( gridPoints ) + self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, True, explodedPaths, pixelTable, width ) + oldGridPointLength = len( gridPoints ) + 1 + while oldGridPointLength - len( gridPoints ) > 0: + oldGridPointLength = len( gridPoints ) + self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, False, explodedPaths, pixelTable, width ) + for pathGroupIndex in xrange( len( pathGroups ) ): + pathGroup = pathGroups[ pathGroupIndex ] + paths[ pathGroupIndex ] = [] + for explodedPathIndex in xrange( pathGroup[0], pathGroup[1] ): + explodedPath = explodedPaths[ explodedPathIndex ] + if len( paths[ pathGroupIndex ] ) == 0: + paths[ pathGroupIndex ] = explodedPath + else: + paths[ pathGroupIndex ] += explodedPath[1 :] + + def addGridCircle(self, center, infillPaths, layerRotation, pixelTable, rotatedLoops, startRotation, width): + 'Add circle to the grid.' + startAngle = -math.atan2(startRotation.imag, startRotation.real) + loop = euclidean.getComplexPolygon(center, self.gridCircleRadius, 17, startAngle) + loopPixelDictionary = {} + euclidean.addLoopToPixelTable(loop, loopPixelDictionary, width) + if not euclidean.isPixelTableIntersecting(pixelTable, loopPixelDictionary): + if euclidean.getIsInFilledRegion(rotatedLoops, euclidean.getLeftPoint(loop)): + addLoop(self.infillWidth, infillPaths, loop, layerRotation) + return + insideIndexPaths = [] + insideIndexPath = None + for pointIndex, point in enumerate(loop): + nextPoint = loop[(pointIndex + 1) % len(loop)] + segmentDictionary = {} + euclidean.addValueSegmentToPixelTable(point, nextPoint, segmentDictionary, None, width) + euclidean.addSquareTwoToPixelDictionary(segmentDictionary, point, None, width) + euclidean.addSquareTwoToPixelDictionary(segmentDictionary, nextPoint, None, width) + shouldAddLoop = not euclidean.isPixelTableIntersecting(pixelTable, segmentDictionary) + if shouldAddLoop: + shouldAddLoop = euclidean.getIsInFilledRegion(rotatedLoops, point) + if shouldAddLoop: + if insideIndexPath == None: + insideIndexPath = [pointIndex] + insideIndexPaths.append(insideIndexPath) + else: + insideIndexPath.append(pointIndex) + else: + insideIndexPath = None + if len(insideIndexPaths) > 1: + insideIndexPathFirst = insideIndexPaths[0] + insideIndexPathLast = insideIndexPaths[-1] + if insideIndexPathFirst[0] == 0 and insideIndexPathLast[-1] == len(loop) - 1: + insideIndexPaths[0] = insideIndexPathLast + insideIndexPathFirst + del insideIndexPaths[-1] + for insideIndexPath in insideIndexPaths: + path = [] + for insideIndex in insideIndexPath: + if len(path) == 0: + path.append(loop[insideIndex]) + path.append(loop[(insideIndex + 1) % len(loop)]) + addPath(self.infillWidth, infillPaths, path, layerRotation) + + def addGridLinePoints( self, begin, end, gridPoints, gridRotationAngle, offset, y ): + 'Add the segments of one line of a grid to the infill.' + if self.gridRadius == 0.0: + return + gridXStep = int(math.floor((begin) / self.gridXStepSize)) - 3 + gridXOffset = offset + self.gridXStepSize * float(gridXStep) + while gridXOffset < end: + if gridXOffset >= begin: + gridPointComplex = complex(gridXOffset, y) * gridRotationAngle + if self.repository.infillPatternGridCircular.value or self.isPointInsideLineSegments(gridPointComplex): + gridPoints.append(gridPointComplex) + gridXStep = self.getNextGripXStep(gridXStep) + gridXOffset = offset + self.gridXStepSize * float(gridXStep) + + def addRemainingGridPoints( + self, arounds, gridPointInsetX, gridPointInsetY, gridPoints, isBothOrNone, paths, pixelTable, width): + 'Add the remaining grid points to the grid point list.' + for gridPointIndex in xrange( len( gridPoints ) - 1, - 1, - 1 ): + gridPoint = gridPoints[ gridPointIndex ] + addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, self.gridRadius, isBothOrNone, self.isDoubleJunction, self.isJunctionWide, paths, pixelTable, width ) + + def addRotatedCarve(self, currentLayer, layerDelta, reverseRotation, surroundingCarves): + 'Add a rotated carve to the surrounding carves.rotatedCarveDictionary' + layerIndex = currentLayer + layerDelta + if layerIndex < 0 or layerIndex >= len(self.rotatedLayers): + return + layerDifference = abs(layerDelta) + rotatedLayer = self.rotatedLayers[layerIndex] + if layerDifference in rotatedLayer.rotatedCarveDictionary: + surroundingCarves.append(rotatedLayer.rotatedCarveDictionary[layerDifference]) + return + nestedRings = rotatedLayer.nestedRings + rotatedCarve = [] + for nestedRing in nestedRings: + planeRotatedLoop = euclidean.getRotatedComplexes(reverseRotation, nestedRing.boundary) + rotatedCarve.append(planeRotatedLoop) + outsetRadius = float(layerDifference) * self.layerHeight * self.surroundingSlope - self.edgeWidth + if outsetRadius > 0.0: + rotatedCarve = intercircle.getInsetSeparateLoopsFromAroundLoops(rotatedCarve, -outsetRadius, self.layerHeight) + surroundingCarves.append(rotatedCarve) + rotatedLayer.rotatedCarveDictionary[layerDifference] = rotatedCarve + + def addThreadsBridgeLayer(self, layerIndex, nestedRings, rotatedLayer, testLoops=None): + 'Add the threads, add the bridge end & the layer end tag.' + if self.oldOrderedLocation == None or self.repository.startFromLowerLeft.value: + self.oldOrderedLocation = getLowerLeftCorner(nestedRings) + extrusionHalfWidth = 0.5 * self.infillWidth + threadSequence = self.threadSequence + if layerIndex < 1: + threadSequence = ['edge', 'loops', 'infill'] + euclidean.addToThreadsRemove(extrusionHalfWidth, nestedRings, self.oldOrderedLocation, self, threadSequence) + if testLoops != None: + for testLoop in testLoops: + self.addGcodeFromThreadZ(testLoop, self.oldOrderedLocation.z) + self.distanceFeedRate.addLine('()') + if rotatedLayer.rotation != None: + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLine('()') + + def addToThread(self, location): + 'Add a location to thread.' + if self.oldLocation == None: + return + if self.isEdge: + self.nestedRing.addToLoop( location ) + return + if self.thread == None: + self.thread = [ self.oldLocation.dropAxis() ] + self.nestedRing.edgePaths.append(self.thread) + self.thread.append(location.dropAxis()) + + def getCraftedGcode( self, repository, gcodeText ): + 'Parse gcode text and store the bevel gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.threadSequence = None + if repository.threadSequenceInfillLoops.value: + self.threadSequence = ['infill', 'loops', 'edge'] + if repository.threadSequenceInfillPerimeter.value: + self.threadSequence = ['infill', 'edge', 'loops'] + if repository.threadSequenceLoopsInfill.value: + self.threadSequence = ['loops', 'infill', 'edge'] + if repository.threadSequenceLoopsPerimeter.value: + self.threadSequence = ['loops', 'edge', 'infill'] + if repository.threadSequencePerimeterInfill.value: + self.threadSequence = ['edge', 'infill', 'loops'] + if repository.threadSequencePerimeterLoops.value: + self.threadSequence = ['edge', 'loops', 'infill'] + if self.repository.infillPerimeterOverlap.value > 0.45: + print('') + print('!!! WARNING !!!') + print('"Infill Perimeter Overlap" is greater than 0.45, which may create problems with the infill, like threads going through empty space and/or the extruder switching on and off a lot.') + print('If you want to stretch the infill a lot, set "Path Stretch over Perimeter Width" in stretch to a high value instead of setting "Infill Perimeter Overlap" to a high value.') + print('') + self.parseInitialization() + if self.edgeWidth == None: + print('Warning, nothing will be done because self.edgeWidth in getCraftedGcode in FillSkein was None.') + return '' + self.fillInset = self.infillWidth - self.infillWidth * self.repository.infillPerimeterOverlap.value + self.infillSolidity = repository.infillSolidity.value + self.edgeMinusHalfInfillWidth = self.edgeWidth - 0.5 * self.infillWidth + if self.isGridToBeExtruded(): + self.setGridVariables(repository) + self.infillBeginRotation = math.radians( repository.infillBeginRotation.value ) + self.infillOddLayerExtraRotation = math.radians( repository.infillOddLayerExtraRotation.value ) + self.solidSurfaceThickness = int( round( self.repository.solidSurfaceThickness.value ) ) + self.doubleSolidSurfaceThickness = self.solidSurfaceThickness + self.solidSurfaceThickness + for lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine( lineIndex ) + for layerIndex in xrange(len(self.rotatedLayers)): + self.addFill(layerIndex) + self.distanceFeedRate.addLines( self.lines[ self.shutdownLineIndex : ] ) + return self.distanceFeedRate.output.getvalue() + + def getGridPoints(self, fillLoops, reverseRotation): + 'Get the grid points.' + if self.infillSolidity > 0.8: + return [] + rotationBaseAngle = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation) + reverseRotationBaseAngle = complex(rotationBaseAngle.real, - rotationBaseAngle.imag) + gridRotationAngle = reverseRotation * rotationBaseAngle + slightlyGreaterThanFillInset = intercircle.globalIntercircleMultiplier * self.gridInset + triangle_mesh.sortLoopsInOrderOfArea(True, fillLoops) + rotatedLoops = euclidean.getRotatedComplexLists(reverseRotationBaseAngle, fillLoops) + if self.repository.infillPatternGridCircular.value: + return self.getGridPointsByLoops( + gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, -self.gridCircleRadius)) + return self.getGridPointsByLoops(gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, self.gridInset)) + + def getGridPointsByLoops(self, gridRotationAngle, loops): + 'Get the grid points by loops.' + gridIntersectionsDictionary = {} + gridPoints = [] + euclidean.addXIntersectionsFromLoopsForTable(loops, gridIntersectionsDictionary, self.gridRadius) + for gridIntersectionsKey in gridIntersectionsDictionary: + y = gridIntersectionsKey * self.gridRadius + self.gridRadius * 0.5 + gridIntersections = gridIntersectionsDictionary[gridIntersectionsKey] + gridIntersections.sort() + gridIntersectionsLength = len(gridIntersections) + if gridIntersectionsLength % 2 == 1: + gridIntersectionsLength -= 1 + for gridIntersectionIndex in xrange(0, gridIntersectionsLength, 2): + begin = gridIntersections[gridIntersectionIndex] + end = gridIntersections[gridIntersectionIndex + 1] + offset = self.offsetMultiplier * (gridIntersectionsKey % 2) + self.offsetBaseX + self.addGridLinePoints(begin, end, gridPoints, gridRotationAngle, offset, y) + return gridPoints + + def getLayerRotation(self, layerIndex): + 'Get the layer rotation.' + rotation = self.rotatedLayers[layerIndex].rotation + if rotation != None: + return rotation + infillBeginRotationRepeat = self.repository.infillBeginRotationRepeat.value + infillOddLayerRotationMultiplier = float( layerIndex % ( infillBeginRotationRepeat + 1 ) == infillBeginRotationRepeat ) + layerAngle = self.infillBeginRotation + infillOddLayerRotationMultiplier * self.infillOddLayerExtraRotation + return euclidean.getWiddershinsUnitPolar(layerAngle) + + def getNextGripXStep( self, gridXStep ): + 'Get the next grid x step, increment by an extra one every three if hexagonal grid is chosen.' + gridXStep += 1 + if self.repository.infillPatternGridHexagonal.value: + if gridXStep % 3 == 0: + gridXStep += 1 + return gridXStep + + def isGridToBeExtruded(self): + 'Determine if the grid is to be extruded.' + if self.repository.infillPatternLine.value: + return False + return self.repository.infillSolidity.value > 0.0 + + def isPointInsideLineSegments( self, gridPoint ): + 'Is the point inside the line segments of the loops.' + if self.solidSurfaceThickness <= 0: + return True + fillLine = int(round(gridPoint.imag / self.infillWidth)) + if fillLine not in self.horizontalSegmentsDictionary: + return False + if fillLine not in self.surroundingXIntersectionsDictionary: + return False + lineSegments = self.horizontalSegmentsDictionary[fillLine] + surroundingXIntersections = self.surroundingXIntersectionsDictionary[fillLine] + for lineSegment in lineSegments: + if isSegmentCompletelyInAnIntersection(lineSegment, surroundingXIntersections ): + xFirst = lineSegment[0].point.real + xSecond = lineSegment[1].point.real + if gridPoint.real > min(xFirst, xSecond) and gridPoint.real < max(xFirst, xSecond): + return True + return False + + def linearMove( self, splitLine ): + 'Add a linear move to the thread.' + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.extruderActive: + self.addToThread( location ) + self.oldLocation = location + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addLine(line) + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + self.infillWidth = self.repository.infillWidth.value + self.surroundingSlope = math.tan(math.radians(min(self.repository.surroundingAngle.value, 80.0))) + self.distanceFeedRate.addTagRoundedLine('infillPerimeterOverlap', self.repository.infillPerimeterOverlap.value) + self.distanceFeedRate.addTagRoundedLine('infillWidth', self.infillWidth) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + threadSequenceString = ' '.join( self.threadSequence ) + self.distanceFeedRate.addTagBracketedLine('threadSequenceString', threadSequenceString ) + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('fill') + self.distanceFeedRate.addLine(line) + + def parseLine( self, lineIndex ): + 'Parse a gcode line and add it to the fill skein.' + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.isEdge = False + self.thread = None + elif firstWord == '()': + self.nestedRing = euclidean.NestedBand() + self.rotatedLayer.nestedRings.append( self.nestedRing ) + elif firstWord == '()': + self.nestedRing = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.nestedRing.addToBoundary( location ) + elif firstWord == '(': + self.rotatedLayer.rotation = gcodec.getRotationBySplitLine(splitLine) + elif firstWord == '()': + self.shutdownLineIndex = lineIndex + elif firstWord == '(': + self.rotatedLayer = RotatedLayer(float(splitLine[1])) + self.rotatedLayers.append( self.rotatedLayer ) + self.thread = None + elif firstWord == '(': + self.isEdge = True + + def setGridVariables( self, repository ): + 'Set the grid variables.' + self.gridInset = 1.2 * self.infillWidth + self.gridRadius = self.infillWidth / self.infillSolidity + self.gridXStepSize = 2.0 * self.gridRadius + self.offsetMultiplier = self.gridRadius + if self.repository.infillPatternGridHexagonal.value: + self.gridXStepSize = 4.0 / 3.0 * self.gridRadius + self.offsetMultiplier = 1.5 * self.gridXStepSize + if self.repository.infillPatternGridCircular.value: + self.gridRadius += self.gridRadius + self.gridXStepSize = self.gridRadius / math.sqrt(.75) + self.offsetMultiplier = 0.5 * self.gridXStepSize + circleInsetOverEdgeWidth = repository.gridCircleSeparationOverEdgeWidth.value + 0.5 + self.gridMinimumCircleRadius = self.edgeWidth + self.gridInset = self.gridMinimumCircleRadius + self.gridCircleRadius = self.offsetMultiplier - circleInsetOverEdgeWidth * self.edgeWidth + if self.gridCircleRadius < self.gridMinimumCircleRadius: + print('') + print('!!! WARNING !!!') + print('Grid Circle Separation over Edge Width is too high, which makes the grid circles too small.') + print('You should reduce Grid Circle Separation over Edge Width to a reasonable value, like the default of 0.5.') + print('The grid circle radius will be set to the minimum grid circle radius.') + print('') + self.gridCircleRadius = self.gridMinimumCircleRadius + self.offsetBaseX = 0.25 * self.gridXStepSize + if self.repository.infillPatternGridRectangular.value: + halfGridMinusWidth = 0.5 * ( self.gridRadius - self.infillWidth ) + self.gridJunctionEnd = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtEnd.value + self.gridJunctionMiddle = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtMiddle.value + + +class RotatedLayer: + 'A rotated layer.' + def __init__( self, z ): + 'Initialize.' + self.rotatedCarveDictionary = {} + self.rotation = None + self.nestedRings = [] + self.z = z + + def __repr__(self): + 'Get the string representation of this RotatedLayer.' + return '%s, %s, %s' % ( self.z, self.rotation, self.nestedRings ) + + +class YIntersectionPath: + 'A class to hold the y intersection position, the loop which it intersected and the point index of the loop which it intersected.' + def __init__( self, pathIndex, pointIndex, y ): + 'Initialize from the path, point index, and y.' + self.pathIndex = pathIndex + self.pointIndex = pointIndex + self.y = y + + def __repr__(self): + 'Get the string representation of this y intersection.' + return '%s, %s, %s' % ( self.pathIndex, self.pointIndex, self.y ) + + def getPath( self, paths ): + 'Get the path from the paths and path index.' + return paths[ self.pathIndex ] + + def getPointIndexPlusOne(self): + 'Get the point index plus one.' + return self.pointIndex + 1 + + +def main(): + 'Display the fill dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fillet.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fillet.py new file mode 100644 index 0000000..d5af827 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/fillet.py @@ -0,0 +1,393 @@ +""" +This page is in the table of contents. +Fillet rounds the corners slightly in a variety of ways. This is to reduce corner blobbing and sudden extruder acceleration. + +The fillet manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fillet + +==Operation== +The default 'Activate Fillet' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Fillet Procedure Choice=== +Default is 'Bevel''. + +====Arc Point==== +When selected, the corners will be filleted with an arc using the gcode point form. + +====Arc Radius==== +When selected, the corners will be filleted with an arc using the gcode radius form. + +====Arc Segment==== +When selected, the corners will be filleted with an arc composed of several segments. + +====Bevel==== +When selected, the corners will be beveled. + +===Corner Feed Rate Multiplier=== +Default: 1.0 + +Defines the ratio of the feed rate in corners over the original feed rate. With a high value the extruder will move quickly in corners, accelerating quickly and leaving a thin extrusion. With a low value, the extruder will move slowly in corners, accelerating gently and leaving a thick extrusion. + +===Fillet Radius over Perimeter Width=== +Default is 0.35. + +Defines the width of the fillet. + +===Reversal Slowdown over Perimeter Width=== +Default is 0.5. + +Defines how far before a path reversal the extruder will slow down. Some tools, like nozzle wipe, double back the path of the extruder and this option will add a slowdown point in that path so there won't be a sudden jerk at the end of the path. If the value is less than 0.1 a slowdown will not be added. + +===Use Intermediate Feed Rate in Corners=== +Default is on. + +When selected, the feed rate entering the corner will be the average of the old feed rate and the new feed rate. + +==Examples== +The following examples fillet the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and fillet.py. + +> python fillet.py +This brings up the fillet dialog. + +> python fillet.py Screw Holder Bottom.stl +The fillet tool is parsing the file: +Screw Holder Bottom.stl +.. +The fillet tool has created the file: +.. Screw Holder Bottom_fillet.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText, repository = None ): + "Fillet a gcode linear move file or text." + return getCraftedTextFromText( archive.getTextIfEmpty( fileName, gcodeText ), repository ) + +def getCraftedTextFromText( gcodeText, repository = None ): + "Fillet a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fillet'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( FilletRepository() ) + if not repository.activateFillet.value: + return gcodeText + if repository.arcPoint.value: + return ArcPointSkein().getCraftedGcode( repository, gcodeText ) + elif repository.arcRadius.value: + return ArcRadiusSkein().getCraftedGcode( repository, gcodeText ) + elif repository.arcSegment.value: + return ArcSegmentSkein().getCraftedGcode( repository, gcodeText ) + elif repository.bevel.value: + return BevelSkein().getCraftedGcode( repository, gcodeText ) + return gcodeText + +def getNewRepository(): + 'Get new repository.' + return FilletRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Fillet a gcode linear move file. Depending on the settings, either arcPoint, arcRadius, arcSegment, bevel or do nothing." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fillet', shouldAnalyze) + + +class BevelSkein: + "A class to bevel a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.feedRateMinute = 960.0 + self.filletRadius = 0.2 + self.lineIndex = 0 + self.lines = None + self.oldFeedRateMinute = None + self.oldLocation = None + self.shouldAddLine = True + + def addLinearMovePoint( self, feedRateMinute, point ): + "Add a gcode linear move, feedRate and newline to the output." + self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( feedRateMinute, point.dropAxis(), point.z ) ) + + def getCornerFeedRate(self): + "Get the corner feed rate, which may be based on the intermediate feed rate." + feedRateMinute = self.feedRateMinute + if self.repository.useIntermediateFeedRateInCorners.value: + if self.oldFeedRateMinute != None: + feedRateMinute = 0.5 * ( self.oldFeedRateMinute + self.feedRateMinute ) + return feedRateMinute * self.cornerFeedRateMultiplier + + def getCraftedGcode( self, repository, gcodeText ): + "Parse gcode text and store the bevel gcode." + self.cornerFeedRateMultiplier = repository.cornerFeedRateMultiplier.value + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.parseInitialization( repository ) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getExtruderOffReversalPoint( self, afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location ): + "If the extruder is off and the path is reversing, add intermediate slow points." + if self.repository.reversalSlowdownDistanceOverEdgeWidth.value < 0.1: + return None + if self.extruderActive: + return None + reversalBufferSlowdownDistance = self.reversalSlowdownDistance * 2.0 + afterSegmentComplexLength = abs( afterSegmentComplex ) + if afterSegmentComplexLength < reversalBufferSlowdownDistance: + return None + beforeSegmentComplexLength = abs( beforeSegmentComplex ) + if beforeSegmentComplexLength < reversalBufferSlowdownDistance: + return None + afterSegmentComplexNormalized = afterSegmentComplex / afterSegmentComplexLength + beforeSegmentComplexNormalized = beforeSegmentComplex / beforeSegmentComplexLength + if euclidean.getDotProduct( afterSegmentComplexNormalized, beforeSegmentComplexNormalized ) < 0.95: + return None + slowdownFeedRate = self.feedRateMinute * 0.5 + self.shouldAddLine = False + beforePoint = euclidean.getPointPlusSegmentWithLength( self.reversalSlowdownDistance * abs( beforeSegment ) / beforeSegmentComplexLength, location, beforeSegment ) + self.addLinearMovePoint( self.feedRateMinute, beforePoint ) + self.addLinearMovePoint( slowdownFeedRate, location ) + afterPoint = euclidean.getPointPlusSegmentWithLength( self.reversalSlowdownDistance * abs( afterSegment ) / afterSegmentComplexLength, location, afterSegment ) + self.addLinearMovePoint( slowdownFeedRate, afterPoint ) + return afterPoint + + def getNextLocation(self): + "Get the next linear move. Return none is none is found." + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if gcodec.getFirstWord(splitLine) == 'G1': + nextLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + return nextLocation + return None + + def linearMove( self, splitLine ): + "Bevel a linear move." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + if self.oldLocation != None: + nextLocation = self.getNextLocation() + if nextLocation != None: + location = self.splitPointGetAfter( location, nextLocation ) + self.oldLocation = location + self.oldFeedRateMinute = self.feedRateMinute + + def parseInitialization( self, repository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('fillet') + return + elif firstWord == '(': + edgeWidth = abs(float(splitLine[1])) + self.curveSection = 0.7 * edgeWidth + self.filletRadius = edgeWidth * repository.filletRadiusOverEdgeWidth.value + self.minimumRadius = 0.1 * edgeWidth + self.reversalSlowdownDistance = edgeWidth * repository.reversalSlowdownDistanceOverEdgeWidth.value + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + self.shouldAddLine = True + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.linearMove(splitLine) + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + if self.shouldAddLine: + self.distanceFeedRate.addLine(line) + + def splitPointGetAfter( self, location, nextLocation ): + "Bevel a point and return the end of the bevel. should get complex for radius" + if self.filletRadius < 2.0 * self.minimumRadius: + return location + afterSegment = nextLocation - location + afterSegmentComplex = afterSegment.dropAxis() + afterSegmentComplexLength = abs( afterSegmentComplex ) + thirdAfterSegmentLength = 0.333 * afterSegmentComplexLength + if thirdAfterSegmentLength < self.minimumRadius: + return location + beforeSegment = self.oldLocation - location + beforeSegmentComplex = beforeSegment.dropAxis() + beforeSegmentComplexLength = abs( beforeSegmentComplex ) + thirdBeforeSegmentLength = 0.333 * beforeSegmentComplexLength + if thirdBeforeSegmentLength < self.minimumRadius: + return location + extruderOffReversalPoint = self.getExtruderOffReversalPoint( afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location ) + if extruderOffReversalPoint != None: + return extruderOffReversalPoint + bevelRadius = min( thirdAfterSegmentLength, self.filletRadius ) + bevelRadius = min( thirdBeforeSegmentLength, bevelRadius ) + self.shouldAddLine = False + beforePoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( beforeSegment ) / beforeSegmentComplexLength, location, beforeSegment ) + self.addLinearMovePoint( self.feedRateMinute, beforePoint ) + afterPoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( afterSegment ) / afterSegmentComplexLength, location, afterSegment ) + self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint ) + return afterPoint + + +class ArcSegmentSkein( BevelSkein ): + "A class to arc segment a skein of extrusions." + def addArc( self, afterCenterDifferenceAngle, afterPoint, beforeCenterSegment, beforePoint, center ): + "Add arc segments to the filleted skein." + absoluteDifferenceAngle = abs( afterCenterDifferenceAngle ) +# steps = int( math.ceil( absoluteDifferenceAngle * 1.5 ) ) + steps = int( math.ceil( min( absoluteDifferenceAngle * 1.5, absoluteDifferenceAngle * abs( beforeCenterSegment ) / self.curveSection ) ) ) + stepPlaneAngle = euclidean.getWiddershinsUnitPolar( afterCenterDifferenceAngle / steps ) + for step in xrange( 1, steps ): + beforeCenterSegment = euclidean.getRoundZAxisByPlaneAngle( stepPlaneAngle, beforeCenterSegment ) + arcPoint = center + beforeCenterSegment + self.addLinearMovePoint( self.getCornerFeedRate(), arcPoint ) + self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint ) + + def splitPointGetAfter( self, location, nextLocation ): + "Fillet a point into arc segments and return the end of the last segment." + if self.filletRadius < 2.0 * self.minimumRadius: + return location + afterSegment = nextLocation - location + afterSegmentComplex = afterSegment.dropAxis() + thirdAfterSegmentLength = 0.333 * abs( afterSegmentComplex ) + if thirdAfterSegmentLength < self.minimumRadius: + return location + beforeSegment = self.oldLocation - location + beforeSegmentComplex = beforeSegment.dropAxis() + thirdBeforeSegmentLength = 0.333 * abs( beforeSegmentComplex ) + if thirdBeforeSegmentLength < self.minimumRadius: + return location + extruderOffReversalPoint = self.getExtruderOffReversalPoint( afterSegment, afterSegmentComplex, beforeSegment, beforeSegmentComplex, location ) + if extruderOffReversalPoint != None: + return extruderOffReversalPoint + bevelRadius = min( thirdAfterSegmentLength, self.filletRadius ) + bevelRadius = min( thirdBeforeSegmentLength, bevelRadius ) + self.shouldAddLine = False + beforePoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( beforeSegment ) / abs( beforeSegmentComplex ), location, beforeSegment ) + self.addLinearMovePoint( self.feedRateMinute, beforePoint ) + afterPoint = euclidean.getPointPlusSegmentWithLength( bevelRadius * abs( afterSegment ) / abs( afterSegmentComplex ), location, afterSegment ) + afterPointComplex = afterPoint.dropAxis() + beforePointComplex = beforePoint.dropAxis() + locationComplex = location.dropAxis() + midpoint = 0.5 * ( afterPoint + beforePoint ) + midpointComplex = midpoint.dropAxis() + midpointMinusLocationComplex = midpointComplex - locationComplex + midpointLocationLength = abs( midpointMinusLocationComplex ) + if midpointLocationLength < 0.01 * self.filletRadius: + self.addLinearMovePoint( self.getCornerFeedRate(), afterPoint ) + return afterPoint + midpointAfterPointLength = abs( midpointComplex - afterPointComplex ) + midpointCenterLength = midpointAfterPointLength * midpointAfterPointLength / midpointLocationLength + radius = math.sqrt( midpointCenterLength * midpointCenterLength + midpointAfterPointLength * midpointAfterPointLength ) + centerComplex = midpointComplex + midpointMinusLocationComplex * midpointCenterLength / midpointLocationLength + center = Vector3( centerComplex.real, centerComplex.imag, midpoint.z ) + afterCenterComplex = afterPointComplex - centerComplex + beforeCenter = beforePoint - center + angleDifference = euclidean.getAngleDifferenceByComplex( afterCenterComplex, beforeCenter.dropAxis() ) + self.addArc( angleDifference, afterPoint, beforeCenter, beforePoint, center ) + return afterPoint + + +class ArcPointSkein( ArcSegmentSkein ): + "A class to arc point a skein of extrusions." + def addArc( self, afterCenterDifferenceAngle, afterPoint, beforeCenterSegment, beforePoint, center ): + "Add an arc point to the filleted skein." + if afterCenterDifferenceAngle == 0.0: + return + afterPointMinusBefore = afterPoint - beforePoint + centerMinusBefore = center - beforePoint + firstWord = 'G3' + if afterCenterDifferenceAngle < 0.0: + firstWord = 'G2' + centerMinusBeforeComplex = centerMinusBefore.dropAxis() + if abs( centerMinusBeforeComplex ) <= 0.0: + return + radius = abs( centerMinusBefore ) + arcDistanceZ = complex( abs( afterCenterDifferenceAngle ) * radius, afterPointMinusBefore.z ) + distance = abs( arcDistanceZ ) + if distance <= 0.0: + return + line = self.distanceFeedRate.getFirstWordMovement( firstWord, afterPointMinusBefore ) + self.getRelativeCenter( centerMinusBeforeComplex ) + cornerFeedRate = self.getCornerFeedRate() + if cornerFeedRate != None: + line += ' F' + self.distanceFeedRate.getRounded(cornerFeedRate) + self.distanceFeedRate.addLine(line) + + def getRelativeCenter( self, centerMinusBeforeComplex ): + "Get the relative center." + return ' I%s J%s' % ( self.distanceFeedRate.getRounded( centerMinusBeforeComplex.real ), self.distanceFeedRate.getRounded( centerMinusBeforeComplex.imag ) ) + + +class ArcRadiusSkein( ArcPointSkein ): + "A class to arc radius a skein of extrusions." + def getRelativeCenter( self, centerMinusBeforeComplex ): + "Get the relative center." + radius = abs( centerMinusBeforeComplex ) + return ' R' + ( self.distanceFeedRate.getRounded(radius) ) + + +class FilletRepository: + "A class to handle the fillet settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fillet.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Filleted', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fillet') + self.activateFillet = settings.BooleanSetting().getFromValue('Activate Fillet', self, False ) + self.filletProcedureChoiceLabel = settings.LabelDisplay().getFromName('Fillet Procedure Choice: ', self ) + filletLatentStringVar = settings.LatentStringVar() + self.arcPoint = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Point', self, False ) + self.arcRadius = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Radius', self, False ) + self.arcSegment = settings.Radio().getFromRadio( filletLatentStringVar, 'Arc Segment', self, False ) + self.bevel = settings.Radio().getFromRadio( filletLatentStringVar, 'Bevel', self, True ) + self.cornerFeedRateMultiplier = settings.FloatSpin().getFromValue(0.8, 'Corner Feed Rate Multiplier (ratio):', self, 1.2, 1.0) + self.filletRadiusOverEdgeWidth = settings.FloatSpin().getFromValue( 0.25, 'Fillet Radius over Perimeter Width (ratio):', self, 0.65, 0.35 ) + self.reversalSlowdownDistanceOverEdgeWidth = settings.FloatSpin().getFromValue( 0.3, 'Reversal Slowdown Distance over Perimeter Width (ratio):', self, 0.7, 0.5 ) + self.useIntermediateFeedRateInCorners = settings.BooleanSetting().getFromValue('Use Intermediate Feed Rate in Corners', self, True ) + self.executeTitle = 'Fillet' + + def execute(self): + "Fillet button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +def main(): + "Display the fillet dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/flow.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/flow.py new file mode 100644 index 0000000..c462de7 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/flow.py @@ -0,0 +1,145 @@ +""" +This page is in the table of contents. +The flow script sets the flow rate by writing the M108 gcode. + +==Operation== +The default 'Activate Flow' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Flow Rate=== +Default is 210. + +Defines the flow rate which will be written following the M108 command. The flow rate is usually a PWM setting, but could be anything, like the rpm of the tool or the duty cycle of the tool. + +==Examples== +The following examples flow the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and flow.py. + +> python flow.py +This brings up the flow dialog. + +> python flow.py Screw Holder Bottom.stl +The flow tool is parsing the file: +Screw Holder Bottom.stl +.. +The flow tool has created the file: +.. Screw Holder Bottom_flow.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', flowRepository = None ): + "Flow the file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), flowRepository ) + +def getCraftedTextFromText( gcodeText, flowRepository = None ): + "Flow a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'flow'): + return gcodeText + if flowRepository == None: + flowRepository = settings.getReadRepository( FlowRepository() ) + if not flowRepository.activateFlow.value: + return gcodeText + return FlowSkein().getCraftedGcode( gcodeText, flowRepository ) + +def getNewRepository(): + 'Get new repository.' + return FlowRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Flow a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'flow', shouldAnalyze) + + +class FlowRepository: + "A class to handle the flow settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.flow.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Flow', self, '') + self.activateFlow = settings.BooleanSetting().getFromValue('Activate Flow', self, True ) + self.flowRate = settings.FloatSpin().getFromValue( 50.0, 'Flow Rate (arbitrary units):', self, 250.0, 210.0 ) + self.executeTitle = 'Flow' + + def execute(self): + "Flow button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class FlowSkein: + "A class to flow a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.lineIndex = 0 + self.lines = None + self.oldFlowRate = None + self.oldLocation = None + + def addFlowRateLine(self): + "Add flow rate line." + flowRate = self.flowRepository.flowRate.value + if flowRate != self.oldFlowRate: + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + self.oldFlowRate = flowRate + + def getCraftedGcode( self, gcodeText, flowRepository ): + "Parse gcode text and store the flow gcode." + self.flowRepository = flowRepository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('flow') + return + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the flow skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1' or firstWord == '(': + self.addFlowRateLine() + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the flow dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/home.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/home.py new file mode 100644 index 0000000..56016be --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/home.py @@ -0,0 +1,198 @@ +""" +This page is in the table of contents. +Plugin to home the tool at beginning of each layer. + +The home manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Home + +==Operation== +The default 'Activate Home' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Name of Home File=== +Default: home.gcode + +At the beginning of a each layer, home will add the commands of a gcode script with the name of the "Name of Home File" setting, if one exists. Home does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. Home looks for those files in the alterations folder in the .skeinforge folder in the home directory. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. + +==Examples== +The following examples home the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and home.py. + +> python home.py +This brings up the home dialog. + +> python home.py Screw Holder Bottom.stl +The home tool is parsing the file: +Screw Holder Bottom.stl +.. +The home tool has created the file: +.. Screw Holder Bottom_home.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, repository = None ): + "Home a gcode linear move file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText( gcodeText, repository = None ): + "Home a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'home'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( HomeRepository() ) + if not repository.activateHome.value: + return gcodeText + return HomeSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return HomeRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Home a gcode linear move file. Chain home the gcode if it is not already homed." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'home', shouldAnalyze) + + +class HomeRepository: + "A class to handle the home settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.home.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Home', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Home') + self.activateHome = settings.BooleanSetting().getFromValue('Activate Home', self, False ) + self.nameOfHomeFile = settings.StringSetting().getFromValue('Name of Home File:', self, 'home.gcode') + self.executeTitle = 'Home' + + def execute(self): + "Home button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class HomeSkein: + "A class to home a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.highestZ = None + self.homeLines = [] + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.shouldHome = False + self.travelFeedRateMinute = 957.0 + + def addFloat( self, begin, end ): + "Add dive to the original height." + beginEndDistance = begin.distance(end) + alongWay = self.absoluteEdgeWidth / beginEndDistance + closeToEnd = euclidean.getIntermediateLocation( alongWay, end, begin ) + closeToEnd.z = self.highestZ + self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.travelFeedRateMinute, closeToEnd.dropAxis(), closeToEnd.z ) ) + + def addHomeTravel( self, splitLine ): + "Add the home travel gcode." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.highestZ = max( self.highestZ, location.z ) + if not self.shouldHome: + return + self.shouldHome = False + if self.oldLocation == None: + return + if self.extruderActive: + self.distanceFeedRate.addLine('M103') + self.addHopUp( self.oldLocation ) + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.homeLines) + self.addHopUp( self.oldLocation ) + self.addFloat( self.oldLocation, location ) + if self.extruderActive: + self.distanceFeedRate.addLine('M101') + + def addHopUp(self, location): + "Add hop to highest point." + locationUp = Vector3( location.x, location.y, self.highestZ ) + self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.travelFeedRateMinute, locationUp.dropAxis(), locationUp.z ) ) + + def getCraftedGcode( self, gcodeText, repository ): + "Parse gcode text and store the home gcode." + self.repository = repository + self.homeLines = settings.getAlterationFileLines(repository.nameOfHomeFile.value) + if len(self.homeLines) < 1: + return gcodeText + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization( repository ) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization( self, repository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('home') + return + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.addHomeTravel(splitLine) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + elif firstWord == '(': + self.layerCount.printProgressIncrement('home') + if len(self.homeLines) > 0: + self.shouldHome = True + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the home dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/hop.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/hop.py new file mode 100644 index 0000000..9b4ac36 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/hop.py @@ -0,0 +1,223 @@ +""" +This page is in the table of contents. +Hop is a script to raise the extruder when it is not extruding. + +Note: + +Note: In some cases where you have thin overhang this plugin can help solve the problem object being knocked off by the head + +The hop manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Hop + +==Operation== +The default 'Activate Hop' checkbox is off. + +It is off because Vik and Nophead found better results without hopping. Numerous users reported better output without this plugin hence it is off by default. + +When activated the extruder will hop when traveling. When it is off, nothing will be done. + +==Settings== +===Hop Over Layer Thickness=== +Default is one. + +Defines the ratio of the hop height over the layer height, this is the most important hop setting. + +===Minimum Hop Angle=== +Default is 20 degrees. + +Defines the minimum angle that the path of the extruder will be raised. An angle of ninety means that the extruder will go straight up as soon as it is not extruding and a low angle means the extruder path will gradually rise to the hop height. + +==Examples== +The following examples hop the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and hop.py. + +> python hop.py +This brings up the hop dialog. + +> python hop.py Screw Holder Bottom.stl +The hop tool is parsing the file: +Screw Holder Bottom.stl +.. +The hop tool has created the file: +.. Screw Holder Bottom_hop.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, hopRepository = None ): + "Hop a gcode linear move text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), hopRepository ) + +def getCraftedTextFromText( gcodeText, hopRepository = None ): + "Hop a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'hop'): + return gcodeText + if hopRepository == None: + hopRepository = settings.getReadRepository( HopRepository() ) + if not hopRepository.activateHop.value: + return gcodeText + return HopSkein().getCraftedGcode( gcodeText, hopRepository ) + +def getNewRepository(): + 'Get new repository.' + return HopRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Hop a gcode linear move file. Chain hop the gcode if it is not already hopped." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'hop', shouldAnalyze) + + +class HopRepository: + "A class to handle the hop settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.hop.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Hop', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Hop') + self.activateHop = settings.BooleanSetting().getFromValue('Activate Hop', self, False ) + self.hopOverLayerThickness = settings.FloatSpin().getFromValue( 0.5, 'Hop Over Layer Thickness (ratio):', self, 1.5, 1.0 ) + self.minimumHopAngle = settings.FloatSpin().getFromValue( 20.0, 'Minimum Hop Angle (degrees):', self, 60.0, 30.0 ) + self.executeTitle = 'Hop' + + def execute(self): + "Hop button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class HopSkein: + "A class to hop a skein of extrusions." + def __init__(self): + 'Initialize' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.feedRateMinute = 961.0 + self.hopHeight = 0.4 + self.hopDistance = self.hopHeight + self.justDeactivated = False + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + + def getCraftedGcode( self, gcodeText, hopRepository ): + "Parse gcode text and store the hop gcode." + self.lines = archive.getTextLines(gcodeText) + self.minimumSlope = math.tan( math.radians( hopRepository.minimumHopAngle.value ) ) + self.parseInitialization( hopRepository ) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getHopLine(self, line): + "Get hopped gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + if self.extruderActive: + return line + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + highestZ = location.z + if self.oldLocation != None: + highestZ = max( highestZ, self.oldLocation.z ) + highestZHop = highestZ + self.hopHeight + locationComplex = location.dropAxis() + if self.justDeactivated: + oldLocationComplex = self.oldLocation.dropAxis() + distance = abs( locationComplex - oldLocationComplex ) + if distance < self.minimumDistance: + if self.isNextTravel() or distance == 0.0: + return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop ) + alongRatio = min( 0.41666666, self.hopDistance / distance ) + oneMinusAlong = 1.0 - alongRatio + closeLocation = oldLocationComplex * oneMinusAlong + locationComplex * alongRatio + self.distanceFeedRate.addLine( self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop ) ) + if self.isNextTravel(): + return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop ) + farLocation = oldLocationComplex * alongRatio + locationComplex * oneMinusAlong + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.feedRateMinute, farLocation, highestZHop ) + return line + if self.isNextTravel(): + return self.distanceFeedRate.getLineWithZ( line, splitLine, highestZHop ) + return line + + def isNextTravel(self): + "Determine if there is another linear travel before the thread ends." + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + return True + if firstWord == 'M101': + return False + return False + + def parseInitialization( self, hopRepository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + layerHeight = float(splitLine[1]) + self.hopHeight = hopRepository.hopOverLayerThickness.value * layerHeight + self.hopDistance = self.hopHeight / self.minimumSlope + self.minimumDistance = 0.5 * layerHeight + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('hop') + return + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if self.distanceFeedRate.getIsAlteration(line): + return + if firstWord == 'G1': + line = self.getHopLine(line) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.justDeactivated = False + elif firstWord == '(': + self.layerCount.printProgressIncrement('hop') + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.justDeactivated = True + self.distanceFeedRate.addLineCheckAlteration(line) + + +def main(): + "Display the hop dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/inset.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/inset.py new file mode 100644 index 0000000..74cc85c --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/inset.py @@ -0,0 +1,473 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Inset will inset the outside outlines by half the edge width, and outset the inside outlines by the same amount. + +The inset manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Inset + +==Settings== +===Add Custom Code for Temperature Reading=== +Default is on. + +When selected, the M105 custom code for temperature reading will be added at the beginning of the file. + +===Infill in Direction of Bridge=== +Default is on. + +When selected, the infill will be in the direction of any bridge across a gap, so that the fill will be able to span a bridge easier. + +===Loop Order Choice=== +Default loop order choice is 'Ascending Area'. + +When overlap is to be removed, for each loop, the overlap is checked against the list of loops already extruded. If the latest loop overlaps an already extruded loop, the overlap is removed from the latest loop. The loops are ordered according to their areas. + +====Ascending Area==== +When selected, the loops will be ordered in ascending area. With thin walled parts, if overlap is being removed the outside of the container will not be extruded. Holes will be the correct size. + +====Descending Area==== +When selected, the loops will be ordered in descending area. With thin walled parts, if overlap is being removed the inside of the container will not be extruded. Holes will be missing the interior wall so they will be slightly wider than model size. + +===Overlap Removal Width over Perimeter Width=== +Default is 0.6. + +Defines the ratio of the overlap removal width over the edge width. Any part of the extrusion that comes within the overlap removal width of another is removed. This is to prevent the extruder from depositing two extrusions right beside each other. If the 'Overlap Removal Width over Perimeter Width' is less than 0.2, the overlap will not be removed. + +===Turn Extruder Heater Off at Shut Down=== +Default is on. + +When selected, the M104 S0 gcode line will be added to the end of the file to turn the extruder heater off by setting the extruder heater temperature to 0. + +==Examples== +The following examples inset the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and inset.py. + +> python inset.py +This brings up the inset dialog. + +> python inset.py Screw Holder Bottom.stl +The inset tool is parsing the file: +Screw Holder Bottom.stl +.. +The inset tool has created the file: +.. Screw Holder Bottom_inset.gcode + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cmath +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addAlreadyFilledArounds( alreadyFilledArounds, loop, radius ): + "Add already filled loops around loop to alreadyFilledArounds." + radius = abs(radius) + alreadyFilledLoop = [] + slightlyGreaterThanRadius = intercircle.globalIntercircleMultiplier * radius + muchGreaterThanRadius = 2.5 * radius + centers = intercircle.getCentersFromLoop( loop, slightlyGreaterThanRadius ) + for center in centers: + alreadyFilledInset = intercircle.getSimplifiedInsetFromClockwiseLoop( center, radius ) + if intercircle.isLargeSameDirection( alreadyFilledInset, center, radius ): + alreadyFilledLoop.append( alreadyFilledInset ) + if len( alreadyFilledLoop ) > 0: + alreadyFilledArounds.append( alreadyFilledLoop ) + +def addSegmentOutline( isThick, outlines, pointBegin, pointEnd, width ): + "Add a diamond or hexagonal outline for a line segment." + width = abs( width ) + exclusionWidth = 0.6 * width + slope = 0.2 + if isThick: + slope = 3.0 + exclusionWidth = 0.8 * width + segment = pointEnd - pointBegin + segmentLength = abs(segment) + if segmentLength == 0.0: + return + normalizedSegment = segment / segmentLength + outline = [] + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = segmentYMirror * pointBegin + pointEndRotated = segmentYMirror * pointEnd + along = 0.05 + alongLength = along * segmentLength + if alongLength > 0.1 * exclusionWidth: + along *= 0.1 * exclusionWidth / alongLength + alongEnd = 1.0 - along + remainingToHalf = 0.5 - along + alongToWidth = exclusionWidth / slope / segmentLength + pointBeginIntermediate = euclidean.getIntermediateLocation( along, pointBeginRotated, pointEndRotated ) + pointEndIntermediate = euclidean.getIntermediateLocation( alongEnd, pointBeginRotated, pointEndRotated ) + outline.append( pointBeginIntermediate ) + verticalWidth = complex( 0.0, exclusionWidth ) + if alongToWidth > 0.9 * remainingToHalf: + verticalWidth = complex( 0.0, slope * remainingToHalf * segmentLength ) + middle = ( pointBeginIntermediate + pointEndIntermediate ) * 0.5 + middleDown = middle - verticalWidth + middleUp = middle + verticalWidth + outline.append( middleUp ) + outline.append( pointEndIntermediate ) + outline.append( middleDown ) + else: + alongOutsideBegin = along + alongToWidth + alongOutsideEnd = alongEnd - alongToWidth + outsideBeginCenter = euclidean.getIntermediateLocation( alongOutsideBegin, pointBeginRotated, pointEndRotated ) + outsideBeginCenterDown = outsideBeginCenter - verticalWidth + outsideBeginCenterUp = outsideBeginCenter + verticalWidth + outsideEndCenter = euclidean.getIntermediateLocation( alongOutsideEnd, pointBeginRotated, pointEndRotated ) + outsideEndCenterDown = outsideEndCenter - verticalWidth + outsideEndCenterUp = outsideEndCenter + verticalWidth + outline.append( outsideBeginCenterUp ) + outline.append( outsideEndCenterUp ) + outline.append( pointEndIntermediate ) + outline.append( outsideEndCenterDown ) + outline.append( outsideBeginCenterDown ) + outlines.append( euclidean.getRotatedComplexes( normalizedSegment, outline ) ) + +def getBridgeDirection(belowLoops, layerLoops, radius): + 'Get span direction for the majority of the overhanging extrusion edge, if any.' + if len(belowLoops) < 1: + return None + belowOutsetLoops = intercircle.getInsetLoopsFromLoops(belowLoops, -radius) + bridgeRotation = complex() + for loop in layerLoops: + for pointIndex, point in enumerate(loop): + previousIndex = (pointIndex + len(loop) - 1) % len(loop) + bridgeRotation += getOverhangDirection(belowOutsetLoops, loop[previousIndex], point) + if abs(bridgeRotation) < 0.75 * radius: + return None + else: + return cmath.sqrt(bridgeRotation / abs(bridgeRotation)) + +def getCraftedText( fileName, text='', repository=None): + "Inset the preface file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Inset the preface gcode text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'inset'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( InsetRepository() ) + return InsetSkein().getCraftedGcode(gcodeText, repository) + +def getDoubledRoundZ( overhangingSegment, segmentRoundZ ): + 'Get doubled plane angle around z of the overhanging segment.' + endpoint = overhangingSegment[0] + roundZ = endpoint.point - endpoint.otherEndpoint.point + roundZ *= segmentRoundZ + if abs( roundZ ) == 0.0: + return complex() + if roundZ.real < 0.0: + roundZ *= - 1.0 + roundZLength = abs( roundZ ) + return roundZ * roundZ / roundZLength + +def getInteriorSegments(loops, segments): + 'Get segments inside the loops.' + interiorSegments = [] + for segment in segments: + center = 0.5 * (segment[0].point + segment[1].point) + if euclidean.getIsInFilledRegion(loops, center): + interiorSegments.append(segment) + return interiorSegments + +def getIsIntersectingWithinList(loop, loopList): + "Determine if the loop is intersecting or is within the loop list." + leftPoint = euclidean.getLeftPoint(loop) + for otherLoop in loopList: + if euclidean.getNumberOfIntersectionsToLeft(otherLoop, leftPoint) % 2 == 1: + return True + return euclidean.isLoopIntersectingLoops(loop, loopList) + +def getNewRepository(): + 'Get new repository.' + return InsetRepository() + +def getOverhangDirection( belowOutsetLoops, segmentBegin, segmentEnd ): + 'Add to span direction from the endpoint segments which overhang the layer below.' + segment = segmentEnd - segmentBegin + normalizedSegment = euclidean.getNormalized( complex( segment.real, segment.imag ) ) + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + segmentBegin = segmentYMirror * segmentBegin + segmentEnd = segmentYMirror * segmentEnd + solidXIntersectionList = [] + y = segmentBegin.imag + solidXIntersectionList.append( euclidean.XIntersectionIndex( - 1.0, segmentBegin.real ) ) + solidXIntersectionList.append( euclidean.XIntersectionIndex( - 1.0, segmentEnd.real ) ) + for belowLoopIndex in xrange( len( belowOutsetLoops ) ): + belowLoop = belowOutsetLoops[ belowLoopIndex ] + rotatedOutset = euclidean.getRotatedComplexes( segmentYMirror, belowLoop ) + euclidean.addXIntersectionIndexesFromLoopY( rotatedOutset, belowLoopIndex, solidXIntersectionList, y ) + overhangingSegments = euclidean.getSegmentsFromXIntersectionIndexes( solidXIntersectionList, y ) + overhangDirection = complex() + for overhangingSegment in overhangingSegments: + overhangDirection += getDoubledRoundZ( overhangingSegment, normalizedSegment ) + return overhangDirection + +def getSegmentsFromLoopListsPoints( loopLists, pointBegin, pointEnd ): + "Get endpoint segments from the beginning and end of a line segment." + normalizedSegment = pointEnd - pointBegin + normalizedSegmentLength = abs( normalizedSegment ) + if normalizedSegmentLength == 0.0: + return [] + normalizedSegment /= normalizedSegmentLength + segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) + pointBeginRotated = segmentYMirror * pointBegin + pointEndRotated = segmentYMirror * pointEnd + rotatedLoopLists = [] + for loopList in loopLists: + rotatedLoopLists.append(euclidean.getRotatedComplexLists(segmentYMirror, loopList)) + xIntersectionIndexList = [] + xIntersectionIndexList.append( euclidean.XIntersectionIndex( - 1, pointBeginRotated.real ) ) + xIntersectionIndexList.append( euclidean.XIntersectionIndex( - 1, pointEndRotated.real ) ) + euclidean.addXIntersectionIndexesFromLoopListsY( rotatedLoopLists, xIntersectionIndexList, pointBeginRotated.imag ) + segments = euclidean.getSegmentsFromXIntersectionIndexes( xIntersectionIndexList, pointBeginRotated.imag ) + for segment in segments: + for endpoint in segment: + endpoint.point *= normalizedSegment + return segments + +def isCloseToLast( paths, point, radius ): + "Determine if the point is close to the last point of the last path." + if len(paths) < 1: + return False + lastPath = paths[-1] + return abs( lastPath[-1] - point ) < radius + +def isIntersectingItself( loop, width ): + "Determine if the loop is intersecting itself." + outlines = [] + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + if euclidean.isLineIntersectingLoops( outlines, pointBegin, pointEnd ): + return True + addSegmentOutline( False, outlines, pointBegin, pointEnd, width ) + return False + +def isIntersectingWithinLists( loop, loopLists ): + "Determine if the loop is intersecting or is within the loop lists." + for loopList in loopLists: + if getIsIntersectingWithinList( loop, loopList ): + return True + return False + +def writeOutput(fileName, shouldAnalyze=True): + "Inset the carving of a gcode file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'inset', shouldAnalyze) + + +class InsetRepository: + "A class to handle the inset settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.inset.html', self) + self.baseNameSynonymDictionary = { + 'Infill in Direction of Bridge' : 'carve.csv', + 'Infill Width over Thickness (ratio):' : 'fill.csv'} + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Inset', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Inset') + self.addCustomCodeForTemperatureReading = settings.BooleanSetting().getFromValue('Add Custom Code for Temperature Reading', self, True) + self.infillInDirectionOfBridge = settings.BooleanSetting().getFromValue('Infill in Direction of Bridge', self, True) + self.infillWidthOverThickness = settings.FloatSpin().getFromValue(1.3, 'Infill Width over Thickness (ratio):', self, 1.7, 1.5) + self.loopOrderChoice = settings.MenuButtonDisplay().getFromName('Loop Order Choice:', self ) + self.loopOrderAscendingArea = settings.MenuRadio().getFromMenuButtonDisplay(self.loopOrderChoice, 'Ascending Area', self, True) + self.loopOrderDescendingArea = settings.MenuRadio().getFromMenuButtonDisplay(self.loopOrderChoice, 'Descending Area', self, False) + self.overlapRemovalWidthOverEdgeWidth = settings.FloatSpin().getFromValue(0.3, 'Overlap Removal Width over Perimeter Width (ratio):', self, 0.9, 0.6) + self.turnExtruderHeaterOffAtShutDown = settings.BooleanSetting().getFromValue('Turn Extruder Heater Off at Shut Down', self, True) + self.volumeFraction = settings.FloatSpin().getFromValue(0.7, 'Volume Fraction (ratio):', self, 0.9, 0.82) + self.executeTitle = 'Inset' + + def execute(self): + "Inset button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class InsetSkein: + "A class to inset a skein of extrusions." + def __init__(self): + 'Initialize.' + self.belowLoops = [] + self.boundary = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.loopLayer = None + + def addGcodeFromPerimeterPaths(self, isIntersectingSelf, loop, loopLayer, loopLists, radius): + "Add the edge paths to the output." + segments = [] + outlines = [] + thickOutlines = [] + allLoopLists = loopLists[:] + [thickOutlines] + aroundLists = loopLists + for pointIndex in xrange(len(loop)): + pointBegin = loop[pointIndex] + pointEnd = loop[(pointIndex + 1) % len(loop)] + if isIntersectingSelf: + if euclidean.isLineIntersectingLoops(outlines, pointBegin, pointEnd): + segments += getSegmentsFromLoopListsPoints(allLoopLists, pointBegin, pointEnd) + else: + segments += getSegmentsFromLoopListsPoints(loopLists, pointBegin, pointEnd) + addSegmentOutline(False, outlines, pointBegin, pointEnd, self.overlapRemovalWidth) + addSegmentOutline(True, thickOutlines, pointBegin, pointEnd, self.overlapRemovalWidth) + else: + segments += getSegmentsFromLoopListsPoints(loopLists, pointBegin, pointEnd) + edgePaths = [] + path = [] + muchSmallerThanRadius = 0.1 * radius + segments = getInteriorSegments(loopLayer.loops, segments) + for segment in segments: + pointBegin = segment[0].point + if not isCloseToLast(edgePaths, pointBegin, muchSmallerThanRadius): + path = [pointBegin] + edgePaths.append(path) + path.append(segment[1].point) + if len(edgePaths) > 1: + firstPath = edgePaths[0] + lastPath = edgePaths[-1] + if abs(lastPath[-1] - firstPath[0]) < 0.1 * muchSmallerThanRadius: + connectedBeginning = lastPath[: -1] + firstPath + edgePaths[0] = connectedBeginning + edgePaths.remove(lastPath) + muchGreaterThanRadius = 6.0 * radius + for edgePath in edgePaths: + if euclidean.getPathLength(edgePath) > muchGreaterThanRadius: + self.distanceFeedRate.addGcodeFromThreadZ(edgePath, loopLayer.z) + + def addGcodeFromRemainingLoop(self, loop, loopLayer, loopLists, radius): + "Add the remainder of the loop which does not overlap the alreadyFilledArounds loops." + centerOutset = intercircle.getLargestCenterOutsetLoopFromLoopRegardless(loop, radius) + euclidean.addNestedRingBeginning(self.distanceFeedRate, centerOutset.outset, loopLayer.z) + self.addGcodePerimeterBlockFromRemainingLoop(centerOutset.center, loopLayer, loopLists, radius) + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLine('()') + + def addGcodePerimeterBlockFromRemainingLoop(self, loop, loopLayer, loopLists, radius): + "Add the perimter block remainder of the loop which does not overlap the alreadyFilledArounds loops." + if self.repository.overlapRemovalWidthOverEdgeWidth.value < 0.2: + self.distanceFeedRate.addPerimeterBlock(loop, loopLayer.z) + return + isIntersectingSelf = isIntersectingItself(loop, self.overlapRemovalWidth) + if isIntersectingWithinLists(loop, loopLists) or isIntersectingSelf: + self.addGcodeFromPerimeterPaths(isIntersectingSelf, loop, loopLayer, loopLists, radius) + else: + self.distanceFeedRate.addPerimeterBlock(loop, loopLayer.z) + addAlreadyFilledArounds(loopLists, loop, self.overlapRemovalWidth) + + def addInitializationToOutput(self): + "Add initialization gcode to the output." + if self.repository.addCustomCodeForTemperatureReading.value: + self.distanceFeedRate.addLine('M105') # Custom code for temperature reading. + + def addInset(self, loopLayer): + "Add inset to the layer." + alreadyFilledArounds = [] + extrudateLoops = intercircle.getInsetLoopsFromLoops(loopLayer.loops, self.halfEdgeWidth) + if self.repository.infillInDirectionOfBridge.value: + bridgeRotation = getBridgeDirection(self.belowLoops, extrudateLoops, self.halfEdgeWidth) + if bridgeRotation != None: + self.distanceFeedRate.addTagBracketedLine('bridgeRotation', bridgeRotation) + self.belowLoops = loopLayer.loops + triangle_mesh.sortLoopsInOrderOfArea(not self.repository.loopOrderAscendingArea.value, extrudateLoops) + for extrudateLoop in extrudateLoops: + self.addGcodeFromRemainingLoop(extrudateLoop, loopLayer, alreadyFilledArounds, self.halfEdgeWidth) + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the bevel gcode." + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + self.addInitializationToOutput() + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('inset') + return + elif firstWord == '(': + layerHeight = float(splitLine[1]) + self.infillWidth = self.repository.infillWidthOverThickness.value * layerHeight + self.distanceFeedRate.addTagRoundedLine('infillWidth', self.infillWidth) + self.distanceFeedRate.addTagRoundedLine('volumeFraction', self.repository.volumeFraction.value) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.halfEdgeWidth = 0.5 * self.edgeWidth + self.overlapRemovalWidth = self.edgeWidth * self.repository.overlapRemovalWidthOverEdgeWidth.value + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the inset skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.boundary.append(location.dropAxis()) + elif firstWord == '()': + self.distanceFeedRate.addLine(line) + if self.repository.turnExtruderHeaterOffAtShutDown.value: + self.distanceFeedRate.addLine('M104 S0') # Turn extruder heater off. + return + elif firstWord == '(': + self.layerCount.printProgressIncrement('inset') + self.loopLayer = euclidean.LoopLayer(float(splitLine[1])) + self.distanceFeedRate.addLine(line) + elif firstWord == '()': + self.addInset(self.loopLayer) + self.loopLayer = None + elif firstWord == '()': + self.boundary = [] + self.loopLayer.loops.append(self.boundary) + if self.loopLayer == None: + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the inset dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/jitter.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/jitter.py new file mode 100644 index 0000000..aef5bca --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/jitter.py @@ -0,0 +1,256 @@ +""" +This page is in the table of contents. +This craft tool jitters the loop end position to a different place on each layer to prevent a ridge from being created on the side of the object. + +The jitter manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter + +==Operation== +The default 'Activate Jitter' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Jitter Over Perimeter Width=== +Default: 2 + +Defines the amount the loop ends will be jittered over the edge width. A high value means the loops will start all over the place and a low value means loops will start at roughly the same place on each layer. + +For example if you turn jitter off and print a cube every outside shell on the cube will start from exactly the same point so you will have a visible "mark/line/seam" on the side of the cube. Using the jitter tool you move that start point around hence you avoid that visible seam. + + +==Examples== +The following examples jitter the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and jitter.py. + +> python jitter.py +This brings up the jitter dialog. + +> python jitter.py Screw Holder Bottom.stl +The jitter tool is parsing the file: +Screw Holder Bottom.stl +.. +The jitter tool has created the file: +.. Screw Holder Bottom_jitter.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, jitterRepository = None ): + 'Jitter a gcode linear move text.' + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), jitterRepository ) + +def getCraftedTextFromText( gcodeText, jitterRepository = None ): + 'Jitter a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'jitter'): + return gcodeText + if jitterRepository == None: + jitterRepository = settings.getReadRepository( JitterRepository() ) + if not jitterRepository.activateJitter.value: + return gcodeText + return JitterSkein().getCraftedGcode( jitterRepository, gcodeText ) + +def getJitteredLoop( jitterDistance, jitterLoop ): + 'Get a jittered loop path.' + loopLength = euclidean.getLoopLength( jitterLoop ) + lastLength = 0.0 + pointIndex = 0 + totalLength = 0.0 + jitterPosition = ( jitterDistance + 256.0 * loopLength ) % loopLength + while totalLength < jitterPosition and pointIndex < len( jitterLoop ): + firstPoint = jitterLoop[pointIndex] + secondPoint = jitterLoop[ (pointIndex + 1) % len( jitterLoop ) ] + pointIndex += 1 + lastLength = totalLength + totalLength += abs(firstPoint - secondPoint) + remainingLength = jitterPosition - lastLength + pointIndex = pointIndex % len( jitterLoop ) + ultimateJitteredPoint = jitterLoop[pointIndex] + penultimateJitteredPointIndex = ( pointIndex + len( jitterLoop ) - 1 ) % len( jitterLoop ) + penultimateJitteredPoint = jitterLoop[ penultimateJitteredPointIndex ] + segment = ultimateJitteredPoint - penultimateJitteredPoint + segmentLength = abs(segment) + originalOffsetLoop = euclidean.getAroundLoop( pointIndex, pointIndex, jitterLoop ) + if segmentLength <= 0.0: + return originalOffsetLoop + newUltimatePoint = penultimateJitteredPoint + segment * remainingLength / segmentLength + return [newUltimatePoint] + originalOffsetLoop + +def getNewRepository(): + 'Get new repository.' + return JitterRepository() + +def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ): + 'Determine if the loop number is equal.' + if betweenXIndex >= len( betweenX ): + return False + return betweenX[ betweenXIndex ].index == loopNumber + +def writeOutput(fileName, shouldAnalyze=True): + 'Jitter a gcode linear move file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'jitter', shouldAnalyze) + + +class JitterRepository: + 'A class to handle the jitter settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.jitter.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Jitter', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter') + self.activateJitter = settings.BooleanSetting().getFromValue('Activate Jitter', self, False) + self.jitterOverEdgeWidth = settings.FloatSpin().getFromValue(1.0, 'Jitter Over Perimeter Width (ratio):', self, 3.0, 2.0) + self.executeTitle = 'Jitter' + + def execute(self): + 'Jitter button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class JitterSkein: + 'A class to jitter a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = None + self.isLoopPerimeter = False + self.layerCount = settings.LayerCount() + self.layerGolden = 0.0 + self.lineIndex = 0 + self.lines = None + self.loopPath = None + self.oldLocation = None + self.operatingFeedRatePerMinute = None + self.travelFeedRateMinute = None + + def addGcodeFromThreadZ( self, thread, z ): + 'Add a gcode thread to the output.' + if len(thread) > 0: + self.addGcodeMovementZ( self.travelFeedRateMinute, thread[0], z ) + else: + print('zero length vertex positions array which was skipped over, this should never happen.') + if len(thread) < 2: + return + self.distanceFeedRate.addLine('M101') + self.addGcodePathZ( self.feedRateMinute, thread[1 :], z ) + + def addGcodeMovementZ(self, feedRateMinute, point, z): + 'Add a movement to the output.' + if feedRateMinute == None: + feedRateMinute = self.operatingFeedRatePerMinute + self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, point, z) + + def addGcodePathZ( self, feedRateMinute, path, z ): + 'Add a gcode path, without modifying the extruder, to the output.' + for point in path: + self.addGcodeMovementZ(feedRateMinute, point, z) + + def addTailoredLoopPath(self): + 'Add a clipped and jittered loop path.' + loop = getJitteredLoop(self.layerJitter, self.loopPath.path[: -1]) + loop = euclidean.getAwayPoints(loop, 0.2 * self.edgeWidth) + self.addGcodeFromThreadZ(loop + [loop[0]], self.loopPath.z) + self.loopPath = None + + def getCraftedGcode(self, jitterRepository, gcodeText): + 'Parse gcode text and store the jitter gcode.' + if jitterRepository.jitterOverEdgeWidth.value == 0.0: + print('Warning, Jitter Over Perimeter Width is zero so thing will be done.') + return gcodeText + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization(jitterRepository) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine(self.lines[self.lineIndex]) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization( self, jitterRepository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('jitter') + return + elif firstWord == '(': + self.operatingFeedRatePerMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.jitter = jitterRepository.jitterOverEdgeWidth.value * self.edgeWidth + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line, jitter it and add it to the jitter skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.setFeedRateLocationLoopPath(line, splitLine) + if self.loopPath != None: + self.loopPath.path.append(self.oldLocation.dropAxis()) + return + elif firstWord == 'M101': + if self.loopPath != None: + return + elif firstWord == 'M103': + self.isLoopPerimeter = False + if self.loopPath != None: + self.addTailoredLoopPath() + elif firstWord == '(': + self.layerCount.printProgressIncrement('jitter') + self.layerGolden = math.fmod(self.layerGolden + 0.61803398874989479, 1.0) + self.layerJitter = self.jitter * self.layerGolden - 0.5 + elif firstWord == '(' or firstWord == '(': + self.isLoopPerimeter = True + self.distanceFeedRate.addLine(line) + + def setFeedRateLocationLoopPath(self, line, splitLine): + 'Set the feedRateMinute, oldLocation and loopPath.' + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if not self.isLoopPerimeter or self.loopPath != None: + return + for afterIndex in xrange(self.lineIndex + 1, len(self.lines)): + line = self.lines[afterIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1' or firstWord == 'M103': + return + elif firstWord == 'M101': + self.loopPath = euclidean.PathZ(self.oldLocation.z) + return + + +def main(): + 'Display the jitter dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lash.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lash.py new file mode 100644 index 0000000..9843d43 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lash.py @@ -0,0 +1,171 @@ +""" +This page is in the table of contents. +Lash is a script to partially compensate for the backlash of the tool head. + +The lash manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Lash + +The lash tool is ported from Erik de Bruijn's 3D-to-5D-Gcode php GPL'd script at: +http://objects.reprap.org/wiki/3D-to-5D-Gcode.php + +The default values are from the settings in Erik's 3D-to-5D-Gcode, I believe the settings are used on his Darwin reprap. + +==Operation== +The default 'Activate Lash' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===X Backlash=== +Default is 0.2 millimeters. + +Defines the distance the tool head will be lashed in the X direction. + +===Y Backlash=== +Default is 0.2 millimeters. + +Defines the distance the tool head will be lashed in the Y direction. + +==Examples== +The following examples lash the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and lash.py. + +> python lash.py +This brings up the lash dialog. + +> python lash.py Screw Holder Bottom.stl +The lash tool is parsing the file: +Screw Holder Bottom.stl +.. +The lash tool has created the file: +.. Screw Holder Bottom_lash.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, lashRepository = None ): + "Get a lashed gcode linear move text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), lashRepository ) + +def getCraftedTextFromText( gcodeText, lashRepository = None ): + "Get a lashed gcode linear move text from text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'lash'): + return gcodeText + if lashRepository == None: + lashRepository = settings.getReadRepository( LashRepository() ) + if not lashRepository.activateLash.value: + return gcodeText + return LashSkein().getCraftedGcode( gcodeText, lashRepository ) + +def getNewRepository(): + 'Get new repository.' + return LashRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Lash a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'lash', shouldAnalyze) + + +class LashRepository: + "A class to handle the lash settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.lash.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Lash', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Lash') + self.activateLash = settings.BooleanSetting().getFromValue('Activate Lash', self, False ) + self.xBacklash = settings.FloatSpin().getFromValue( 0.1, 'X Backlash (mm):', self, 0.5, 0.2 ) + self.yBacklash = settings.FloatSpin().getFromValue( 0.1, 'Y Backlash (mm):', self, 0.5, 0.3 ) + self.executeTitle = 'Lash' + + def execute(self): + "Lash button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class LashSkein: + "A class to lash a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 958.0 + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + + def getCraftedGcode( self, gcodeText, lashRepository ): + "Parse gcode text and store the lash gcode." + self.lines = archive.getTextLines(gcodeText) + self.lashRepository = lashRepository + self.xBacklash = lashRepository.xBacklash.value + self.yBacklash = lashRepository.yBacklash.value + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLash(line) + return self.distanceFeedRate.output.getvalue() + + def getLashedLine( self, line, location, splitLine ): + "Get lashed gcode line." + if self.oldLocation == None: + return line + if location.x > self.oldLocation.x: + line = self.distanceFeedRate.getLineWithX( line, splitLine, location.x + self.xBacklash ) + else: + line = self.distanceFeedRate.getLineWithX( line, splitLine, location.x - self.xBacklash ) + if location.y > self.oldLocation.y: + line = self.distanceFeedRate.getLineWithY( line, splitLine, location.y + self.yBacklash ) + else: + line = self.distanceFeedRate.getLineWithY( line, splitLine, location.y - self.yBacklash ) + return line + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('lash') + return + self.distanceFeedRate.addLine(line) + + def parseLash(self, line): + "Parse a gcode line and add it to the lash skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + line = self.getLashedLine( line, location, splitLine ) + self.oldLocation = location + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the lash dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lift.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lift.py new file mode 100644 index 0000000..7e19329 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/lift.py @@ -0,0 +1,195 @@ +""" +This page is in the table of contents. +Lift will change the altitude of the cutting tool when it is on so that it will cut through the slab at the correct altitude. It will also lift the gcode when the tool is off so that the cutting tool will clear the top of the slab. + +==Operation== +The default 'Activate Lift' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Cutting Lift over Layer Step=== +Default is minus 0.5, because the end mill is the more common tool. + +Defines the ratio of the amount the cutting tool will be lifted over the layer step. If whittle is off the layer step will be the layer height, if it is on, it will be the layer step from the whittle gcode. If the cutting tool is like an end mill, where the cutting happens until the end of the tool, then the 'Cutting Lift over Layer Step' should be minus 0.5, so that the end mill cuts to the bottom of the slab. If the cutting tool is like a laser, where the cutting happens around the focal point. the 'Cutting Lift over Layer Step' should be zero, so that the cutting action will be focused in the middle of the slab. + +===Clearance above Top=== +Default is 5 millimeters. + +Defines the distance above the top of the slab the cutting tool will be lifted when will tool is off so that the cutting tool will clear the top of the slab. + +==Examples== +The following examples lift the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and lift.py. + +> python lift.py +This brings up the lift dialog. + +> python lift.py Screw Holder Bottom.stl +The lift tool is parsing the file: +Screw Holder Bottom.stl +.. +The lift tool has created the file: +.. Screw Holder Bottom_lift.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', liftRepository = None ): + "Lift the preface file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), liftRepository ) + +def getCraftedTextFromText( gcodeText, liftRepository = None ): + "Lift the preface gcode text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'lift'): + return gcodeText + if liftRepository == None: + liftRepository = settings.getReadRepository( LiftRepository() ) + if not liftRepository.activateLift.value: + return gcodeText + return LiftSkein().getCraftedGcode( liftRepository, gcodeText ) + +def getNewRepository(): + 'Get new repository.' + return LiftRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Lift the carving of a gcode file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'lift', shouldAnalyze) + + +class LiftRepository: + "A class to handle the lift settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.lift.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Lifted', self, '') + self.activateLift = settings.BooleanSetting().getFromValue('Activate Lift', self, True ) + self.cuttingLiftOverLayerStep = settings.FloatSpin().getFromValue( - 1.0, 'Cutting Lift over Layer Step (ratio):', self, 1.0, - 0.5 ) + self.clearanceAboveTop = settings.FloatSpin().getFromValue( 0.0, 'Clearance above Top (mm):', self, 10.0, 5.0 ) + self.executeTitle = 'Lift' + + def execute(self): + "Lift button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class LiftSkein: + "A class to lift a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.layerStep = None + self.layerHeight = 0.3333333333 + self.lineIndex = 0 + self.maximumZ = - 912345678.0 + self.oldLocation = None + self.previousActiveMovementLine = None + self.previousInactiveMovementLine = None + + def addPreviousInactiveMovementLineIfNecessary(self): + "Add the previous inactive movement line if necessary." + if self.previousInactiveMovementLine != None: + self.distanceFeedRate.addLine( self.previousInactiveMovementLine ) + self.previousInactiveMovementLine = None + + def getCraftedGcode( self, liftRepository, gcodeText ): + "Parse gcode text and store the lift gcode." + self.liftRepository = liftRepository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.oldLocation = None + if self.layerStep == None: + self.layerStep = self.layerHeight + self.cuttingLift = self.layerStep * liftRepository.cuttingLiftOverLayerStep.value + self.setMaximumZ() + self.travelZ = self.maximumZ + 0.5 * self.layerStep + liftRepository.clearanceAboveTop.value + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getLinearMove( self, line, location, splitLine ): + "Get the linear move." + if self.extruderActive: + z = location.z + self.cuttingLift + return self.distanceFeedRate.getLineWithZ( line, splitLine, z ) + if self.previousActiveMovementLine != None: + previousActiveMovementLineSplit = self.previousActiveMovementLine.split() + self.distanceFeedRate.addLine( self.distanceFeedRate.getLineWithZ( self.previousActiveMovementLine, previousActiveMovementLineSplit, self.travelZ ) ) + self.previousActiveMovementLine = None + self.distanceFeedRate.addLine( self.distanceFeedRate.getLineWithZ( line, splitLine, self.travelZ ) ) + self.previousInactiveMovementLine = line + return '' + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('lift') + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.layerStep = float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the lift skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + line = self.getLinearMove( line, location, splitLine ) + self.previousActiveMovementLine = line + self.oldLocation = location + elif firstWord == 'M101': + self.addPreviousInactiveMovementLineIfNecessary() + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.distanceFeedRate.addLine(line) + + def setMaximumZ(self): + "Set maximum z." + localOldLocation = None + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( localOldLocation, splitLine ) + self.maximumZ = max( self.maximumZ, location.z ) + localOldLocation = location + + +def main(): + "Display the lift dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py new file mode 100644 index 0000000..48786e6 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py @@ -0,0 +1,201 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +This plugin limits the feed rate of the tool head, so that the stepper motors are not driven too fast and skip steps. + +The limit manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit + +The maximum z feed rate is defined in speed. + +==Operation== +The default 'Activate Limit' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Maximum Initial Feed Rate=== +Default is one millimeter per second. + +Defines the maximum speed of the inital tool head move. + +==Examples== +The following examples limit the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and limit.py. + +> python limit.py +This brings up the limit dialog. + +> python limit.py Screw Holder Bottom.stl +The limit tool is parsing the file: +Screw Holder Bottom.stl +.. +The limit tool has created the file: +.. Screw Holder Bottom_limit.gcode + +""" + +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from datetime import date +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/28/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, gcodeText='', repository=None): + 'Limit a gcode file or text.' + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Limit a gcode text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'limit'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(LimitRepository()) + if not repository.activateLimit.value: + return gcodeText + return LimitSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return LimitRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Limit a gcode file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'limit', shouldAnalyze) + + +class LimitRepository: + 'A class to handle the limit settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.limit.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Limit', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit') + self.activateLimit = settings.BooleanSetting().getFromValue('Activate Limit', self, False) + self.maximumInitialFeedRate = settings.FloatSpin().getFromValue(0.5, 'Maximum Initial Feed Rate (mm/s):', self, 10.0, 1.0) + self.executeTitle = 'Limit' + + def execute(self): + 'Limit button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class LimitSkein: + 'A class to limit a skein of extrusions.' + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = None + self.lineIndex = 0 + self.maximumZDrillFeedRatePerSecond = 987654321.0 + self.oldLocation = None + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the limit gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.maximumZDrillFeedRatePerSecond = min(self.maximumZDrillFeedRatePerSecond, self.maximumZFeedRatePerSecond) + self.maximumZCurrentFeedRatePerSecond = self.maximumZFeedRatePerSecond + for lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine( lineIndex ) + return self.distanceFeedRate.output.getvalue() + + def getLimitedInitialMovement(self, line, splitLine): + 'Get a limited linear movement.' + if self.oldLocation == None: + line = self.distanceFeedRate.getLineWithFeedRate(60.0 * self.repository.maximumInitialFeedRate.value, line, splitLine) + return line + + def getZLimitedLine(self, deltaZ, distance, line, splitLine): + 'Get a replaced z limited gcode movement line.' + zFeedRateSecond = self.feedRateMinute * deltaZ / distance / 60.0 + if zFeedRateSecond <= self.maximumZCurrentFeedRatePerSecond: + return line + limitedFeedRateMinute = self.feedRateMinute * self.maximumZCurrentFeedRatePerSecond / zFeedRateSecond + return self.distanceFeedRate.getLineWithFeedRate(limitedFeedRateMinute, line, splitLine) + + def getZLimitedLineArc(self, line, splitLine): + 'Get a replaced z limited gcode arc movement line.' + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + if self.feedRateMinute == None or self.oldLocation == None: + return line + relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation += relativeLocation + deltaZ = abs(relativeLocation.z) + distance = gcodec.getArcDistance(relativeLocation, splitLine) + return self.getZLimitedLine(deltaZ, distance, line, splitLine) + + def getZLimitedLineLinear(self, line, location, splitLine): + 'Get a replaced z limited gcode linear movement line.' + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + if location == self.oldLocation: + return '' + if self.feedRateMinute == None or self.oldLocation == None: + return line + deltaZ = abs(location.z - self.oldLocation.z) + distance = abs(location - self.oldLocation) + return self.getZLimitedLine(deltaZ, distance, line, splitLine) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('limit') + return + elif firstWord == '(': + self.maximumZDrillFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.maximumZFeedRatePerSecond = float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine( self, lineIndex ): + 'Parse a gcode line and add it to the limit skein.' + line = self.lines[lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + line = self.getLimitedInitialMovement(line, splitLine) + line = self.getZLimitedLineLinear(line, location, splitLine) + self.oldLocation = location + elif firstWord == 'G2' or firstWord == 'G3': + line = self.getZLimitedLineArc(line, splitLine) + elif firstWord == 'M101': + self.maximumZCurrentFeedRatePerSecond = self.maximumZDrillFeedRatePerSecond + elif firstWord == 'M103': + self.maximumZCurrentFeedRatePerSecond = self.maximumZFeedRatePerSecond + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the limit dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/mill.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/mill.py new file mode 100644 index 0000000..90248e5 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/mill.py @@ -0,0 +1,379 @@ +""" +This page is in the table of contents. +Mill is a script to mill the outlines. + +==Operation== +The default 'Activate Mill' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Add Loops=== +====Add Inner Loops==== +Default is on. + +When selected, the inner milling loops will be added. + +====Add Outer Loops==== +Default is on. + +When selected, the outer milling loops will be added. + +===Cross Hatch=== +Default is on. + +When selected, there will be alternating horizontal and vertical milling paths, if it is off there will only be horizontal milling paths. + +===Loop Outset=== +====Loop Inner Outset over Perimeter Width==== +Default is 0.5. + +Defines the ratio of the amount the inner milling loop will be outset over the edge width. + +====Loop Outer Outset over Perimeter Width==== +Default is one. + +Defines the ratio of the amount the outer milling loop will be outset over the edge width. The 'Loop Outer Outset over Perimeter Width' ratio should be greater than the 'Loop Inner Outset over Perimeter Width' ratio. + +===Mill Width over Perimeter Width=== +Default is one. + +Defines the ratio of the mill line width over the edge width. If the ratio is one, all the material will be milled. The greater the 'Mill Width over Perimeter Width' the farther apart the mill lines will be and so less of the material will be directly milled, the remaining material might still be removed in chips if the ratio is not much greater than one. + +==Examples== +The following examples mill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and mill.py. + +> python mill.py +This brings up the mill dialog. + +> python mill.py Screw Holder Bottom.stl +The mill tool is parsing the file: +Screw Holder Bottom.stl +.. +The mill tool has created the file: +Screw Holder Bottom_mill.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText = '', repository=None): + 'Mill the file or gcodeText.' + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Mill a gcode linear move gcodeText.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'mill'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( MillRepository() ) + if not repository.activateMill.value: + return gcodeText + return MillSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return MillRepository() + +def getPointsFromSegmentTable(segmentTable): + 'Get the points from the segment table.' + points = [] + segmentTableKeys = segmentTable.keys() + segmentTableKeys.sort() + for segmentTableKey in segmentTableKeys: + for segment in segmentTable[segmentTableKey]: + for endpoint in segment: + points.append(endpoint.point) + return points + +def isPointOfTableInLoop( loop, pointTable ): + 'Determine if a point in the point table is in the loop.' + for point in loop: + if point in pointTable: + return True + return False + +def writeOutput(fileName, shouldAnalyze=True): + 'Mill a gcode linear move file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'mill', shouldAnalyze) + + +class Average: + 'A class to hold values and get the average.' + def __init__(self): + self.reset() + + def addValue( self, value ): + 'Add a value to the total and the number of values.' + self.numberOfValues += 1 + self.total += value + + def getAverage(self): + 'Get the average.' + if self.numberOfValues == 0: + print('should never happen, self.numberOfValues in Average is zero') + return 0.0 + return self.total / float( self.numberOfValues ) + + def reset(self): + 'Set the number of values and the total to the default.' + self.numberOfValues = 0 + self.total = 0.0 + + +class MillRepository: + 'A class to handle the mill settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.mill.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Mill', self, '') + self.activateMill = settings.BooleanSetting().getFromValue('Activate Mill', self, True ) + settings.LabelDisplay().getFromName('- Add Loops -', self ) + self.addInnerLoops = settings.BooleanSetting().getFromValue('Add Inner Loops', self, True ) + self.addOuterLoops = settings.BooleanSetting().getFromValue('Add Outer Loops', self, True ) + self.crossHatch = settings.BooleanSetting().getFromValue('Cross Hatch', self, True ) + settings.LabelDisplay().getFromName('- Loop Outset -', self ) + self.loopInnerOutsetOverEdgeWidth = settings.FloatSpin().getFromValue( 0.3, 'Loop Inner Outset over Perimeter Width (ratio):', self, 0.7, 0.5 ) + self.loopOuterOutsetOverEdgeWidth = settings.FloatSpin().getFromValue( 0.8, 'Loop Outer Outset over Perimeter Width (ratio):', self, 1.4, 1.0 ) + self.millWidthOverEdgeWidth = settings.FloatSpin().getFromValue( 0.8, 'Mill Width over Edge Width (ratio):', self, 1.8, 1.0 ) + self.executeTitle = 'Mill' + + def execute(self): + 'Mill button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + + +class MillSkein: + 'A class to mill a skein of extrusions.' + def __init__(self): + self.aroundPixelTable = {} + self.average = Average() + self.boundaryLayers = [] + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = 0.6 + self.isExtruderActive = False + self.layerIndex = 0 + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + + def addGcodeFromLoops(self, loops, z): + 'Add gcode from loops.' + if self.oldLocation == None: + self.oldLocation = Vector3() + self.oldLocation.z = z + for loop in loops: + self.distanceFeedRate.addGcodeFromThreadZ(loop, z) + euclidean.addToThreadsFromLoop(self.halfEdgeWidth, 'loop', loop, self.oldLocation, self) + + def addGcodeFromThreadZ( self, thread, z ): + 'Add a thread to the output.' + self.distanceFeedRate.addGcodeFromThreadZ( thread, z ) + + def addMillThreads(self): + 'Add the mill threads to the skein.' + boundaryLayer = self.boundaryLayers[self.layerIndex] + endpoints = euclidean.getEndpointsFromSegmentTable( boundaryLayer.segmentTable ) + if len(endpoints) < 1: + return + paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.millWidth, self.aroundPixelTable, self.aroundWidth) + averageZ = self.average.getAverage() + if self.repository.addInnerLoops.value: + self.addGcodeFromLoops( boundaryLayer.innerLoops, averageZ ) + if self.repository.addOuterLoops.value: + self.addGcodeFromLoops( boundaryLayer.outerLoops, averageZ ) + for path in paths: + simplifiedPath = euclidean.getSimplifiedPath( path, self.millWidth ) + self.distanceFeedRate.addGcodeFromThreadZ( simplifiedPath, averageZ ) + + def addSegmentTableLoops( self, boundaryLayerIndex ): + 'Add the segment tables and loops to the boundary.' + boundaryLayer = self.boundaryLayers[boundaryLayerIndex] + euclidean.subtractXIntersectionsTable(boundaryLayer.outerHorizontalTable, boundaryLayer.innerHorizontalTable) + euclidean.subtractXIntersectionsTable(boundaryLayer.outerVerticalTable, boundaryLayer.innerVerticalTable) + boundaryLayer.horizontalSegmentTable = self.getHorizontalSegmentTableForXIntersectionsTable( + boundaryLayer.outerHorizontalTable) + boundaryLayer.verticalSegmentTable = self.getVerticalSegmentTableForXIntersectionsTable( + boundaryLayer.outerVerticalTable) + betweenPoints = getPointsFromSegmentTable(boundaryLayer.horizontalSegmentTable) + betweenPoints += getPointsFromSegmentTable(boundaryLayer.verticalSegmentTable) + innerPoints = euclidean.getPointsByHorizontalDictionary(self.millWidth, boundaryLayer.innerHorizontalTable) + innerPoints += euclidean.getPointsByVerticalDictionary(self.millWidth, boundaryLayer.innerVerticalTable) + innerPointTable = {} + for innerPoint in innerPoints: + innerPointTable[innerPoint] = None + boundaryLayer.innerLoops = [] + boundaryLayer.outerLoops = [] + millRadius = 0.75 * self.millWidth + loops = triangle_mesh.getDescendingAreaOrientedLoops(betweenPoints, betweenPoints, millRadius) + for loop in loops: + if isPointOfTableInLoop(loop, innerPointTable): + boundaryLayer.innerLoops.append(loop) + else: + boundaryLayer.outerLoops.append(loop) + if self.repository.crossHatch.value and boundaryLayerIndex % 2 == 1: + boundaryLayer.segmentTable = boundaryLayer.verticalSegmentTable + else: + boundaryLayer.segmentTable = boundaryLayer.horizontalSegmentTable + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the mill gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.parseBoundaries() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getHorizontalSegmentTableForXIntersectionsTable( self, xIntersectionsTable ): + 'Get the horizontal segment table from the xIntersectionsTable.' + horizontalSegmentTable = {} + xIntersectionsTableKeys = xIntersectionsTable.keys() + xIntersectionsTableKeys.sort() + for xIntersectionsTableKey in xIntersectionsTableKeys: + xIntersections = xIntersectionsTable[ xIntersectionsTableKey ] + segments = euclidean.getSegmentsFromXIntersections( xIntersections, xIntersectionsTableKey * self.millWidth ) + horizontalSegmentTable[ xIntersectionsTableKey ] = segments + return horizontalSegmentTable + + def getHorizontalXIntersectionsTable(self, loops): + 'Get the horizontal x intersections table from the loops.' + horizontalXIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable(loops, horizontalXIntersectionsTable, self.millWidth) + return horizontalXIntersectionsTable + + def getVerticalSegmentTableForXIntersectionsTable( self, xIntersectionsTable ): + 'Get the vertical segment table from the xIntersectionsTable which has the x and y swapped.' + verticalSegmentTable = {} + xIntersectionsTableKeys = xIntersectionsTable.keys() + xIntersectionsTableKeys.sort() + for xIntersectionsTableKey in xIntersectionsTableKeys: + xIntersections = xIntersectionsTable[ xIntersectionsTableKey ] + segments = euclidean.getSegmentsFromXIntersections( xIntersections, xIntersectionsTableKey * self.millWidth ) + for segment in segments: + for endpoint in segment: + endpoint.point = complex( endpoint.point.imag, endpoint.point.real ) + verticalSegmentTable[ xIntersectionsTableKey ] = segments + return verticalSegmentTable + + def parseBoundaries(self): + 'Parse the boundaries and add them to the boundary layers.' + boundaryLoop = None + boundaryLayer = None + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '()': + boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if boundaryLoop == None: + boundaryLoop = [] + boundaryLayer.loops.append(boundaryLoop) + boundaryLoop.append(location.dropAxis()) + elif firstWord == '(': + boundaryLayer = euclidean.LoopLayer(float(splitLine[1])) + self.boundaryLayers.append(boundaryLayer) + if len(self.boundaryLayers) < 2: + return + for boundaryLayer in self.boundaryLayers: + boundaryLayer.innerOutsetLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.loopInnerOutset) + boundaryLayer.outerOutsetLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.loopOuterOutset) + boundaryLayer.innerHorizontalTable = self.getHorizontalXIntersectionsTable( boundaryLayer.innerOutsetLoops ) + boundaryLayer.outerHorizontalTable = self.getHorizontalXIntersectionsTable( boundaryLayer.outerOutsetLoops ) + boundaryLayer.innerVerticalTable = self.getHorizontalXIntersectionsTable( euclidean.getDiagonalFlippedLoops( boundaryLayer.innerOutsetLoops ) ) + boundaryLayer.outerVerticalTable = self.getHorizontalXIntersectionsTable( euclidean.getDiagonalFlippedLoops( boundaryLayer.outerOutsetLoops ) ) + for boundaryLayerIndex in xrange( len(self.boundaryLayers) - 2, - 1, - 1 ): + boundaryLayer = self.boundaryLayers[ boundaryLayerIndex ] + boundaryLayerBelow = self.boundaryLayers[ boundaryLayerIndex + 1 ] + euclidean.joinXIntersectionsTables( boundaryLayerBelow.outerHorizontalTable, boundaryLayer.outerHorizontalTable ) + euclidean.joinXIntersectionsTables( boundaryLayerBelow.outerVerticalTable, boundaryLayer.outerVerticalTable ) + for boundaryLayerIndex in xrange( 1, len(self.boundaryLayers) ): + boundaryLayer = self.boundaryLayers[ boundaryLayerIndex ] + boundaryLayerAbove = self.boundaryLayers[ boundaryLayerIndex - 1 ] + euclidean.joinXIntersectionsTables( boundaryLayerAbove.innerHorizontalTable, boundaryLayer.innerHorizontalTable ) + euclidean.joinXIntersectionsTables( boundaryLayerAbove.innerVerticalTable, boundaryLayer.innerVerticalTable ) + for boundaryLayerIndex in xrange( len(self.boundaryLayers) ): + self.addSegmentTableLoops(boundaryLayerIndex) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('mill') + return + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.aroundWidth = 0.1 * self.edgeWidth + self.halfEdgeWidth = 0.5 * self.edgeWidth + self.millWidth = self.edgeWidth * self.repository.millWidthOverEdgeWidth.value + self.loopInnerOutset = self.halfEdgeWidth + self.edgeWidth * self.repository.loopInnerOutsetOverEdgeWidth.value + self.loopOuterOutset = self.halfEdgeWidth + self.edgeWidth * self.repository.loopOuterOutsetOverEdgeWidth.value + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the mill skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.isExtruderActive: + self.average.addValue(location.z) + if self.oldLocation != None: + euclidean.addValueSegmentToPixelTable( self.oldLocation.dropAxis(), location.dropAxis(), self.aroundPixelTable, None, self.aroundWidth ) + self.oldLocation = location + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + elif firstWord == '(': + settings.printProgress(self.layerIndex, 'mill') + self.aroundPixelTable = {} + self.average.reset() + elif firstWord == '()': + if len(self.boundaryLayers) > self.layerIndex: + self.addMillThreads() + self.layerIndex += 1 + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the mill dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/multiply.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/multiply.py new file mode 100644 index 0000000..3ae9461 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/multiply.py @@ -0,0 +1,285 @@ +""" +This page is in the table of contents. +The multiply plugin will take a single object and create an array of objects. It is used when you want to print single object multiple times in a single pass. + +You can also position any object using this plugin by setting the center X and center Y to the desired coordinates (0,0 for the center of the print_bed) and setting the number of rows and columns to 1 (effectively setting a 1x1 matrix - printing only a single object). + +The multiply manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Multiply + +Besides using the multiply tool, another way of printing many copies of the model is to duplicate the model in Art of Illusion, however many times you want, with the appropriate offsets. Then you can either use the Join Objects script in the scripts submenu to create a combined shape or you can export the whole scene as an xml file, which skeinforge can then slice. + +==Operation== +The default 'Activate Multiply' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Center=== +Default is the origin. + +The center of the shape will be moved to the "Center X" and "Center Y" coordinates. + +====Center X==== +====Center Y==== + +===Number of Cells=== +====Number of Columns==== +Default is one. + +Defines the number of columns in the array table. + +====Number of Rows==== +Default is one. + +Defines the number of rows in the table. + +===Reverse Sequence every Odd Layer=== +Default is off. + +When selected the build sequence will be reversed on every odd layer so that the tool will travel less. The problem is that the builds would be made with different amount of time to cool, so some would be too hot and some too cold, which is why the default is off. + +===Separation over Perimeter Width=== +Default is fifteen. + +Defines the ratio of separation between the shape copies over the edge width. + +==Examples== +The following examples multiply the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and multiply.py. + +> python multiply.py +This brings up the multiply dialog. + +> python multiply.py Screw Holder Bottom.stl +The multiply tool is parsing the file: +Screw Holder Bottom.stl +.. +The multiply tool has created the file: +.. Screw Holder Bottom_multiply.gcode + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text='', repository=None): + 'Multiply the fill file or text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Multiply the fill text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'multiply'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(MultiplyRepository()) + if not repository.activateMultiply.value: + return gcodeText + return MultiplySkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return MultiplyRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Multiply a gcode linear move file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'multiply', shouldAnalyze) + + +class MultiplyRepository: + 'A class to handle the multiply settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.multiply.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Multiply', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Multiply') + self.activateMultiply = settings.BooleanSetting().getFromValue('Activate Multiply', self, True) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Center -', self ) + self.centerX = settings.FloatSpin().getFromValue(-100.0, 'Center X (mm):', self, 100.0, 105.0) + self.centerY = settings.FloatSpin().getFromValue(-100.0, 'Center Y (mm):', self, 100.0, 105.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Number of Cells -', self) + self.numberOfColumns = settings.IntSpin().getFromValue(1, 'Number of Columns (integer):', self, 10, 1) + self.numberOfRows = settings.IntSpin().getFromValue(1, 'Number of Rows (integer):', self, 10, 1) + settings.LabelSeparator().getFromRepository(self) + self.reverseSequenceEveryOddLayer = settings.BooleanSetting().getFromValue('Reverse Sequence every Odd Layer', self, False) + self.separationOverEdgeWidth = settings.FloatSpin().getFromValue(5.0, 'Separation over Perimeter Width (ratio):', self, 25.0, 15.0) + self.executeTitle = 'Multiply' + + def execute(self): + 'Multiply button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( + self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class MultiplySkein: + 'A class to multiply a skein of extrusions.' + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.isExtrusionActive = False + self.layerIndex = 0 + self.layerLines = [] + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.rowIndex = 0 + self.shouldAccumulate = True + + def addElement(self, offset): + 'Add moved element to the output.' + for line in self.layerLines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '(': + movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine) + line = self.distanceFeedRate.getBoundaryLine(movedLocation) + elif firstWord == 'G1': + movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine) + line = self.distanceFeedRate.getLinearGcodeMovement(movedLocation.dropAxis(), movedLocation.z) + elif firstWord == '(': + movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine) + line = self.distanceFeedRate.getInfillBoundaryLine(movedLocation) + self.distanceFeedRate.addLine(line) + + def addLayer(self): + 'Add multiplied layer to the output.' + self.addRemoveThroughLayer() + offset = self.centerOffset - self.arrayCenter - self.shapeCenter + for rowIndex in xrange(self.repository.numberOfRows.value): + yRowOffset = float(rowIndex) * self.extentPlusSeparation.imag + if self.layerIndex % 2 == 1 and self.repository.reverseSequenceEveryOddLayer.value: + yRowOffset = self.arrayExtent.imag - yRowOffset + for columnIndex in xrange(self.repository.numberOfColumns.value): + xColumnOffset = float(columnIndex) * self.extentPlusSeparation.real + if self.rowIndex % 2 == 1: + xColumnOffset = self.arrayExtent.real - xColumnOffset + elementOffset = complex(offset.real + xColumnOffset, offset.imag + yRowOffset) + self.addElement(elementOffset) + self.rowIndex += 1 + settings.printProgress(self.layerIndex, 'multiply') + if len(self.layerLines) > 1: + self.layerIndex += 1 + self.layerLines = [] + + def addRemoveThroughLayer(self): + 'Parse gcode initialization and store the parameters.' + for layerLineIndex in xrange(len(self.layerLines)): + line = self.layerLines[layerLineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.addLine(line) + if firstWord == '(': + self.layerLines = self.layerLines[layerLineIndex + 1 :] + return + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the multiply gcode.' + self.centerOffset = complex(repository.centerX.value, repository.centerY.value) + self.repository = repository + self.numberOfColumns = repository.numberOfColumns.value + self.numberOfRows = repository.numberOfRows.value + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.setCorners() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getMovedLocationSetOldLocation(self, offset, splitLine): + 'Get the moved location and set the old location.' + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation = location + return Vector3(location.x + offset.real, location.y + offset.imag, location.z) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('multiply') + self.distanceFeedRate.addLine(line) + self.lineIndex += 1 + return + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the multiply skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()': + self.addLayer() + self.distanceFeedRate.addLine(line) + return + elif firstWord == '()': + self.shouldAccumulate = False + if self.shouldAccumulate: + self.layerLines.append(line) + return + self.distanceFeedRate.addLine(line) + + def setCorners(self): + 'Set maximum and minimum corners and z.' + cornerMaximumComplex = complex(-987654321.0, -987654321.0) + cornerMinimumComplex = -cornerMaximumComplex + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.isExtrusionActive: + locationComplex = location.dropAxis() + cornerMaximumComplex = euclidean.getMaximum(locationComplex, cornerMaximumComplex) + cornerMinimumComplex = euclidean.getMinimum(locationComplex, cornerMinimumComplex) + self.oldLocation = location + elif firstWord == 'M101': + self.isExtrusionActive = True + elif firstWord == 'M103': + self.isExtrusionActive = False + self.extent = cornerMaximumComplex - cornerMinimumComplex + self.shapeCenter = 0.5 * (cornerMaximumComplex + cornerMinimumComplex) + self.separation = self.repository.separationOverEdgeWidth.value * self.absoluteEdgeWidth + self.extentPlusSeparation = self.extent + complex(self.separation, self.separation) + columnsMinusOne = self.numberOfColumns - 1 + rowsMinusOne = self.numberOfRows - 1 + self.arrayExtent = complex(self.extentPlusSeparation.real * columnsMinusOne, self.extentPlusSeparation.imag * rowsMinusOne) + self.arrayCenter = 0.5 * self.arrayExtent + + +def main(): + 'Display the multiply dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/oozebane.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/oozebane.py new file mode 100644 index 0000000..234b85f --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/oozebane.py @@ -0,0 +1,581 @@ +""" +This page is in the table of contents. +Oozebane is a script to turn off the extruder before the end of a thread and turn it on before the beginning. + +The oozebane manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Oozebane + +After oozebane turns the extruder on, it slows the feed rate down where the thread starts. Then it speeds it up in steps so in theory the thread will remain at roughly the same thickness from the beginning. + +==Operation== +The default 'Activate Oozebane' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===After Startup Distance=== +Default is 1.2. + +When oozebane reaches the point where the extruder would of turned on, it slows down so that the thread will be thick at that point. Afterwards it speeds the extruder back up to operating speed. The speed up distance is the "After Startup Distance". + +===Early Shutdown Distance=== +Default is 1.2. + +Defines the distance before the end of the thread that the extruder will be turned off. It is the most important oozebane setting. A higher distance means the extruder will turn off sooner and the end of the line will be thinner. + +===Early Startup Maximum Distance=== +Default is 1.2. + +Defines the maximum distance before the thread starts that the extruder will be turned on + +===Early Startup Distance Constant=== +Default is twenty. + +The longer the extruder has been off, the earlier the extruder will turn back on, the ratio is one minus one over e to the power of the distance the extruder has been off over the "Early Startup Distance Constant". + +===First Early Startup Distance=== +Default is twenty five. + +Defines the distance before the first thread starts that the extruder will be turned off. This value should be high because, according to Marius, the extruder takes a second or two to extrude when starting for the first time. + +===Minimum Distance for Early Shutdown=== +Default is zero. + +Defines the minimum distance that the extruder has to be off after the thread end for the early shutdown feature to activate. + +===Minimum Distance for Early Startup=== +Default is zero. + +Defines the minimum distance that the extruder has to be off before the thread begins for the early start up feature to activate. + +===Slowdown Startup Steps=== +Default is three. + +When oozebane turns the extruder off, it slows the feed rate down in steps so in theory the thread will remain at roughly the same thickness until the end. The "Slowdown Startup Steps" setting is the number of steps, the more steps the smaller the size of the step that the feed rate will be decreased and the larger the size of the resulting gcode file. + +==Examples== +The following examples oozebane the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and oozebane.py. + +> python oozebane.py +This brings up the oozebane dialog. + +> python oozebane.py Screw Holder Bottom.stl +The oozebane tool is parsing the file: +Screw Holder Bottom.stl +.. +The oozebane tool has created the file: +.. Screw Holder Bottom_oozebane.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, oozebaneRepository = None ): + "Oozebane a gcode linear move file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), oozebaneRepository ) + +def getCraftedTextFromText( gcodeText, oozebaneRepository = None ): + "Oozebane a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'oozebane'): + return gcodeText + if oozebaneRepository == None: + oozebaneRepository = settings.getReadRepository( OozebaneRepository() ) + if not oozebaneRepository.activateOozebane.value: + return gcodeText + return OozebaneSkein().getCraftedGcode( gcodeText, oozebaneRepository ) + +def getNewRepository(): + 'Get new repository.' + return OozebaneRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Oozebane a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'oozebane', shouldAnalyze) + + +class OozebaneRepository: + "A class to handle the oozebane settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.oozebane.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Oozebane', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Oozebane') + self.activateOozebane = settings.BooleanSetting().getFromValue('Activate Oozebane', self, False ) + self.afterStartupDistance = settings.FloatSpin().getFromValue( 0.7, 'After Startup Distance (millimeters):', self, 1.7, 1.2 ) + self.earlyShutdownDistance = settings.FloatSpin().getFromValue( 0.7, 'Early Shutdown Distance (millimeters):', self, 1.7, 1.2 ) + self.earlyStartupDistanceConstant = settings.FloatSpin().getFromValue( 10.0, 'Early Startup Distance Constant (millimeters):', self, 30.0, 20.0 ) + self.earlyStartupMaximumDistance = settings.FloatSpin().getFromValue( 0.7, 'Early Startup Maximum Distance (millimeters):', self, 1.7, 1.2 ) + self.firstEarlyStartupDistance = settings.FloatSpin().getFromValue( 5.0, 'First Early Startup Distance (millimeters):', self, 45.0, 25.0 ) + self.minimumDistanceForEarlyStartup = settings.FloatSpin().getFromValue( 0.0, 'Minimum Distance for Early Startup (millimeters):', self, 10.0, 0.0 ) + self.minimumDistanceForEarlyShutdown = settings.FloatSpin().getFromValue( 0.0, 'Minimum Distance for Early Shutdown (millimeters):', self, 10.0, 0.0 ) + self.slowdownStartupSteps = settings.IntSpin().getFromValue( 2, 'Slowdown Startup Steps (positive integer):', self, 5, 3 ) + self.executeTitle = 'Oozebane' + + def execute(self): + "Oozebane button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class OozebaneSkein: + "A class to oozebane a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.distanceFromThreadEndToThreadBeginning = None + self.earlyStartupDistance = None + self.extruderInactiveLongEnough = True + self.feedRateMinute = 961.0 + self.isExtruderActive = False + self.isFirstExtrusion = True + self.isShutdownEarly = False + self.isStartupEarly = False + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.operatingFeedRateMinute = 959.0 + self.shutdownStepIndex = 999999999 + self.startupStepIndex = 999999999 + + def addAfterStartupLine( self, splitLine ): + "Add the after startup lines." + distanceAfterThreadBeginning = self.getDistanceAfterThreadBeginning() + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + segment = self.oldLocation - location + segmentLength = segment.magnitude() + distanceBack = distanceAfterThreadBeginning - self.afterStartupDistances[ self.startupStepIndex ] + if segmentLength > 0.0: + locationBack = location + segment * distanceBack / segmentLength + feedRate = self.operatingFeedRateMinute * self.afterStartupFlowRates[ self.startupStepIndex ] + if not self.isCloseToEither( locationBack, location, self.oldLocation ): + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( feedRate, locationBack ) ) + self.startupStepIndex += 1 + + def addLineSetShutdowns(self, line): + "Add a line and set the shutdown variables." + self.distanceFeedRate.addLine(line) + self.isShutdownEarly = True + + def getActiveFeedRateRatio(self): + "Get the feed rate of the first active move over the operating feed rate." + isSearchExtruderActive = self.isExtruderActive + for afterIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + if isSearchExtruderActive: + return gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) / self.operatingFeedRateMinute + elif firstWord == 'M101': + isSearchExtruderActive = True + print('active feed rate ratio was not found in oozebane.') + return 1.0 + + def getAddAfterStartupLines(self, line): + "Get and / or add after the startup lines." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + while self.isDistanceAfterThreadBeginningGreater(): + self.addAfterStartupLine(splitLine) + if self.startupStepIndex >= len( self.afterStartupDistances ): + self.startupStepIndex = len( self.afterStartupDistances ) + 999999999999 + return self.getLinearMoveWithFeedRateSplitLine( self.operatingFeedRateMinute, splitLine ) + feedRate = self.operatingFeedRateMinute * self.getStartupFlowRateMultiplier( self.getDistanceAfterThreadBeginning() / self.afterStartupDistance, len( self.afterStartupDistances ) ) + return self.getLinearMoveWithFeedRateSplitLine( feedRate, splitLine ) + + def getAddBeforeStartupLines(self, line): + "Get and / or add before the startup lines." + distanceThreadBeginning = self.getDistanceToThreadBeginning() + if distanceThreadBeginning == None: + return line + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + self.extruderInactiveLongEnough = False + self.isStartupEarly = True + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + segment = self.oldLocation - location + segmentLength = segment.magnitude() + distanceBack = self.earlyStartupDistance - distanceThreadBeginning + if segmentLength <= 0.0: + print('This should never happen, segmentLength is zero in getAddBeforeStartupLines in oozebane.') + print(line) + self.extruderInactiveLongEnough = True + self.isStartupEarly = False + return line + locationBack = location + segment * distanceBack / segmentLength + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) , locationBack ) ) + self.distanceFeedRate.addLine('M101') + if self.isCloseToEither( locationBack, location, self.oldLocation ): + return '' + return self.getLinearMoveWithFeedRate( self.operatingFeedRateMinute, location ) + + def getAddShutSlowDownLine(self, line): + "Add the shutdown and slowdown lines." + if self.shutdownStepIndex >= len( self.earlyShutdownDistances ): + self.shutdownStepIndex = len( self.earlyShutdownDistances ) + 99999999 + return False + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + distanceThreadEnd = self.getDistanceToExtruderOffCommand( self.earlyShutdownDistances[ self.shutdownStepIndex ] ) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if distanceThreadEnd == None: + distanceThreadEnd = self.getDistanceToExtruderOffCommand( self.earlyShutdownDistances[0] ) + if distanceThreadEnd != None: + shutdownFlowRateMultiplier = self.getShutdownFlowRateMultiplier( 1.0 - distanceThreadEnd / self.earlyShutdownDistance, len( self.earlyShutdownDistances ) ) + line = self.getLinearMoveWithFeedRate( self.feedRateMinute * shutdownFlowRateMultiplier, location ) + self.distanceFeedRate.addLine(line) + return False + segment = self.oldLocation - location + segmentLength = segment.magnitude() + distanceBack = self.earlyShutdownDistances[ self.shutdownStepIndex ] - distanceThreadEnd + locationBack = location + if segmentLength > 0.0: + locationBack = location + segment * distanceBack / segmentLength + if self.shutdownStepIndex == 0: + if not self.isCloseToEither( locationBack, location, self.oldLocation ): + line = self.getLinearMoveWithFeedRate( self.feedRateMinute, locationBack ) + self.distanceFeedRate.addLine(line) + self.addLineSetShutdowns('M103') + return True + if self.isClose( locationBack, self.oldLocation ): + return True + feedRate = self.feedRateMinute * self.earlyShutdownFlowRates[ self.shutdownStepIndex ] + line = self.getLinearMoveWithFeedRate( feedRate, locationBack ) + if self.isClose( locationBack, location ): + line = self.getLinearMoveWithFeedRate( feedRate, location ) + self.distanceFeedRate.addLine(line) + return True + + def getAddShutSlowDownLines(self, line): + "Get and / or add the shutdown and slowdown lines." + while self.getAddShutSlowDownLine(line): + self.shutdownStepIndex += 1 + return '' + + def getCraftedGcode( self, gcodeText, oozebaneRepository ): + "Parse gcode text and store the oozebane gcode." + self.lines = archive.getTextLines(gcodeText) + self.oozebaneRepository = oozebaneRepository + self.parseInitialization( oozebaneRepository ) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getDistanceAfterThreadBeginning(self): + "Get the distance after the beginning of the thread." + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + lastThreadLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + totalDistance = 0.0 + extruderOnReached = False + for beforeIndex in xrange( self.lineIndex - 1, 3, - 1 ): + line = self.lines[ beforeIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( lastThreadLocation, splitLine ) + totalDistance += location.distance( lastThreadLocation ) + lastThreadLocation = location + if extruderOnReached: + return totalDistance + elif firstWord == 'M101': + extruderOnReached = True + return None + + def getDistanceToExtruderOffCommand( self, remainingDistance ): + "Get the distance to the word." + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + lastThreadLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + totalDistance = 0.0 + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( lastThreadLocation, splitLine ) + totalDistance += location.distance( lastThreadLocation ) + lastThreadLocation = location + if totalDistance >= remainingDistance: + return None + elif firstWord == 'M103': + return totalDistance + return None + + def getDistanceToThreadBeginning(self): + "Get the distance to the beginning of the thread." + if self.earlyStartupDistance == None: + return None + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + lastThreadLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + totalDistance = 0.0 + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( lastThreadLocation, splitLine ) + totalDistance += location.distance( lastThreadLocation ) + lastThreadLocation = location + if totalDistance >= self.earlyStartupDistance: + return None + elif firstWord == 'M101': + return totalDistance + return None + + def getDistanceToThreadBeginningAfterThreadEnd( self, remainingDistance ): + "Get the distance to the thread beginning after the end of this thread." + extruderOnReached = False + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + lastThreadLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + threadEndReached = False + totalDistance = 0.0 + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( lastThreadLocation, splitLine ) + if threadEndReached: + totalDistance += location.distance( lastThreadLocation ) + if totalDistance >= remainingDistance: + return None + if extruderOnReached: + return totalDistance + lastThreadLocation = location + elif firstWord == 'M101': + extruderOnReached = True + elif firstWord == 'M103': + threadEndReached = True + return None + + def getDistanceToThreadEnd(self): + "Get the distance to the end of the thread." + if self.shutdownStepIndex >= len( self.earlyShutdownDistances ): + return None + return self.getDistanceToExtruderOffCommand( self.earlyShutdownDistances[ self.shutdownStepIndex ] ) + + def getLinearMoveWithFeedRate( self, feedRate, location ): + "Get a linear move line with the feed rate." + return self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( feedRate, location.dropAxis(), location.z ) + + def getLinearMoveWithFeedRateSplitLine( self, feedRate, splitLine ): + "Get a linear move line with the feed rate and split line." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + return self.getLinearMoveWithFeedRate( feedRate, location ) + + def getOozebaneLine(self, line): + "Get oozebaned gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + if self.oldLocation == None: + return line + if self.startupStepIndex < len( self.afterStartupDistances ): + return self.getAddAfterStartupLines(line) + if self.extruderInactiveLongEnough: + return self.getAddBeforeStartupLines(line) + if self.shutdownStepIndex < len( self.earlyShutdownDistances ): + return self.getAddShutSlowDownLines(line) + if self.isStartupEarly: + return self.getLinearMoveWithFeedRateSplitLine( self.operatingFeedRateMinute, splitLine ) + return line + + def getShutdownFlowRateMultiplier( self, along, numberOfDistances ): + "Get the shut down flow rate multipler." + if numberOfDistances <= 0: + return 1.0 + return 1.0 - 0.5 / float( numberOfDistances ) - along * float( numberOfDistances - 1 ) / float( numberOfDistances ) + + def getStartupFlowRateMultiplier( self, along, numberOfDistances ): + "Get the startup flow rate multipler." + if numberOfDistances <= 0: + return 1.0 + return min( 1.0, 0.5 / float( numberOfDistances ) + along ) + + def isClose( self, location, otherLocation ): + "Determine if the location is close to the other location." + return location.distanceSquared( otherLocation ) < self.closeSquared + + def isCloseToEither( self, location, otherLocationFirst, otherLocationSecond ): + "Determine if the location is close to the other locations." + if self.isClose( location, otherLocationFirst ): + return True + return self.isClose( location, otherLocationSecond ) + + def isDistanceAfterThreadBeginningGreater(self): + "Determine if the distance after the thread beginning is greater than the step index after startup distance." + if self.startupStepIndex >= len( self.afterStartupDistances ): + return False + return self.getDistanceAfterThreadBeginning() > self.afterStartupDistances[ self.startupStepIndex ] + + def parseInitialization( self, oozebaneRepository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('oozebane') + return + elif firstWord == '(': + self.operatingFeedRateMinute = 60.0 * float(splitLine[1]) + self.feedRateMinute = self.operatingFeedRateMinute + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.setExtrusionWidth( oozebaneRepository ) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.setEarlyStartupDistance(splitLine) + line = self.getOozebaneLine(line) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + elif firstWord == 'M101': + self.isExtruderActive = True + self.extruderInactiveLongEnough = False + if self.getDistanceToExtruderOffCommand( self.earlyShutdownDistance ) == None: + self.setEarlyShutdown() + if self.getDistanceToExtruderOffCommand( 1.03 * ( self.earlyShutdownDistance + self.afterStartupDistance ) ) == None: + afterStartupRatio = 1.0 + if self.minimumDistanceForEarlyStartup > 0.0: + if self.distanceFromThreadEndToThreadBeginning != None: + afterStartupRatio = self.distanceFromThreadEndToThreadBeginning / self.minimumDistanceForEarlyStartup + self.setAfterStartupFlowRates( afterStartupRatio ) + self.startupStepIndex = 9999999999 + if len( self.afterStartupDistances ) > 0: + self.startupStepIndex = 0 + if self.isStartupEarly: + self.isStartupEarly = False + return + elif firstWord == 'M103': + self.isExtruderActive = False + self.shutdownStepIndex = 999999999 + if self.getDistanceToThreadBeginning() == None: + self.extruderInactiveLongEnough = True + self.distanceFromThreadEndToThreadBeginning = None + self.earlyStartupDistance = None + if self.isShutdownEarly: + self.isShutdownEarly = False + return + self.distanceFeedRate.addLine(line) + + def setAfterStartupFlowRates( self, afterStartupRatio ): + "Set the after startup flow rates." + afterStartupRatio = min( 1.0, afterStartupRatio ) + afterStartupRatio = max( 0.0, afterStartupRatio ) + self.afterStartupDistance = afterStartupRatio * self.getActiveFeedRateRatio() * self.oozebaneRepository.afterStartupDistance.value + self.afterStartupDistances = [] + self.afterStartupFlowRate = 1.0 + self.afterStartupFlowRates = [] + afterStartupSteps = int( math.floor( afterStartupRatio * float( self.oozebaneRepository.slowdownStartupSteps.value ) ) ) + if afterStartupSteps < 1: + return + if afterStartupSteps < 2: + afterStartupSteps = 2 + for stepIndex in xrange( afterStartupSteps ): + afterWay = ( stepIndex + 1 ) / float( afterStartupSteps ) + afterMiddleWay = self.getStartupFlowRateMultiplier( stepIndex / float( afterStartupSteps ), afterStartupSteps ) + self.afterStartupDistances.append( afterWay * self.afterStartupDistance ) + if stepIndex == 0: + self.afterStartupFlowRate = afterMiddleWay + else: + self.afterStartupFlowRates.append( afterMiddleWay ) + if afterStartupSteps > 0: + self.afterStartupFlowRates.append(1.0) + + def setEarlyShutdown(self): + "Set the early shutdown variables." + distanceToThreadBeginning = self.getDistanceToThreadBeginningAfterThreadEnd( self.minimumDistanceForEarlyShutdown ) + earlyShutdownRatio = 1.0 + if distanceToThreadBeginning != None: + if self.minimumDistanceForEarlyShutdown > 0.0: + earlyShutdownRatio = distanceToThreadBeginning / self.minimumDistanceForEarlyShutdown + self.setEarlyShutdownFlowRates( earlyShutdownRatio ) + if len( self.earlyShutdownDistances ) > 0: + self.shutdownStepIndex = 0 + + def setEarlyShutdownFlowRates( self, earlyShutdownRatio ): + "Set the extrusion width." + earlyShutdownRatio = min( 1.0, earlyShutdownRatio ) + earlyShutdownRatio = max( 0.0, earlyShutdownRatio ) + self.earlyShutdownDistance = earlyShutdownRatio * self.getActiveFeedRateRatio() * self.oozebaneRepository.earlyShutdownDistance.value + self.earlyShutdownDistances = [] + self.earlyShutdownFlowRates = [] + earlyShutdownSteps = int( math.floor( earlyShutdownRatio * float( self.oozebaneRepository.slowdownStartupSteps.value ) ) ) + if earlyShutdownSteps < 2: + earlyShutdownSteps = 0 + earlyShutdownStepsMinusOne = float( earlyShutdownSteps ) - 1.0 + for stepIndex in xrange( earlyShutdownSteps ): + downMiddleWay = self.getShutdownFlowRateMultiplier( stepIndex / earlyShutdownStepsMinusOne, earlyShutdownSteps ) + downWay = 1.0 - stepIndex / earlyShutdownStepsMinusOne + self.earlyShutdownFlowRates.append( downMiddleWay ) + self.earlyShutdownDistances.append( downWay * self.earlyShutdownDistance ) + + def setEarlyStartupDistance( self, splitLine ): + "Set the early startup distance." + if self.earlyStartupDistance != None: + return + self.distanceFromThreadEndToThreadBeginning = 0.0 + lastThreadLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.oldLocation != None: + self.distanceFromThreadEndToThreadBeginning = lastThreadLocation.distance( self.oldLocation ) + for afterIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[ afterIndex ] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine( lastThreadLocation, splitLine ) + self.distanceFromThreadEndToThreadBeginning += location.distance( lastThreadLocation ) + lastThreadLocation = location + elif firstWord == 'M101': + distanceConstantRatio = self.distanceFromThreadEndToThreadBeginning / self.earlyStartupDistanceConstant + earlyStartupOperatingDistance = self.earlyStartupMaximumDistance * ( 1.0 - math.exp( - distanceConstantRatio ) ) + if self.isFirstExtrusion: + earlyStartupOperatingDistance = self.oozebaneRepository.firstEarlyStartupDistance.value + self.isFirstExtrusion = False + self.earlyStartupDistance = earlyStartupOperatingDistance * self.getActiveFeedRateRatio() + return + + def setExtrusionWidth( self, oozebaneRepository ): + "Set the extrusion width." + self.closeSquared = 0.01 * self.edgeWidth * self.edgeWidth + self.earlyStartupMaximumDistance = oozebaneRepository.earlyStartupMaximumDistance.value + self.earlyStartupDistanceConstant = oozebaneRepository.earlyStartupDistanceConstant.value + self.minimumDistanceForEarlyStartup = oozebaneRepository.minimumDistanceForEarlyStartup.value + self.minimumDistanceForEarlyShutdown = oozebaneRepository.minimumDistanceForEarlyShutdown.value + self.setEarlyShutdownFlowRates(1.0) + self.setAfterStartupFlowRates(1.0) + + +def main(): + "Display the oozebane dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/outset.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/outset.py new file mode 100644 index 0000000..6bc7529 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/outset.py @@ -0,0 +1,168 @@ +""" +This page is in the table of contents. +Outset outsets the edges of the slices of a gcode file. The outside edges will be outset by half the edge width, and the inside edges will be inset by half the edge width. Outset is needed for subtractive machining, like cutting or milling. + +==Operation== +The default 'Activate Outset' checkbox is on. When it is on, the gcode will be outset, when it is off, the gcode will not be changed. + +==Examples== +The following examples outset the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and outset.py. + +> python outset.py +This brings up the outset dialog. + +> python outset.py Screw Holder Bottom.stl +The outset tool is parsing the file: +Screw Holder Bottom.stl +.. +The outset tool has created the file: +.. Screw Holder Bottom_outset.gcode + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', repository=None): + 'Outset the preface file or text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Outset the preface gcode text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'outset'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( OutsetRepository() ) + if not repository.activateOutset.value: + return gcodeText + return OutsetSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return OutsetRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Outset the carving of a gcode file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'outset', shouldAnalyze) + + +class OutsetRepository: + 'A class to handle the outset settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.outset.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Outset', self, '') + self.activateOutset = settings.BooleanSetting().getFromValue('Activate Outset', self, True ) + self.executeTitle = 'Outset' + + def execute(self): + 'Outset button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class OutsetSkein: + 'A class to outset a skein of extrusions.' + def __init__(self): + self.boundary = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.loopLayer = None + + def addGcodeFromRemainingLoop( self, loop, radius, z ): + 'Add the remainder of the loop.' + boundary = intercircle.getLargestInsetLoopFromLoopRegardless( loop, radius ) + euclidean.addNestedRingBeginning( self.distanceFeedRate, boundary, z ) + self.distanceFeedRate.addPerimeterBlock(loop, z) + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLine('()') + + def addOutset(self, loopLayer): + 'Add outset to the layer.' + extrudateLoops = intercircle.getInsetLoopsFromLoops(loopLayer.loops, -self.absoluteHalfEdgeWidth) + triangle_mesh.sortLoopsInOrderOfArea(False, extrudateLoops) + for extrudateLoop in extrudateLoops: + self.addGcodeFromRemainingLoop(extrudateLoop, self.absoluteHalfEdgeWidth, loopLayer.z) + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the bevel gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine( lineIndex ) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('outset') + return + elif firstWord == '(': + self.absoluteHalfEdgeWidth = 0.5 * abs(float(splitLine[1])) + self.distanceFeedRate.addLine(line) + + def parseLine( self, lineIndex ): + 'Parse a gcode line and add it to the outset skein.' + line = self.lines[lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.boundary.append(location.dropAxis()) + elif firstWord == '(': + self.layerCount.printProgressIncrement('outset') + self.loopLayer = euclidean.LoopLayer(float(splitLine[1])) + self.distanceFeedRate.addLine(line) + elif firstWord == '()': + self.addOutset( self.loopLayer ) + self.loopLayer = None + elif firstWord == '()': + self.boundary = [] + self.loopLayer.loops.append( self.boundary ) + if self.loopLayer == None: + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the outset dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/preface.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/preface.py new file mode 100644 index 0000000..f39a260 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/preface.py @@ -0,0 +1,230 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Preface converts the svg slices into gcode extrusion layers, optionally with home, positioning, turn off, and unit commands. + +The preface manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Preface + +==Settings== +===Meta=== +Default is empty. + +The 'Meta' field is to add meta tags or a note to all your files. Whatever is in that field will be added in a meta tagged line to the output. + +===Set Positioning to Absolute=== +Default is on. + +When selected, preface will add the G90 command to set positioning to absolute. + +===Set Units to Millimeters=== +Default is on. + +When selected, preface will add the G21 command to set the units to millimeters. + +===Start at Home=== +Default is off. + +When selected, the G28 go to home gcode will be added at the beginning of the file. + +===Turn Extruder Off=== +====Turn Extruder Off at Shut Down==== +Default is on. + +When selected, the M103 turn extruder off gcode will be added at the end of the file. + +====Turn Extruder Off at Start Up==== +Default is on. + +When selected, the M103 turn extruder off gcode will be added at the beginning of the file. + +==Examples== +The following examples preface the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and preface.py. + +> python preface.py +This brings up the preface dialog. + +> python preface.py Screw Holder Bottom.stl +The preface tool is parsing the file: +Screw Holder Bottom.stl +.. +The preface tool has created the file: +.. Screw Holder Bottom_preface.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from datetime import date, datetime +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.svg_reader import SVGReader +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +from time import strftime +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', repository = None ): + "Preface and convert an svg file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText( text, repository = None ): + "Preface and convert an svg text." + if gcodec.isProcedureDoneOrFileIsEmpty( text, 'preface'): + return text + if repository == None: + repository = settings.getReadRepository(PrefaceRepository()) + return PrefaceSkein().getCraftedGcode(repository, text) + +def getNewRepository(): + 'Get new repository.' + return PrefaceRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Preface the carving of a gcode file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'preface', shouldAnalyze) + + +class PrefaceRepository: + "A class to handle the preface settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.preface.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Preface', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Preface') + self.meta = settings.StringSetting().getFromValue('Meta:', self, '') + self.setPositioningToAbsolute = settings.BooleanSetting().getFromValue('Set Positioning to Absolute', self, True ) + self.setUnitsToMillimeters = settings.BooleanSetting().getFromValue('Set Units to Millimeters', self, True ) + self.startAtHome = settings.BooleanSetting().getFromValue('Start at Home', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Turn Extruder Off -', self ) + self.turnExtruderOffAtShutDown = settings.BooleanSetting().getFromValue('Turn Extruder Off at Shut Down', self, True ) + self.turnExtruderOffAtStartUp = settings.BooleanSetting().getFromValue('Turn Extruder Off at Start Up', self, True ) + self.executeTitle = 'Preface' + + def execute(self): + "Preface button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class PrefaceSkein: + "A class to preface a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.lineIndex = 0 + self.oldLocation = None + self.svgReader = SVGReader() + + def addInitializationToOutput(self): + "Add initialization gcode to the output." + self.distanceFeedRate.addTagBracketedLine('format', 'skeinforge gcode') + absoluteFilePathUntilDot = archive.getUntilDot(archive.getCraftPluginsDirectoryPath('preface.py')) + dateTodayString = date.today().isoformat().replace('-', '.')[2 :] + if absoluteFilePathUntilDot == '/home/enrique/Desktop/backup/babbleold/script/reprap/fabmetheus/skeinforge_application/skeinforge_plugins/craft_plugins/preface': #is this script on Enrique's computer? + archive.writeFileText(archive.getVersionFileName(), dateTodayString) + versionText = archive.getFileText(archive.getVersionFileName()) + self.distanceFeedRate.addTagBracketedLine('version', versionText) + dateTimeTuple = datetime.now().timetuple() + created = dateTodayString + '|%s:%s' % (dateTimeTuple[3], dateTimeTuple[4]) + self.distanceFeedRate.addTagBracketedLine('created', created) + self.distanceFeedRate.addLine('()') + if self.repository.setPositioningToAbsolute.value: + self.distanceFeedRate.addLine('G90 ;set positioning to absolute') # Set positioning to absolute. + if self.repository.setUnitsToMillimeters.value: + self.distanceFeedRate.addLine('G21 ;set units to millimeters') # Set units to millimeters. + if self.repository.startAtHome.value: + self.distanceFeedRate.addLine('G28 ;start at home') # Start at home. + if self.repository.turnExtruderOffAtStartUp.value: + self.distanceFeedRate.addLine('M103') # Turn extruder off. + craftTypeName = skeinforge_profile.getCraftTypeName() + self.distanceFeedRate.addTagBracketedLine('craftTypeName', craftTypeName) + self.distanceFeedRate.addTagBracketedLine('decimalPlacesCarried', self.distanceFeedRate.decimalPlacesCarried) + layerHeight = float(self.svgReader.sliceDictionary['layerHeight']) + self.distanceFeedRate.addTagRoundedLine('layerThickness', layerHeight) + self.distanceFeedRate.addTagRoundedLine('layerHeight', layerHeight) + if self.repository.meta.value: + self.distanceFeedRate.addTagBracketedLine('meta', self.repository.meta.value) + edgeWidth = float(self.svgReader.sliceDictionary['edgeWidth']) + self.distanceFeedRate.addTagRoundedLine('edgeWidth', edgeWidth) + self.distanceFeedRate.addTagRoundedLine('perimeterWidth', edgeWidth) + self.distanceFeedRate.addTagBracketedLine('profileName', skeinforge_profile.getProfileName(craftTypeName)) + self.distanceFeedRate.addLine('()') + pluginFileNames = skeinforge_craft.getPluginFileNames() + for pluginFileName in pluginFileNames: + self.addToolSettingLines(pluginFileName) + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addTagBracketedLine('timeStampPreface', strftime('%Y%m%d_%H%M%S')) + procedureNames = self.svgReader.sliceDictionary['procedureName'].replace(',', ' ').split() + for procedureName in procedureNames: + self.distanceFeedRate.addTagBracketedProcedure(procedureName) + self.distanceFeedRate.addTagBracketedProcedure('preface') + self.distanceFeedRate.addLine('()') # Initialization is finished, extrusion is starting. + self.distanceFeedRate.addLine('()') # Initialization is finished, crafting is starting. + + def addPreface( self, loopLayer ): + "Add preface to the carve layer." + self.distanceFeedRate.addLine('( %s )' % loopLayer.z ) # Indicate that a new layer is starting. + for loop in loopLayer.loops: + self.distanceFeedRate.addGcodeFromLoop(loop, loopLayer.z) + self.distanceFeedRate.addLine('()') + + def addShutdownToOutput(self): + "Add shutdown gcode to the output." + self.distanceFeedRate.addLine('()') # GCode formatted comment + if self.repository.turnExtruderOffAtShutDown.value: + self.distanceFeedRate.addLine('M103') # Turn extruder motor off. + + def addToolSettingLines(self, pluginName): + "Add tool setting lines." + preferences = skeinforge_craft.getCraftPreferences(pluginName) + if skeinforge_craft.getCraftValue('Activate %s' % pluginName.capitalize(), preferences) != True: + return + for preference in preferences: + valueWithoutReturn = str(preference.value).replace('\n', ' ').replace('\r', ' ') + if preference.name != 'WindowPosition' and not preference.name.startswith('Open File'): + line = '%s %s %s' % (pluginName, preference.name.replace(' ', '_'), valueWithoutReturn) + self.distanceFeedRate.addTagBracketedLine('setting', line) + + def getCraftedGcode( self, repository, gcodeText ): + "Parse gcode text and store the bevel gcode." + self.repository = repository + self.svgReader.parseSVG('', gcodeText) + if self.svgReader.sliceDictionary == None: + print('Warning, nothing will be done because the sliceDictionary could not be found getCraftedGcode in preface.') + return '' + self.distanceFeedRate.decimalPlacesCarried = int(self.svgReader.sliceDictionary['decimalPlacesCarried']) + self.addInitializationToOutput() + for loopLayerIndex, loopLayer in enumerate(self.svgReader.loopLayers): + settings.printProgressByNumber(loopLayerIndex, len(self.svgReader.loopLayers), 'preface') + self.addPreface( loopLayer ) + self.addShutdownToOutput() + return self.distanceFeedRate.output.getvalue() + + +def main(): + "Display the preface dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py new file mode 100644 index 0000000..cdb47ce --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py @@ -0,0 +1,1108 @@ +""" +This page is in the table of contents. +Raft is a plugin to create a raft, elevate the nozzle and set the temperature. A raft is a flat base structure on top of which your object is being build and has a few different purposes. It fills irregularities like scratches and pits in your printbed and gives you a nice base parallel to the printheads movement. It also glues your object to the bed so to prevent warping in bigger object. The rafts base layer performs these tricks while the sparser interface layer(s) help you removing the object from the raft after printing. It is based on the Nophead's reusable raft, which has a base layer running one way, and a couple of perpendicular layers above. Each set of layers can be set to a different temperature. There is the option of having the extruder orbit the raft for a while, so the heater barrel has time to reach a different temperature, without ooze accumulating around the nozzle. + +The raft manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft + +The important values for the raft settings are the temperatures of the raft, the first layer and the next layers. These will be different for each material. The default settings for ABS, HDPE, PCL & PLA are extrapolated from Nophead's experiments. + +You don't necessarily need a raft and especially small object will print fine on a flat bed without one, sometimes its even better when you need a water tight base to print directly on the bed. If you want to only set the temperature or only create support material or only elevate the nozzle without creating a raft, set the Base Layers and Interface Layers to zero. + + +Image:Raft.jpg|Raft + + +Example of a raft on the left with the interface layers partially removed exposing the base layer. Notice that the first line of the base is rarely printed well because of the startup time of the extruder. On the right you see an object with its raft still attached. + +The Raft panel has some extra settings, it probably made sense to have them there but they have not that much to do with the actual Raft. First are the Support material settings. Since close to all RepRap style printers have no second extruder for support material Skeinforge offers the option to print support structures with the same material set at a different speed and temperature. The idea is that the support sticks less to the actual object when it is extruded around the minimum possible working temperature. This results in a temperature change EVERY layer so build time will increase seriously. + +Allan Ecker aka The Masked Retriever's has written two quicktips for raft which follow below. +"Skeinforge Quicktip: The Raft, Part 1" at: +http://blog.thingiverse.com/2009/07/14/skeinforge-quicktip-the-raft-part-1/ +"Skeinforge Quicktip: The Raft, Part II" at: +http://blog.thingiverse.com/2009/08/04/skeinforge-quicktip-the-raft-part-ii/ + +Nophead has written about rafts on his blog: +http://hydraraptor.blogspot.com/2009/07/thoughts-on-rafts.html + +More pictures of rafting in action are available from the Metalab blog at: +http://reprap.soup.io/?search=rafting + +==Operation== +Default: On + +When it is on, the functions described below will work, when it is off, nothing will be done, so no temperatures will be set, nozzle will not be lifted.. + +==Settings== +===Add Raft, Elevate Nozzle, Orbit=== +Default: On + +When selected, the script will also create a raft, elevate the nozzle, orbit and set the altitude of the bottom of the raft. It also turns on support generation. + +===Base=== +Base layer is the part of the raft that touches the bed. + +====Base Feed Rate Multiplier==== +Default is one. + +Defines the base feed rate multiplier. The greater the 'Base Feed Rate Multiplier', the thinner the base, the lower the 'Base Feed Rate Multiplier', the thicker the base. + +====Base Flow Rate Multiplier==== +Default is one. + +Defines the base flow rate multiplier. The greater the 'Base Flow Rate Multiplier', the thicker the base, the lower the 'Base Flow Rate Multiplier', the thinner the base. + +====Base Infill Density==== +Default is 0.5. + +Defines the infill density ratio of the base of the raft. + +====Base Layer Height over Layer Thickness==== +Default is two. + +Defines the ratio of the height & width of the base layer compared to the height and width of the object infill. The feed rate will be slower for raft layers which have thicker extrusions than the object infill. + +====Base Layers==== +Default is one. + +Defines the number of base layers. + +====Base Nozzle Lift over Base Layer Thickness==== +Default is 0.4. + +Defines the amount the nozzle is above the center of the base extrusion divided by the base layer thickness. + +===Initial Circling=== +Default is off. + +When selected, the extruder will initially circle around until it reaches operating temperature. + +===Infill Overhang over Extrusion Width=== +Default is 0.05. + +Defines the ratio of the infill overhang over the the extrusion width of the raft. + +===Interface=== +====Interface Feed Rate Multiplier==== +Default is one. + +Defines the interface feed rate multiplier. The greater the 'Interface Feed Rate Multiplier', the thinner the interface, the lower the 'Interface Feed Rate Multiplier', the thicker the interface. + +====Interface Flow Rate Multiplier==== +Default is one. + +Defines the interface flow rate multiplier. The greater the 'Interface Flow Rate Multiplier', the thicker the interface, the lower the 'Interface Flow Rate Multiplier', the thinner the interface. + +====Interface Infill Density==== +Default is 0.5. + +Defines the infill density ratio of the interface of the raft. + +====Interface Layer Thickness over Extrusion Height==== +Default is one. + +Defines the ratio of the height & width of the interface layer compared to the height and width of the object infill. The feed rate will be slower for raft layers which have thicker extrusions than the object infill. + +====Interface Layers==== +Default is two. + +Defines the number of interface layers to print. + +====Interface Nozzle Lift over Interface Layer Thickness==== +Default is 0.45. + +Defines the amount the nozzle is above the center of the interface extrusion divided by the interface layer thickness. + +===Name of Alteration Files=== +If support material is generated, raft looks for alteration files in the alterations folder in the .skeinforge folder in the home directory. Raft does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. + +====Name of Support End File==== +Default is support_end.gcode. + +If support material is generated and if there is a file with the name of the "Name of Support End File" setting, it will be added to the end of the support gcode. + +====Name of Support Start File==== +If support material is generated and if there is a file with the name of the "Name of Support Start File" setting, it will be added to the start of the support gcode. + +===Operating Nozzle Lift over Layer Thickness=== +Default is 0.5. + +Defines the amount the nozzle is above the center of the operating extrusion divided by the layer height. + +===Raft Size=== +The raft fills a rectangle whose base size is the rectangle around the bottom layer of the object expanded on each side by the 'Raft Margin' plus the 'Raft Additional Margin over Length (%)' percentage times the length of the side. + +====Raft Additional Margin over Length==== +Default is 1 percent. + +====Raft Margin==== +Default is three millimeters. + +===Support=== +Good articles on support material are at: +http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-1/ +http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-2/ + +====Support Cross Hatch==== +Default is off. + +When selected, the support material will cross hatched. Cross hatching the support makes it stronger and harder to remove, which is why the default is off. + +====Support Flow Rate over Operating Flow Rate==== +Default: 0.9. + +Defines the ratio of the flow rate when the support is extruded over the operating flow rate. With a number less than one, the support flow rate will be smaller so the support will be thinner and easier to remove. + +====Support Gap over Perimeter Extrusion Width==== +Default: 0.5. + +Defines the gap between the support material and the object over the edge extrusion width. + +====Support Material Choice==== +Default is 'None' because the raft takes time to generate. + +=====Empty Layers Only===== +When selected, support material will be only on the empty layers. This is useful when making identical objects in a stack. + +=====Everywhere===== +When selected, support material will be added wherever there are overhangs, even inside the object. Because support material inside objects is hard or impossible to remove, this option should only be chosen if the object has a cavity that needs support and there is some way to extract the support material. + +=====Exterior Only===== +When selected, support material will be added only the exterior of the object. This is the best option for most objects which require support material. + +=====None===== +When selected, raft will not add support material. + +====Support Minimum Angle==== +Default is sixty degrees. + +Defines the minimum angle that a surface overhangs before support material is added. If angle is lower then this value the support will be generated. This angle is defined from the vertical, so zero is a vertical wall, ten is a wall with a bit of overhang, thirty is the typical safe angle for filament extrusion, sixty is a really high angle for extrusion and ninety is an unsupported horizontal ceiling. + +==Examples== +The following examples raft the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and raft.py. + +> python raft.py +This brings up the raft dialog. + +> python raft.py Screw Holder Bottom.stl +The raft tool is parsing the file: +Screw Holder Bottom.stl +.. +The raft tool has created the file: +Screw Holder Bottom_raft.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +#maybe later wide support +#raft outline temperature http://hydraraptor.blogspot.com/2008/09/screw-top-pot.html +def getCraftedText( fileName, text='', repository=None): + 'Raft the file or text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Raft a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'raft'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( RaftRepository() ) + if not repository.activateRaft.value: + return gcodeText + return RaftSkein().getCraftedGcode(gcodeText, repository) + +def getCrossHatchPointLine( crossHatchPointLineTable, y ): + 'Get the cross hatch point line.' + if not crossHatchPointLineTable.has_key(y): + crossHatchPointLineTable[ y ] = {} + return crossHatchPointLineTable[ y ] + +def getEndpointsFromYIntersections( x, yIntersections ): + 'Get endpoints from the y intersections.' + endpoints = [] + for yIntersectionIndex in xrange( 0, len( yIntersections ), 2 ): + firstY = yIntersections[ yIntersectionIndex ] + secondY = yIntersections[ yIntersectionIndex + 1 ] + if firstY != secondY: + firstComplex = complex( x, firstY ) + secondComplex = complex( x, secondY ) + endpointFirst = euclidean.Endpoint() + endpointSecond = euclidean.Endpoint().getFromOtherPoint( endpointFirst, secondComplex ) + endpointFirst.getFromOtherPoint( endpointSecond, firstComplex ) + endpoints.append( endpointFirst ) + endpoints.append( endpointSecond ) + return endpoints + +def getExtendedLineSegment(extensionDistance, lineSegment, loopXIntersections): + 'Get extended line segment.' + pointBegin = lineSegment[0].point + pointEnd = lineSegment[1].point + segment = pointEnd - pointBegin + segmentLength = abs(segment) + if segmentLength <= 0.0: + print('This should never happen in getExtendedLineSegment in raft, the segment should have a length greater than zero.') + print(lineSegment) + return None + segmentExtend = segment * extensionDistance / segmentLength + lineSegment[0].point -= segmentExtend + lineSegment[1].point += segmentExtend + for loopXIntersection in loopXIntersections: + setExtendedPoint(lineSegment[0], pointBegin, loopXIntersection) + setExtendedPoint(lineSegment[1], pointEnd, loopXIntersection) + return lineSegment + +def getLoopsBySegmentsDictionary(segmentsDictionary, width): + 'Get loops from a horizontal segments dictionary.' + points = [] + for endpoint in getVerticalEndpoints(segmentsDictionary, width, 0.1 * width, width): + points.append(endpoint.point) + for endpoint in euclidean.getEndpointsFromSegmentTable(segmentsDictionary): + points.append(endpoint.point) + return triangle_mesh.getDescendingAreaOrientedLoops(points, points, width + width) + +def getNewRepository(): + 'Get new repository.' + return RaftRepository() + +def getVerticalEndpoints(horizontalSegmentsTable, horizontalStep, verticalOverhang, verticalStep): + 'Get vertical endpoints.' + interfaceSegmentsTableKeys = horizontalSegmentsTable.keys() + interfaceSegmentsTableKeys.sort() + verticalTableTable = {} + for interfaceSegmentsTableKey in interfaceSegmentsTableKeys: + interfaceSegments = horizontalSegmentsTable[interfaceSegmentsTableKey] + for interfaceSegment in interfaceSegments: + begin = int(round(interfaceSegment[0].point.real / verticalStep)) + end = int(round(interfaceSegment[1].point.real / verticalStep)) + for stepIndex in xrange(begin, end + 1): + if stepIndex not in verticalTableTable: + verticalTableTable[stepIndex] = {} + verticalTableTable[stepIndex][interfaceSegmentsTableKey] = None + verticalTableTableKeys = verticalTableTable.keys() + verticalTableTableKeys.sort() + verticalEndpoints = [] + for verticalTableTableKey in verticalTableTableKeys: + verticalTable = verticalTableTable[verticalTableTableKey] + verticalTableKeys = verticalTable.keys() + verticalTableKeys.sort() + xIntersections = [] + for verticalTableKey in verticalTableKeys: + y = verticalTableKey * horizontalStep + if verticalTableKey - 1 not in verticalTableKeys: + xIntersections.append(y - verticalOverhang) + if verticalTableKey + 1 not in verticalTableKeys: + xIntersections.append(y + verticalOverhang) + for segment in euclidean.getSegmentsFromXIntersections(xIntersections, verticalTableTableKey * verticalStep): + for endpoint in segment: + endpoint.point = complex(endpoint.point.imag, endpoint.point.real) + verticalEndpoints.append(endpoint) + return verticalEndpoints + +def setExtendedPoint( lineSegmentEnd, pointOriginal, x ): + 'Set the point in the extended line segment.' + if x > min( lineSegmentEnd.point.real, pointOriginal.real ) and x < max( lineSegmentEnd.point.real, pointOriginal.real ): + lineSegmentEnd.point = complex( x, pointOriginal.imag ) + +def writeOutput(fileName, shouldAnalyze=True): + 'Raft a gcode linear move file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'raft', shouldAnalyze) + + +class RaftRepository: + 'A class to handle the raft settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.raft.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Raft', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute( + 'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft') + self.activateRaft = settings.BooleanSetting().getFromValue('Activate Raft', self, True) + self.addRaftElevateNozzleOrbitSetAltitude = settings.BooleanSetting().getFromValue( + 'Add Raft, Elevate Nozzle, Orbit:', self, True) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Base -', self) + self.baseFeedRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Feed Rate Multiplier (ratio):', self, 1.1, 1.0) + self.baseFlowRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Flow Rate Multiplier (ratio):', self, 1.1, 1.0) + self.baseInfillDensity = settings.FloatSpin().getFromValue(0.3, 'Base Infill Density (ratio):', self, 0.9, 0.5) + self.baseLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( + 1.0, 'Base Layer Thickness over Layer Thickness:', self, 3.0, 2.0) + self.baseLayers = settings.IntSpin().getFromValue(0, 'Base Layers (integer):', self, 3, 0) + self.baseNozzleLiftOverBaseLayerThickness = settings.FloatSpin().getFromValue( + 0.2, 'Base Nozzle Lift over Base Layer Thickness (ratio):', self, 0.8, 0.4) + settings.LabelSeparator().getFromRepository(self) + self.initialCircling = settings.BooleanSetting().getFromValue('Initial Circling:', self, False) + self.infillOverhangOverExtrusionWidth = settings.FloatSpin().getFromValue( + 0.0, 'Infill Overhang over Extrusion Width (ratio):', self, 0.5, 0.05) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Interface -', self) + self.interfaceFeedRateMultiplier = settings.FloatSpin().getFromValue( + 0.7, 'Interface Feed Rate Multiplier (ratio):', self, 1.1, 1.0) + self.interfaceFlowRateMultiplier = settings.FloatSpin().getFromValue( + 0.7, 'Interface Flow Rate Multiplier (ratio):', self, 1.1, 1.0) + self.interfaceInfillDensity = settings.FloatSpin().getFromValue( + 0.3, 'Interface Infill Density (ratio):', self, 0.9, 0.5) + self.interfaceLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( + 1.0, 'Interface Layer Thickness over Layer Thickness:', self, 3.0, 1.0) + self.interfaceLayers = settings.IntSpin().getFromValue( + 0, 'Interface Layers (integer):', self, 3, 0) + self.interfaceNozzleLiftOverInterfaceLayerThickness = settings.FloatSpin().getFromValue( + 0.25, 'Interface Nozzle Lift over Interface Layer Thickness (ratio):', self, 0.85, 0.45) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Name of Alteration Files -', self) + self.nameOfSupportEndFile = settings.StringSetting().getFromValue('Name of Support End File:', self, 'support_end.gcode') + self.nameOfSupportStartFile = settings.StringSetting().getFromValue( + 'Name of Support Start File:', self, 'support_start.gcode') + settings.LabelSeparator().getFromRepository(self) + self.operatingNozzleLiftOverLayerThickness = settings.FloatSpin().getFromValue( + 0.3, 'Operating Nozzle Lift over Layer Thickness (ratio):', self, 0.7, 0.5) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Raft Size -', self) + self.raftAdditionalMarginOverLengthPercent = settings.FloatSpin().getFromValue( + 0.5, 'Raft Additional Margin over Length (%):', self, 1.5, 1.0) + self.raftMargin = settings.FloatSpin().getFromValue( + 1.0, 'Raft Margin (mm):', self, 5.0, 3.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Support -', self) + self.supportCrossHatch = settings.BooleanSetting().getFromValue('Support Cross Hatch', self, False) + self.supportFlowRateOverOperatingFlowRate = settings.FloatSpin().getFromValue( + 0.7, 'Support Flow Rate over Operating Flow Rate (ratio):', self, 1.1, 1.0) + self.supportGapOverPerimeterExtrusionWidth = settings.FloatSpin().getFromValue( + 0.5, 'Support Gap over Perimeter Extrusion Width (ratio):', self, 1.5, 1.0) + self.supportMaterialChoice = settings.MenuButtonDisplay().getFromName('Support Material Choice: ', self) + self.supportChoiceNone = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'None', self, True) + self.supportChoiceEmptyLayersOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Empty Layers Only', self, False) + self.supportChoiceEverywhere = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Everywhere', self, False) + self.supportChoiceExteriorOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Exterior Only', self, False) + self.supportMinimumAngle = settings.FloatSpin().getFromValue(40.0, 'Support Minimum Angle (degrees):', self, 80.0, 60.0) + self.executeTitle = 'Raft' + + def execute(self): + 'Raft button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class RaftSkein: + 'A class to raft a skein of extrusions.' + def __init__(self): + self.addLineLayerStart = True + self.baseTemperature = None + self.beginLoop = None + self.boundaryLayers = [] + self.coolingRate = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = 0.6 + self.extrusionStart = True + self.extrusionTop = 0.0 + self.feedRateMinute = 961.0 + self.heatingRate = None + self.insetTable = {} + self.interfaceTemperature = None + self.isEdgePath = False + self.isNestedRing = True + self.isStartupEarly = False + self.layerIndex = - 1 + self.layerStarted = False + self.layerHeight = 0.4 + self.lineIndex = 0 + self.lines = None + self.objectFirstLayerInfillTemperature = None + self.objectFirstLayerPerimeterTemperature = None + self.objectNextLayersTemperature = None + self.oldFlowRate = None + self.oldLocation = None + self.oldTemperatureOutputString = None + self.operatingFeedRateMinute = None + self.operatingFlowRate = None + self.operatingLayerEndLine = '( )' + self.operatingJump = None + self.orbitalFeedRatePerSecond = 2.01 + self.supportFlowRate = None + self.supportLayers = [] + self.supportLayersTemperature = None + self.supportedLayersTemperature = None + self.travelFeedRateMinute = None + + def addBaseLayer(self): + 'Add a base layer.' + baseLayerThickness = self.layerHeight * self.baseLayerThicknessOverLayerThickness + zCenter = self.extrusionTop + 0.5 * baseLayerThickness + z = zCenter + baseLayerThickness * self.repository.baseNozzleLiftOverBaseLayerThickness.value + if len(self.baseEndpoints) < 1: + print('This should never happen, the base layer has a size of zero.') + return + self.addLayerFromEndpoints( + self.baseEndpoints, + self.repository.baseFeedRateMultiplier.value, + self.repository.baseFlowRateMultiplier.value, + baseLayerThickness, + self.baseLayerThicknessOverLayerThickness, + self.baseStep, + z) + + def addBaseSegments(self, baseExtrusionWidth): + 'Add the base segments.' + baseOverhang = self.repository.infillOverhangOverExtrusionWidth.value * baseExtrusionWidth + self.baseEndpoints = getVerticalEndpoints(self.interfaceSegmentsTable, self.interfaceStep, baseOverhang, self.baseStep) + + def addEmptyLayerSupport( self, boundaryLayerIndex ): + 'Add support material to a layer if it is empty.' + supportLayer = SupportLayer([]) + self.supportLayers.append(supportLayer) + if len( self.boundaryLayers[ boundaryLayerIndex ].loops ) > 0: + return + aboveXIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsAbove(boundaryLayerIndex), aboveXIntersectionsTable, self.interfaceStep ) + belowXIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsBelow(boundaryLayerIndex), belowXIntersectionsTable, self.interfaceStep ) + supportLayer.xIntersectionsTable = euclidean.getIntersectionOfXIntersectionsTables( [ aboveXIntersectionsTable, belowXIntersectionsTable ] ) + + def addFlowRate(self, flowRate): + 'Add a flow rate value if different.' + if flowRate != None: + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + + def addInterfaceLayer(self): + 'Add an interface layer.' + interfaceLayerThickness = self.layerHeight * self.interfaceLayerThicknessOverLayerThickness + zCenter = self.extrusionTop + 0.5 * interfaceLayerThickness + z = zCenter + interfaceLayerThickness * self.repository.interfaceNozzleLiftOverInterfaceLayerThickness.value + if len(self.interfaceEndpoints) < 1: + print('This should never happen, the interface layer has a size of zero.') + return + self.addLayerFromEndpoints( + self.interfaceEndpoints, + self.repository.interfaceFeedRateMultiplier.value, + self.repository.interfaceFlowRateMultiplier.value, + interfaceLayerThickness, + self.interfaceLayerThicknessOverLayerThickness, + self.interfaceStep, + z) + + def addInterfaceTables(self, interfaceExtrusionWidth): + 'Add interface tables.' + overhang = self.repository.infillOverhangOverExtrusionWidth.value * interfaceExtrusionWidth + self.interfaceEndpoints = [] + self.interfaceIntersectionsTableKeys = self.interfaceIntersectionsTable.keys() + self.interfaceSegmentsTable = {} + for yKey in self.interfaceIntersectionsTableKeys: + self.interfaceIntersectionsTable[yKey].sort() + y = yKey * self.interfaceStep + lineSegments = euclidean.getSegmentsFromXIntersections(self.interfaceIntersectionsTable[yKey], y) + xIntersectionIndexList = [] + for lineSegmentIndex in xrange(len(lineSegments)): + lineSegment = lineSegments[lineSegmentIndex] + endpointBegin = lineSegment[0] + endpointEnd = lineSegment[1] + endpointBegin.point = complex(self.baseStep * math.floor(endpointBegin.point.real / self.baseStep) - overhang, y) + endpointEnd.point = complex(self.baseStep * math.ceil(endpointEnd.point.real / self.baseStep) + overhang, y) + if endpointEnd.point.real > endpointBegin.point.real: + euclidean.addXIntersectionIndexesFromSegment(lineSegmentIndex, lineSegment, xIntersectionIndexList) + xIntersections = euclidean.getJoinOfXIntersectionIndexes(xIntersectionIndexList) + joinedSegments = euclidean.getSegmentsFromXIntersections(xIntersections, y) + if len(joinedSegments) > 0: + self.interfaceSegmentsTable[yKey] = joinedSegments + for joinedSegment in joinedSegments: + self.interfaceEndpoints += joinedSegment + + def addLayerFromEndpoints( + self, + endpoints, + feedRateMultiplier, + flowRateMultiplier, + layerLayerThickness, + layerThicknessRatio, + step, + z): + 'Add a layer from endpoints and raise the extrusion top.' + layerThicknessRatioSquared = layerThicknessRatio * layerThicknessRatio + feedRateMinute = self.feedRateMinute * feedRateMultiplier / layerThicknessRatioSquared + if len(endpoints) < 1: + return + aroundPixelTable = {} + aroundWidth = 0.34321 * step + paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * step, aroundPixelTable, aroundWidth) + self.addLayerLine(z) + if self.operatingFlowRate != None: + self.addFlowRate(flowRateMultiplier * self.operatingFlowRate) + for path in paths: + simplifiedPath = euclidean.getSimplifiedPath(path, step) + self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinute, simplifiedPath, self.travelFeedRateMinute, z) + self.extrusionTop += layerLayerThickness + self.addFlowRate(self.oldFlowRate) + + def addLayerLine(self, z): + 'Add the layer gcode line and close the last layer gcode block.' + if self.layerStarted: + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLine('( %s )' % self.distanceFeedRate.getRounded(z)) # Indicate that a new layer is starting. + if self.beginLoop != None: + zBegin = self.extrusionTop + self.layerHeight + intercircle.addOrbitsIfLarge(self.distanceFeedRate, self.beginLoop, self.orbitalFeedRatePerSecond, self.temperatureChangeTimeBeforeRaft, zBegin) + self.beginLoop = None + self.layerStarted = True + + def addOperatingOrbits(self, boundaryLoops, pointComplex, temperatureChangeTime, z): + 'Add the orbits before the operating layers.' + if len(boundaryLoops) < 1: + return + insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, self.edgeWidth) + if len(insetBoundaryLoops) < 1: + insetBoundaryLoops = boundaryLoops + largestLoop = euclidean.getLargestLoop(insetBoundaryLoops) + if pointComplex != None: + largestLoop = euclidean.getLoopStartingClosest(self.edgeWidth, pointComplex, largestLoop) + intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureChangeTime, z) + + def addRaft(self): + 'Add the raft.' + if len(self.boundaryLayers) < 0: + print('this should never happen, there are no boundary layers in addRaft') + return + self.baseLayerThicknessOverLayerThickness = self.repository.baseLayerThicknessOverLayerThickness.value + baseExtrusionWidth = self.edgeWidth * self.baseLayerThicknessOverLayerThickness + self.baseStep = baseExtrusionWidth / self.repository.baseInfillDensity.value + self.interfaceLayerThicknessOverLayerThickness = self.repository.interfaceLayerThicknessOverLayerThickness.value + interfaceExtrusionWidth = self.edgeWidth * self.interfaceLayerThicknessOverLayerThickness + self.interfaceStep = interfaceExtrusionWidth / self.repository.interfaceInfillDensity.value + self.setCornersZ() + self.cornerMinimumComplex = self.cornerMinimum.dropAxis() + originalExtent = self.cornerMaximumComplex - self.cornerMinimumComplex + self.raftOutsetRadius = self.repository.raftMargin.value + self.repository.raftAdditionalMarginOverLengthPercent.value * 0.01 * max(originalExtent.real, originalExtent.imag) + self.setBoundaryLayers() + outsetSeparateLoops = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[0].loops, -self.raftOutsetRadius, 0.8) + self.interfaceIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable(outsetSeparateLoops, self.interfaceIntersectionsTable, self.interfaceStep) + if len(self.supportLayers) > 0: + supportIntersectionsTable = self.supportLayers[0].xIntersectionsTable + euclidean.joinXIntersectionsTables(supportIntersectionsTable, self.interfaceIntersectionsTable) + self.addInterfaceTables(interfaceExtrusionWidth) + self.addRaftPerimeters() + self.baseIntersectionsTable = {} + complexRadius = complex(self.raftOutsetRadius, self.raftOutsetRadius) + self.complexHigh = complexRadius + self.cornerMaximumComplex + self.complexLow = self.cornerMinimumComplex - complexRadius + self.beginLoop = euclidean.getSquareLoopWiddershins(self.cornerMinimumComplex, self.cornerMaximumComplex) + if not intercircle.orbitsAreLarge(self.beginLoop, self.temperatureChangeTimeBeforeRaft): + self.beginLoop = None + if self.repository.baseLayers.value > 0: + self.addTemperatureLineIfDifferent(self.baseTemperature) + self.addBaseSegments(baseExtrusionWidth) + for baseLayerIndex in xrange(self.repository.baseLayers.value): + self.addBaseLayer() + if self.repository.interfaceLayers.value > 0: + self.addTemperatureLineIfDifferent(self.interfaceTemperature) + self.interfaceIntersectionsTableKeys.sort() + for interfaceLayerIndex in xrange(self.repository.interfaceLayers.value): + self.addInterfaceLayer() + self.operatingJump = self.extrusionTop + self.layerHeight * self.repository.operatingNozzleLiftOverLayerThickness.value + for boundaryLayer in self.boundaryLayers: + if self.operatingJump != None: + boundaryLayer.z += self.operatingJump + if self.repository.baseLayers.value > 0 or self.repository.interfaceLayers.value > 0: + boundaryZ = self.boundaryLayers[0].z + if self.layerStarted: + self.distanceFeedRate.addLine('()') + self.layerStarted = False + self.distanceFeedRate.addLine('( )') + self.addLayerLine(boundaryZ) + temperatureChangeTimeBeforeFirstLayer = self.getTemperatureChangeTime(self.objectFirstLayerPerimeterTemperature) + self.addTemperatureLineIfDifferent(self.objectFirstLayerPerimeterTemperature) + largestOutsetLoop = intercircle.getLargestInsetLoopFromLoop(euclidean.getLargestLoop(outsetSeparateLoops), -self.raftOutsetRadius) + intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestOutsetLoop, self.orbitalFeedRatePerSecond, temperatureChangeTimeBeforeFirstLayer, boundaryZ) + self.addLineLayerStart = False + + def addRaftedLine( self, splitLine ): + 'Add elevated gcode line with operating feed rate.' + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + z = self.oldLocation.z + if self.operatingJump != None: + z += self.operatingJump + temperature = self.objectNextLayersTemperature + if self.layerIndex == 0: + if self.isEdgePath: + temperature = self.objectFirstLayerPerimeterTemperature + else: + temperature = self.objectFirstLayerInfillTemperature + self.addTemperatureLineIfDifferent(temperature) + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, self.oldLocation.dropAxis(), z) + + def addRaftPerimeters(self): + 'Add raft edges if there is a raft.' + interfaceOutset = self.halfEdgeWidth * self.interfaceLayerThicknessOverLayerThickness + for supportLayer in self.supportLayers: + supportSegmentTable = supportLayer.supportSegmentTable + if len(supportSegmentTable) > 0: + outset = interfaceOutset + self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(supportSegmentTable, self.interfaceStep), outset) + if self.repository.baseLayers.value < 1 and self.repository.interfaceLayers.value < 1: + return + overhangMultiplier = 1.0 + self.repository.infillOverhangOverExtrusionWidth.value + self.repository.infillOverhangOverExtrusionWidth.value + outset = self.halfEdgeWidth + if self.repository.interfaceLayers.value > 0: + outset = max(interfaceOutset * overhangMultiplier, outset) + if self.repository.baseLayers.value > 0: + outset = max(self.halfEdgeWidth * self.baseLayerThicknessOverLayerThickness * overhangMultiplier, outset) + self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(self.interfaceSegmentsTable, self.interfaceStep), outset) + + def addRaftPerimetersByLoops(self, loops, outset): + 'Add raft edges to the gcode for loops.' + loops = intercircle.getInsetSeparateLoopsFromLoops(loops, -outset) + for loop in loops: + self.distanceFeedRate.addLine('()') + for point in loop: + roundedX = self.distanceFeedRate.getRounded(point.real) + roundedY = self.distanceFeedRate.getRounded(point.imag) + self.distanceFeedRate.addTagBracketedLine('raftPoint', 'X%s Y%s' % (roundedX, roundedY)) + self.distanceFeedRate.addLine('()') + + def addSegmentTablesToSupportLayers(self): + 'Add segment tables to the support layers.' + for supportLayer in self.supportLayers: + supportLayer.supportSegmentTable = {} + xIntersectionsTable = supportLayer.xIntersectionsTable + for xIntersectionsTableKey in xIntersectionsTable: + y = xIntersectionsTableKey * self.interfaceStep + supportLayer.supportSegmentTable[ xIntersectionsTableKey ] = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], y ) + + def addSupportLayerTemperature(self, endpoints, z): + 'Add support layer and temperature before the object layer.' + self.distanceFeedRate.addLine('()') + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportStartLines) + self.addTemperatureOrbits(endpoints, self.supportedLayersTemperature, z) + aroundPixelTable = {} + aroundWidth = 0.34321 * self.interfaceStep + boundaryLoops = self.boundaryLayers[self.layerIndex].loops + halfSupportOutset = 0.5 * self.supportOutset + aroundBoundaryLoops = intercircle.getAroundsFromLoops(boundaryLoops, halfSupportOutset) + for aroundBoundaryLoop in aroundBoundaryLoops: + euclidean.addLoopToPixelTable(aroundBoundaryLoop, aroundPixelTable, aroundWidth) + paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, aroundWidth) + feedRateMinuteMultiplied = self.operatingFeedRateMinute + supportFlowRateMultiplied = self.supportFlowRate + if self.layerIndex == 0: + feedRateMinuteMultiplied *= self.objectFirstLayerFeedRateInfillMultiplier + if supportFlowRateMultiplied != None: + supportFlowRateMultiplied *= self.objectFirstLayerFlowRateInfillMultiplier + self.addFlowRate(supportFlowRateMultiplied) + for path in paths: + self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinuteMultiplied, path, self.travelFeedRateMinute, z) + self.addFlowRate(self.oldFlowRate) + self.addTemperatureOrbits(endpoints, self.supportLayersTemperature, z) + self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportEndLines) + self.distanceFeedRate.addLine('()') + + def addSupportSegmentTable( self, layerIndex ): + 'Add support segments from the boundary layers.' + aboveLayer = self.boundaryLayers[ layerIndex + 1 ] + aboveLoops = aboveLayer.loops + supportLayer = self.supportLayers[layerIndex] + if len( aboveLoops ) < 1: + return + boundaryLayer = self.boundaryLayers[layerIndex] + rise = aboveLayer.z - boundaryLayer.z + outsetSupportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.minimumSupportRatio * rise) + numberOfSubSteps = 4 + subStepSize = self.interfaceStep / float( numberOfSubSteps ) + aboveIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable( aboveLoops, aboveIntersectionsTable, subStepSize ) + outsetIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopsForTable( outsetSupportLoops, outsetIntersectionsTable, subStepSize ) + euclidean.subtractXIntersectionsTable( aboveIntersectionsTable, outsetIntersectionsTable ) + for aboveIntersectionsTableKey in aboveIntersectionsTable.keys(): + supportIntersectionsTableKey = int( round( float( aboveIntersectionsTableKey ) / numberOfSubSteps ) ) + xIntersectionIndexList = [] + if supportIntersectionsTableKey in supportLayer.xIntersectionsTable: + euclidean.addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] ) + euclidean.addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, aboveIntersectionsTable[ aboveIntersectionsTableKey ] ) + supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList ) + + def addTemperatureLineIfDifferent(self, temperature): + 'Add a line of temperature if different.' + if temperature == None: + return + temperatureOutputString = euclidean.getRoundedToThreePlaces(temperature) + if temperatureOutputString == self.oldTemperatureOutputString: + return + if temperatureOutputString != None: + self.distanceFeedRate.addLine('M104 S' + temperatureOutputString) # Set temperature. + self.oldTemperatureOutputString = temperatureOutputString + + def addTemperatureOrbits( self, endpoints, temperature, z ): + 'Add the temperature and orbits around the support layer.' + if self.layerIndex < 0: + return + boundaryLoops = self.boundaryLayers[self.layerIndex].loops + temperatureTimeChange = self.getTemperatureChangeTime( temperature ) + self.addTemperatureLineIfDifferent( temperature ) + if len( boundaryLoops ) < 1: + layerCornerHigh = complex(-987654321.0, -987654321.0) + layerCornerLow = complex(987654321.0, 987654321.0) + for endpoint in endpoints: + layerCornerHigh = euclidean.getMaximum( layerCornerHigh, endpoint.point ) + layerCornerLow = euclidean.getMinimum( layerCornerLow, endpoint.point ) + squareLoop = euclidean.getSquareLoopWiddershins( layerCornerLow, layerCornerHigh ) + intercircle.addOrbitsIfLarge( self.distanceFeedRate, squareLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z ) + return + edgeInset = 0.4 * self.edgeWidth + insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, edgeInset) + if len( insetBoundaryLoops ) < 1: + insetBoundaryLoops = boundaryLoops + largestLoop = euclidean.getLargestLoop( insetBoundaryLoops ) + intercircle.addOrbitsIfLarge( self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z ) + + def addToFillXIntersectionIndexTables( self, supportLayer ): + 'Add fill segments from the boundary layers.' + supportLoops = supportLayer.supportLoops + supportLayer.fillXIntersectionsTable = {} + if len(supportLoops) < 1: + return + euclidean.addXIntersectionsFromLoopsForTable( supportLoops, supportLayer.fillXIntersectionsTable, self.interfaceStep ) + + def extendXIntersections( self, loops, radius, xIntersectionsTable ): + 'Extend the support segments.' + xIntersectionsTableKeys = xIntersectionsTable.keys() + for xIntersectionsTableKey in xIntersectionsTableKeys: + lineSegments = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], xIntersectionsTableKey ) + xIntersectionIndexList = [] + loopXIntersections = [] + euclidean.addXIntersectionsFromLoops( loops, loopXIntersections, xIntersectionsTableKey ) + for lineSegmentIndex in xrange( len( lineSegments ) ): + lineSegment = lineSegments[ lineSegmentIndex ] + extendedLineSegment = getExtendedLineSegment( radius, lineSegment, loopXIntersections ) + if extendedLineSegment != None: + euclidean.addXIntersectionIndexesFromSegment( lineSegmentIndex, extendedLineSegment, xIntersectionIndexList ) + xIntersections = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList ) + if len( xIntersections ) > 0: + xIntersectionsTable[ xIntersectionsTableKey ] = xIntersections + else: + del xIntersectionsTable[ xIntersectionsTableKey ] + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the raft gcode.' + self.repository = repository + self.minimumSupportRatio = math.tan( math.radians( repository.supportMinimumAngle.value ) ) + self.supportEndLines = settings.getAlterationFileLines(repository.nameOfSupportEndFile.value) + self.supportStartLines = settings.getAlterationFileLines(repository.nameOfSupportStartFile.value) + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.temperatureChangeTimeBeforeRaft = 0.0 + if self.repository.initialCircling.value: + maxBaseInterfaceTemperature = max(self.baseTemperature, self.interfaceTemperature) + firstMaxTemperature = max(maxBaseInterfaceTemperature, self.objectFirstLayerPerimeterTemperature) + self.temperatureChangeTimeBeforeRaft = self.getTemperatureChangeTime(firstMaxTemperature) + if repository.addRaftElevateNozzleOrbitSetAltitude.value: + self.addRaft() + self.addTemperatureLineIfDifferent( self.objectFirstLayerPerimeterTemperature ) + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) + + def getElevatedBoundaryLine( self, splitLine ): + 'Get elevated boundary gcode line.' + location = gcodec.getLocationFromSplitLine(None, splitLine) + if self.operatingJump != None: + location.z += self.operatingJump + return self.distanceFeedRate.getBoundaryLine( location ) + + def getInsetLoops( self, boundaryLayerIndex ): + 'Inset the support loops if they are not already inset.' + if boundaryLayerIndex not in self.insetTable: + self.insetTable[ boundaryLayerIndex ] = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[ boundaryLayerIndex ].loops, self.quarterEdgeWidth) + return self.insetTable[ boundaryLayerIndex ] + + def getInsetLoopsAbove( self, boundaryLayerIndex ): + 'Get the inset loops above the boundary layer index.' + for aboveLayerIndex in xrange( boundaryLayerIndex + 1, len(self.boundaryLayers) ): + if len( self.boundaryLayers[ aboveLayerIndex ].loops ) > 0: + return self.getInsetLoops( aboveLayerIndex ) + return [] + + def getInsetLoopsBelow( self, boundaryLayerIndex ): + 'Get the inset loops below the boundary layer index.' + for belowLayerIndex in xrange( boundaryLayerIndex - 1, - 1, - 1 ): + if len( self.boundaryLayers[ belowLayerIndex ].loops ) > 0: + return self.getInsetLoops( belowLayerIndex ) + return [] + + def getStepsUntilEnd( self, begin, end, stepSize ): + 'Get steps from the beginning until the end.' + step = begin + steps = [] + while step < end: + steps.append( step ) + step += stepSize + return steps + + def getSupportEndpoints(self): + 'Get the support layer segments.' + if len(self.supportLayers) <= self.layerIndex: + return [] + supportSegmentTable = self.supportLayers[self.layerIndex].supportSegmentTable + if self.layerIndex % 2 == 1 and self.repository.supportCrossHatch.value: + return getVerticalEndpoints(supportSegmentTable, self.interfaceStep, 0.1 * self.edgeWidth, self.interfaceStep) + return euclidean.getEndpointsFromSegmentTable(supportSegmentTable) + + def getTemperatureChangeTime( self, temperature ): + 'Get the temperature change time.' + if temperature == None: + return 0.0 + oldTemperature = 25.0 # typical chamber temperature + if self.oldTemperatureOutputString != None: + oldTemperature = float( self.oldTemperatureOutputString ) + if temperature == oldTemperature: + return 0.0 + if temperature > oldTemperature: + return ( temperature - oldTemperature ) / self.heatingRate + return ( oldTemperature - temperature ) / abs( self.coolingRate ) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + self.baseTemperature = float(splitLine[1]) + elif firstWord == '(': + self.coolingRate = float(splitLine[1]) + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('raft') + elif firstWord == '(': + self.heatingRate = float(splitLine[1]) + elif firstWord == '(': + self.interfaceTemperature = float(splitLine[1]) + elif firstWord == '(': + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == 'M108': + self.oldFlowRate = float(splitLine[1][1 :]) + elif firstWord == '(': + self.objectFirstLayerFeedRateInfillMultiplier = float(splitLine[1]) + elif firstWord == '(': + self.objectFirstLayerFlowRateInfillMultiplier = float(splitLine[1]) + elif firstWord == '(': + self.objectFirstLayerInfillTemperature = float(splitLine[1]) + elif firstWord == '(': + self.objectFirstLayerPerimeterTemperature = float(splitLine[1]) + elif firstWord == '(': + self.objectNextLayersTemperature = float(splitLine[1]) + elif firstWord == '(': + self.orbitalFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.operatingFeedRateMinute = 60.0 * float(splitLine[1]) + self.feedRateMinute = self.operatingFeedRateMinute + elif firstWord == '(': + self.operatingFlowRate = float(splitLine[1]) + self.oldFlowRate = self.operatingFlowRate + self.supportFlowRate = self.operatingFlowRate * self.repository.supportFlowRateOverOperatingFlowRate.value + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.halfEdgeWidth = 0.5 * self.edgeWidth + self.quarterEdgeWidth = 0.25 * self.edgeWidth + self.supportOutset = self.edgeWidth + self.edgeWidth * self.repository.supportGapOverPerimeterExtrusionWidth.value + elif firstWord == '(': + self.supportLayersTemperature = float(splitLine[1]) + elif firstWord == '(': + self.supportedLayersTemperature = float(splitLine[1]) + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the raft skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + if self.extrusionStart: + self.addRaftedLine(splitLine) + return + elif firstWord == 'M101': + if self.isStartupEarly: + self.isStartupEarly = False + return + elif firstWord == 'M108': + self.oldFlowRate = float(splitLine[1][1 :]) + elif firstWord == '(': + line = self.getElevatedBoundaryLine(splitLine) + elif firstWord == '()': + self.extrusionStart = False + self.distanceFeedRate.addLine( self.operatingLayerEndLine ) + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'raft') + boundaryLayer = None + layerZ = self.extrusionTop + float(splitLine[1]) + if len(self.boundaryLayers) > 0: + boundaryLayer = self.boundaryLayers[self.layerIndex] + layerZ = boundaryLayer.z + if self.operatingJump != None: + line = '( %s )' % self.distanceFeedRate.getRounded( layerZ ) + if self.layerStarted and self.addLineLayerStart: + self.distanceFeedRate.addLine('()') + self.layerStarted = False + if self.layerIndex > len(self.supportLayers) + 1: + self.distanceFeedRate.addLine( self.operatingLayerEndLine ) + self.operatingLayerEndLine = '' + if self.addLineLayerStart: + self.distanceFeedRate.addLine(line) + self.addLineLayerStart = True + line = '' + endpoints = self.getSupportEndpoints() + if self.layerIndex == 1: + if len(endpoints) < 1: + temperatureChangeTimeBeforeNextLayers = self.getTemperatureChangeTime( self.objectNextLayersTemperature ) + self.addTemperatureLineIfDifferent( self.objectNextLayersTemperature ) + if self.repository.addRaftElevateNozzleOrbitSetAltitude.value and len( boundaryLayer.loops ) > 0: + self.addOperatingOrbits( boundaryLayer.loops, euclidean.getXYComplexFromVector3( self.oldLocation ), temperatureChangeTimeBeforeNextLayers, layerZ ) + if len(endpoints) > 0: + self.addSupportLayerTemperature( endpoints, layerZ ) + elif firstWord == '(' or firstWord == '()': + self.isEdgePath = True + elif firstWord == '()' or firstWord == '()': + self.isEdgePath = False + self.distanceFeedRate.addLine(line) + + def setBoundaryLayers(self): + 'Set the boundary layers.' + if self.repository.supportChoiceNone.value: + return + if len(self.boundaryLayers) < 2: + return + if self.repository.supportChoiceEmptyLayersOnly.value: + supportLayer = SupportLayer([]) + self.supportLayers.append(supportLayer) + for boundaryLayerIndex in xrange(1, len(self.boundaryLayers) -1): + self.addEmptyLayerSupport(boundaryLayerIndex) + self.truncateSupportSegmentTables() + self.addSegmentTablesToSupportLayers() + return + for boundaryLayer in self.boundaryLayers: + # thresholdRadius of 0.8 is needed to avoid the ripple inset bug http://hydraraptor.blogspot.com/2010/12/crackers.html + supportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.supportOutset, 0.8) + supportLayer = SupportLayer(supportLoops) + self.supportLayers.append(supportLayer) + for supportLayerIndex in xrange(len(self.supportLayers) - 1): + self.addSupportSegmentTable(supportLayerIndex) + self.truncateSupportSegmentTables() + for supportLayerIndex in xrange(len(self.supportLayers) - 1): + boundaryLoops = self.boundaryLayers[supportLayerIndex].loops + self.extendXIntersections( boundaryLoops, self.supportOutset, self.supportLayers[supportLayerIndex].xIntersectionsTable) + for supportLayer in self.supportLayers: + self.addToFillXIntersectionIndexTables(supportLayer) + if self.repository.supportChoiceExteriorOnly.value: + for supportLayerIndex in xrange(1, len(self.supportLayers)): + self.subtractJoinedFill(supportLayerIndex) + for supportLayer in self.supportLayers: + euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable) + for supportLayerIndex in xrange(len(self.supportLayers) - 2, -1, -1): + xIntersectionsTable = self.supportLayers[supportLayerIndex].xIntersectionsTable + aboveXIntersectionsTable = self.supportLayers[supportLayerIndex + 1].xIntersectionsTable + euclidean.joinXIntersectionsTables(aboveXIntersectionsTable, xIntersectionsTable) + for supportLayerIndex in xrange(len(self.supportLayers)): + supportLayer = self.supportLayers[supportLayerIndex] + self.extendXIntersections(supportLayer.supportLoops, self.raftOutsetRadius, supportLayer.xIntersectionsTable) + for supportLayer in self.supportLayers: + euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable) + self.addSegmentTablesToSupportLayers() + + def setCornersZ(self): + 'Set maximum and minimum corners and z.' + boundaryLoop = None + boundaryLayer = None + layerIndex = - 1 + self.cornerMaximumComplex = complex(-912345678.0, -912345678.0) + self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0) + self.firstLayerLoops = [] + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '()': + boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if boundaryLoop == None: + boundaryLoop = [] + boundaryLayer.loops.append(boundaryLoop) + boundaryLoop.append(location.dropAxis()) + self.cornerMaximumComplex = euclidean.getMaximum(self.cornerMaximumComplex, location.dropAxis()) + self.cornerMinimum.minimize(location) + elif firstWord == '(': + z = float(splitLine[1]) + boundaryLayer = euclidean.LoopLayer(z) + self.boundaryLayers.append(boundaryLayer) + elif firstWord == '(': + layerIndex += 1 + if self.repository.supportChoiceNone.value: + if layerIndex > 1: + return + + def subtractJoinedFill( self, supportLayerIndex ): + 'Join the fill then subtract it from the support layer table.' + supportLayer = self.supportLayers[supportLayerIndex] + fillXIntersectionsTable = supportLayer.fillXIntersectionsTable + belowFillXIntersectionsTable = self.supportLayers[ supportLayerIndex - 1 ].fillXIntersectionsTable + euclidean.joinXIntersectionsTables( belowFillXIntersectionsTable, supportLayer.fillXIntersectionsTable ) + euclidean.subtractXIntersectionsTable( supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable ) + + def truncateSupportSegmentTables(self): + 'Truncate the support segments after the last support segment which contains elements.' + for supportLayerIndex in xrange( len(self.supportLayers) - 1, - 1, - 1 ): + if len( self.supportLayers[supportLayerIndex].xIntersectionsTable ) > 0: + self.supportLayers = self.supportLayers[ : supportLayerIndex + 1 ] + return + self.supportLayers = [] + + +class SupportLayer: + 'Support loops with segment tables.' + def __init__( self, supportLoops ): + self.supportLoops = supportLoops + self.supportSegmentTable = {} + self.xIntersectionsTable = {} + + def __repr__(self): + 'Get the string representation of this loop layer.' + return '%s' % ( self.supportLoops ) + + +def main(): + 'Display the raft dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/scale.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/scale.py new file mode 100644 index 0000000..d644079 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/scale.py @@ -0,0 +1,163 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Scale scales the carving to compensate for shrinkage after the extrusion has cooled. + +The scale manual page is at: + +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Scale + +It is best to only change the XY Plane Scale, because that does not affect other variables. If you choose to change the Z Axis Scale, that increases the layer height so you must increase the feed rate in speed by the same amount and maybe some other variables which depend on layer height. + +==Operation== +The default 'Activate Scale' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===XY Plane Scale=== +Default is 1.01. + +Defines the amount the xy plane of the carving will be scaled. The xy coordinates will be scaled, but the edge width is not changed, so this can be changed without affecting other variables. + +===Z Axis Scale=== +Default is one. + +Defines the amount the z axis of the carving will be scaled. The default is one because changing this changes many variables related to the layer height. For example, the feedRate should be multiplied by the Z Axis Scale because the layers would be farther apart. + +===SVG Viewer=== +Default is webbrowser. + +If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. + +==Examples== +The following examples scale the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and scale.py. + +> python scale.py +This brings up the scale dialog. + +> python scale.py Screw Holder Bottom.stl +The scale tool is parsing the file: +Screw Holder Bottom.stl +.. +The scale tool has created the file: +.. Screw Holder Bottom_scale.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from datetime import date +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.svg_reader import SVGReader +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from fabmetheus_utilities import svg_writer +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import cStringIO +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, svgText='', repository=None): + "Scale and convert an svg file or svgText." + return getCraftedTextFromText(fileName, archive.getTextIfEmpty(fileName, svgText), repository) + +def getCraftedTextFromText(fileName, svgText, repository=None): + "Scale and convert an svgText." + if gcodec.isProcedureDoneOrFileIsEmpty(svgText, 'scale'): + return svgText + if repository == None: + repository = settings.getReadRepository(ScaleRepository()) + if repository.activateScale.value: + return ScaleSkein().getCraftedGcode(fileName, repository, svgText) + return svgText + +def getNewRepository(): + 'Get new repository.' + return ScaleRepository() + +def setLoopLayerScale(loopLayer, xyPlaneScale, zAxisScale): + "Set the slice element scale." + for loop in loopLayer.loops: + for pointIndex in xrange(len(loop)): + loop[pointIndex] *= xyPlaneScale + loopLayer.z *= zAxisScale + +def writeOutput(fileName, shouldAnalyze=True): + 'Scale the carving.' + skeinforge_craft.writeSVGTextWithNounMessage(fileName, ScaleRepository(), shouldAnalyze) + + +class ScaleRepository: + "A class to handle the scale settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.scale.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Scale', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Scale') + self.activateScale = settings.BooleanSetting().getFromValue('Activate Scale', self, False) + self.xyPlaneScale = settings.FloatSpin().getFromValue(0.99, 'XY Plane Scale (ratio):', self, 1.03, 1.01) + self.zAxisScale = settings.FloatSpin().getFromValue(0.99, 'Z Axis Scale (ratio):', self, 1.02, 1.0) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + self.executeTitle = 'Scale' + + def execute(self): + "Scale button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class ScaleSkein: + "A class to scale a skein of extrusions." + def getCraftedGcode(self, fileName, repository, svgText): + "Parse svgText and store the scale svgText." + svgReader = SVGReader() + svgReader.parseSVG('', svgText) + if svgReader.sliceDictionary == None: + print('Warning, nothing will be done because the sliceDictionary could not be found getCraftedGcode in preface.') + return '' + xyPlaneScale = repository.xyPlaneScale.value + zAxisScale = repository.zAxisScale.value + decimalPlacesCarried = int(svgReader.sliceDictionary['decimalPlacesCarried']) + layerHeight = zAxisScale * float(svgReader.sliceDictionary['layerHeight']) + edgeWidth = float(svgReader.sliceDictionary['edgeWidth']) + loopLayers = svgReader.loopLayers + for loopLayer in loopLayers: + setLoopLayerScale(loopLayer, xyPlaneScale, zAxisScale) + cornerMaximum = Vector3(-912345678.0, -912345678.0, -912345678.0) + cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0) + svg_writer.setSVGCarvingCorners(cornerMaximum, cornerMinimum, layerHeight, loopLayers) + svgWriter = svg_writer.SVGWriter( + True, + cornerMaximum, + cornerMinimum, + decimalPlacesCarried, + layerHeight, + edgeWidth) + commentElement = svg_writer.getCommentElement(svgReader.documentElement) + procedureNameString = svgReader.sliceDictionary['procedureName'] + ',scale' + return svgWriter.getReplacedSVGTemplate(fileName, loopLayers, procedureNameString, commentElement) + + +def main(): + "Display the scale dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skin.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skin.py new file mode 100644 index 0000000..37e0841 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skin.py @@ -0,0 +1,392 @@ +""" +This page is in the table of contents. +Skin is a plugin to smooth the surface skin of an object by replacing the edge surface with a surface printed at a fraction of the carve +height. This gives the impression that the object was carved at a much thinner height giving a high-quality finish, but still prints +in a relatively short time. The latest process has some similarities with a description at: + +http://adventuresin3-dprinting.blogspot.com/2011/05/skinning.html + +The skin manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skin + +==Operation== +The default 'Activate Skin' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Division=== +====Horizontal Infill Divisions==== +Default: 2 + +Defines the number of times the skinned infill is divided horizontally. + +====Horizontal Perimeter Divisions==== +Default: 1 + +Defines the number of times the skinned edges are divided horizontally. + +====Vertical Divisions==== +Default: 2 + +Defines the number of times the skinned infill and edges are divided vertically. + +===Hop When Extruding Infill=== +Default is off. + +When selected, the extruder will hop before and after extruding the lower infill in order to avoid the regular thickness threads. + +===Layers From=== +Default: 1 + +Defines which layer of the print the skinning process starts from. It is not wise to set this to zero, skinning the bottom layer is likely to cause the bottom edge not to adhere well to the print surface. + +==Tips== +Due to the very small Z-axis moves skinning can generate as it prints the edge, it can cause the Z-axis speed to be limited by the Limit plug-in, if you have it enabled. This can cause some printers to pause excessively during each layer change. To overcome this, ensure that the Z-axis max speed in the Limit tool is set to an appropriate value for your printer, e.g. 10mm/s + +Since Skin prints a number of fractional-height edge layers for each layer, printing the edge last causes the print head to travel down from the current print height. Depending on the shape of your extruder nozzle, you may get higher quality prints if you print the edges first, so the print head always travels up. This is set via the Thread Sequence Choice setting in the Fill tool. + +==Examples== +The following examples skin the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and skin.py. + +> python skin.py +This brings up the skin dialog. + +> python skin.py Screw Holder Bottom.stl +The skin tool is parsing the file: +Screw Holder Bottom.stl +.. +The skin tool has created the file: +.. Screw Holder Bottom_skin.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique aht yahoo.com) & James Blackwell (jim_blag ahht hotmail.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, gcodeText, repository=None): + 'Skin a gcode linear move text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, gcodeText), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Skin a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'skin'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(SkinRepository()) + if not repository.activateSkin.value: + return gcodeText + return SkinSkein().getCraftedGcode(gcodeText, repository) + +def getIsMinimumSides(loops, sides=3): + 'Determine if all the loops have at least the given number of sides.' + for loop in loops: + if len(loop) < sides: + return False + return True + +def getNewRepository(): + 'Get new repository.' + return SkinRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Skin a gcode linear move file. Chain skin the gcode if it is not already skinned.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'skin', shouldAnalyze) + + +class SkinRepository: + 'A class to handle the skin settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.skin.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Skin', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skin') + self.activateSkin = settings.BooleanSetting().getFromValue('Activate Skin', self, False) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Division -', self) + self.horizontalInfillDivisions = settings.IntSpin().getSingleIncrementFromValue(1, 'Horizontal Infill Divisions (integer):', self, 3, 2) + self.horizontalPerimeterDivisions = settings.IntSpin().getSingleIncrementFromValue(1, 'Horizontal Perimeter Divisions (integer):', self, 3, 1) + self.verticalDivisions = settings.IntSpin().getSingleIncrementFromValue(1, 'Vertical Divisions (integer):', self, 3, 2) + settings.LabelSeparator().getFromRepository(self) + self.hopWhenExtrudingInfill = settings.BooleanSetting().getFromValue('Hop When Extruding Infill', self, False) + self.layersFrom = settings.IntSpin().getSingleIncrementFromValue(0, 'Layers From (index):', self, 912345678, 1) + self.executeTitle = 'Skin' + + def execute(self): + 'Skin button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class SkinSkein: + 'A class to skin a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.clipOverEdgeWidth = 0.0 + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edge = None + self.feedRateMinute = 959.0 + self.infill = None + self.infillBoundaries = None + self.infillBoundary = None + self.layerIndex = -1 + self.lineIndex = 0 + self.lines = None + self.maximumZFeedRateMinute = 60.0 + self.oldFlowRate = None + self.oldLocation = None + self.travelFeedRateMinute = 957.0 + + def addFlowRateLine(self, flowRate): + 'Add a flow rate line.' + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + + def addPerimeterLoop(self, thread, z): + 'Add the edge loop to the gcode.' + self.distanceFeedRate.addGcodeFromFeedRateThreadZ(self.feedRateMinute, thread, self.travelFeedRateMinute, z) + + def addSkinnedInfill(self): + 'Add skinned infill.' + if self.infillBoundaries == None: + return + bottomZ = self.oldLocation.z + self.layerHeight / self.verticalDivisionsFloat - self.layerHeight + offsetY = 0.5 * self.skinInfillWidth + self.addFlowRateLine(self.oldFlowRate / self.verticalDivisionsFloat / self.horizontalInfillDivisionsFloat) + for verticalDivisionIndex in xrange(self.verticalDivisions): + z = bottomZ + self.layerHeight / self.verticalDivisionsFloat * float(verticalDivisionIndex) + self.addSkinnedInfillBoundary(self.infillBoundaries, offsetY * (verticalDivisionIndex % 2 == 0), self.oldLocation.z, z) + self.addFlowRateLine(self.oldFlowRate) + self.infillBoundaries = None + + def addSkinnedInfillBoundary(self, infillBoundaries, offsetY, upperZ, z): + 'Add skinned infill boundary.' + arounds = [] + aroundWidth = 0.34321 * self.skinInfillInset + endpoints = [] + pixelTable = {} + rotatedLoops = [] + for infillBoundary in infillBoundaries: + infillBoundaryRotated = euclidean.getRotatedComplexes(self.reverseRotation, infillBoundary) + if offsetY != 0.0: + for infillPointRotatedIndex, infillPointRotated in enumerate(infillBoundaryRotated): + infillBoundaryRotated[infillPointRotatedIndex] = complex(infillPointRotated.real, infillPointRotated.imag - offsetY) + rotatedLoops.append(infillBoundaryRotated) + infillDictionary = triangle_mesh.getInfillDictionary( + arounds, aroundWidth, self.skinInfillInset, self.skinInfillWidth, pixelTable, rotatedLoops) + for infillDictionaryKey in infillDictionary.keys(): + xIntersections = infillDictionary[infillDictionaryKey] + xIntersections.sort() + for segment in euclidean.getSegmentsFromXIntersections(xIntersections, infillDictionaryKey * self.skinInfillWidth): + for endpoint in segment: + endpoint.point = complex(endpoint.point.real, endpoint.point.imag + offsetY) + endpoints.append(endpoint) + infillPaths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.skinInfillWidth, pixelTable, aroundWidth) + for infillPath in infillPaths: + addPointBeforeThread = True + infillRotated = euclidean.getRotatedComplexes(self.rotation, infillPath) + if upperZ > z and self.repository.hopWhenExtrudingInfill.value: + feedRateMinute = self.travelFeedRateMinute + infillRotatedFirst = infillRotated[0] + location = Vector3(infillRotatedFirst.real, infillRotatedFirst.imag, upperZ) + distance = abs(location - self.oldLocation) + if distance > 0.0: + deltaZ = abs(upperZ - self.oldLocation.z) + zFeedRateComponent = feedRateMinute * deltaZ / distance + if zFeedRateComponent > self.maximumZFeedRateMinute: + feedRateMinute *= self.maximumZFeedRateMinute / zFeedRateComponent + self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, infillRotatedFirst, upperZ) + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.maximumZFeedRateMinute, infillRotatedFirst, z) + addPointBeforeThread = False + if addPointBeforeThread: + self.distanceFeedRate.addGcodeMovementZ(infillRotated[0], z) + self.distanceFeedRate.addLine('M101') + for point in infillRotated[1 :]: + self.distanceFeedRate.addGcodeMovementZ(point, z) + self.distanceFeedRate.addLine('M103') + lastPointRotated = infillRotated[-1] + self.oldLocation = Vector3(lastPointRotated.real, lastPointRotated.imag, upperZ) + if upperZ > z and self.repository.hopWhenExtrudingInfill.value: + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.maximumZFeedRateMinute, lastPointRotated, upperZ) + + def addSkinnedPerimeter(self): + 'Add skinned edge.' + if self.edge == None: + return + bottomZ = self.oldLocation.z + self.layerHeight / self.verticalDivisionsFloat - self.layerHeight + edgeThread = self.edge[: -1] + edges = [] + radiusAddition = self.edgeWidth / self.horizontalPerimeterDivisionsFloat + radius = 0.5 * radiusAddition - self.halfEdgeWidth + for division in xrange(self.repository.horizontalPerimeterDivisions.value): + edges.append(self.getClippedSimplifiedLoopPathByLoop(intercircle.getLargestInsetLoopFromLoop(edgeThread, radius))) + radius += radiusAddition + skinnedPerimeterFlowRate = self.oldFlowRate / self.verticalDivisionsFloat + if getIsMinimumSides(edges): + self.addFlowRateLine(skinnedPerimeterFlowRate / self.horizontalPerimeterDivisionsFloat) + for verticalDivisionIndex in xrange(self.verticalDivisions): + z = bottomZ + self.layerHeight / self.verticalDivisionsFloat * float(verticalDivisionIndex) + for edge in edges: + self.addPerimeterLoop(edge, z) + else: + self.addFlowRateLine(skinnedPerimeterFlowRate) + for verticalDivisionIndex in xrange(self.verticalDivisions): + z = bottomZ + self.layerHeight / self.verticalDivisionsFloat * float(verticalDivisionIndex) + self.addPerimeterLoop(self.edge, z) + self.addFlowRateLine(self.oldFlowRate) + self.edge = None + + def getClippedSimplifiedLoopPathByLoop(self, loop): + 'Get clipped and simplified loop path from a loop.' + if len(loop) == 0: + return [] + loopPath = loop + [loop[0]] + return euclidean.getClippedSimplifiedLoopPath(self.clipLength, loopPath, self.halfEdgeWidth) + + def getCraftedGcode( self, gcodeText, repository ): + 'Parse gcode text and store the skin gcode.' + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.layersFromBottom = repository.layersFrom.value + self.horizontalInfillDivisionsFloat = float(repository.horizontalInfillDivisions.value) + self.horizontalPerimeterDivisionsFloat = float(repository.horizontalPerimeterDivisions.value) + self.verticalDivisions = max(repository.verticalDivisions.value, 1) + self.verticalDivisionsFloat = float(self.verticalDivisions) + self.parseInitialization() + self.clipLength = 0.5 * self.clipOverEdgeWidth * self.edgeWidth + self.skinInfillInset = 0.5 * (self.infillWidth + self.skinInfillWidth) * (1.0 - self.infillPerimeterOverlap) + self.parseBoundaries() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) + + def parseBoundaries(self): + 'Parse the boundaries and add them to the boundary layers.' + self.boundaryLayers = [] + self.layerIndexTop = -1 + boundaryLoop = None + boundaryLayer = None + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '()': + boundaryLoop = None + elif firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if boundaryLoop == None: + boundaryLoop = [] + boundaryLayer.loops.append(boundaryLoop) + boundaryLoop.append(location.dropAxis()) + elif firstWord == '(': + boundaryLayer = euclidean.LoopLayer(float(splitLine[1])) + self.boundaryLayers.append(boundaryLayer) + self.layerIndexTop += 1 + for boundaryLayerIndex, boundaryLayer in enumerate(self.boundaryLayers): + if len(boundaryLayer.loops) > 0: + self.layersFromBottom += boundaryLayerIndex + return + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + self.clipOverEdgeWidth = float(splitLine[1]) + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('skin') + return + elif firstWord == '(': + self.infillPerimeterOverlap = float(splitLine[1]) + elif firstWord == '(': + self.infillWidth = float(splitLine[1]) + self.skinInfillWidth = self.infillWidth / self.horizontalInfillDivisionsFloat + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.maximumZFeedRateMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.oldFlowRate = float(splitLine[1]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.halfEdgeWidth = 0.5 * self.edgeWidth + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the skin skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.infillBoundaries != None: + return + if self.edge != None: + self.edge.append(self.oldLocation.dropAxis()) + return + elif firstWord == '()': + if self.layerIndex >= self.layersFromBottom and self.layerIndex == self.layerIndexTop: + self.infillBoundaries = [] + elif firstWord == '()': + self.addSkinnedInfill() + elif firstWord == '()': + if self.infillBoundaries != None: + self.infillBoundary = [] + self.infillBoundaries.append(self.infillBoundary) + elif firstWord == '(': + if self.infillBoundaries != None: + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.infillBoundary.append(location.dropAxis()) + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'skin') + elif firstWord == 'M101' or firstWord == 'M103': + if self.infillBoundaries != None or self.edge != None: + return + elif firstWord == 'M108': + self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + elif firstWord == '(': + if self.layerIndex >= self.layersFromBottom: + self.edge = [] + elif firstWord == '(': + self.rotation = gcodec.getRotationBySplitLine(splitLine) + self.reverseRotation = complex(self.rotation.real, -self.rotation.imag) + elif firstWord == '()': + self.addSkinnedPerimeter() + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the skin dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py new file mode 100644 index 0000000..21bc7d9 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/skirt.py @@ -0,0 +1,349 @@ +""" +This page is in the table of contents. +Skirt is a plugin to give the extruder some extra time to begin extruding properly before beginning the object, and to put a baffle around the model in order to keep the extrusion warm. + +The skirt manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skirt + +It is loosely based on Lenbook's outline plugin: + +http://www.thingiverse.com/thing:4918 + +it is also loosely based on the outline that Nophead sometimes uses: + +http://hydraraptor.blogspot.com/2010/01/hot-metal-and-serendipity.html + +and also loosely based on the baffles that Nophead made to keep corners warm: + +http://hydraraptor.blogspot.com/2010/09/some-corners-like-it-hot.html + +If you want only an outline, set 'Layers To' to one. This gives the extruder some extra time to begin extruding properly before beginning your object, and gives you an early verification of where your object will be extruded. + +If you also want an insulating skirt around the entire object, set 'Layers To' to a huge number, like 912345678. This will additionally make an insulating baffle around the object; to prevent moving air from cooling the object, which increases warping, especially in corners. + +==Operation== +The default 'Activate Skirt' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Convex=== +Default is on. + +When selected, the skirt will be convex, going around the model with only convex angles. If convex is not selected, the skirt will hug the model, going into every nook and cranny. + +===Gap over Perimeter Width=== +Default is three. + +Defines the ratio of the gap between the object and the skirt over the edge width. If the ratio is too low, the skirt will connect to the object, if the ratio is too high, the skirt willl not provide much insulation for the object. + +===Layers To=== +Default is a one. + +Defines the number of layers of the skirt. If you want only an outline, set 'Layers To' to one. If you want an insulating skirt around the entire object, set 'Layers To' to a huge number, like 912345678. + +==Examples== +The following examples skirt the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and skirt.py. + +> python skirt.py +This brings up the skirt dialog. + +> python skirt.py Screw Holder Bottom.stl +The skirt tool is parsing the file: +Screw Holder Bottom.stl +.. +The skirt tool has created the file: +.. Screw Holder Bottom_skirt.gcode + +""" + + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text='', repository=None): + 'Skirt the fill file or text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Skirt the fill text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'skirt'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(SkirtRepository()) + if not repository.activateSkirt.value: + return gcodeText + return SkirtSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return SkirtRepository() + +def getOuterLoops(loops): + 'Get widdershins outer loops.' + outerLoops = [] + for loop in loops: + if not euclidean.isPathInsideLoops(outerLoops, loop): + outerLoops.append(loop) + intercircle.directLoops(True, outerLoops) + return outerLoops + +def writeOutput(fileName, shouldAnalyze=True): + 'Skirt a gcode linear move file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'skirt', shouldAnalyze) + + +class LoopCrossDictionary: + 'Loop with a horizontal and vertical dictionary.' + def __init__(self): + 'Initialize LoopCrossDictionary.' + self.loop = [] + + def __repr__(self): + 'Get the string representation of this LoopCrossDictionary.' + return str(self.loop) + + +class SkirtRepository: + 'A class to handle the skirt settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.skirt.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Skirt', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skirt') + self.activateSkirt = settings.BooleanSetting().getFromValue('Activate Skirt', self, False) + self.convex = settings.BooleanSetting().getFromValue('Convex:', self, True) + self.gapOverEdgeWidth = settings.FloatSpin().getFromValue(1.0, 'Gap over Perimeter Width (ratio):', self, 5.0, 3.0) + self.layersTo = settings.IntSpin().getSingleIncrementFromValue(0, 'Layers To (index):', self, 912345678, 1) + self.executeTitle = 'Skirt' + + def execute(self): + 'Skirt button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( + self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class SkirtSkein: + 'A class to skirt a skein of extrusions.' + def __init__(self): + 'Initialize variables.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 961.0 + self.isExtruderActive = False + self.isSupportLayer = False + self.layerIndex = -1 + self.lineIndex = 0 + self.lines = None + self.oldFlowRate = None + self.oldLocation = None + self.oldTemperatureInput = None + self.skirtFlowRate = None + self.skirtTemperature = None + self.travelFeedRateMinute = 957.0 + self.unifiedLoop = LoopCrossDictionary() + + def addFlowRate(self, flowRate): + 'Add a line of temperature if different.' + if flowRate != None: + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + + def addSkirt(self, z): + 'At skirt at z to gcode output.' + self.setSkirtFeedFlowTemperature() + self.distanceFeedRate.addLine('()') + oldTemperature = self.oldTemperatureInput + self.addTemperatureLineIfDifferent(self.skirtTemperature) + self.addFlowRate(self.skirtFlowRate) + for outsetLoop in self.outsetLoops: + closedLoop = outsetLoop + [outsetLoop[0]] + self.distanceFeedRate.addGcodeFromFeedRateThreadZ(self.feedRateMinute, closedLoop, self.travelFeedRateMinute, z) + self.addFlowRate(self.oldFlowRate) + self.addTemperatureLineIfDifferent(oldTemperature) + self.distanceFeedRate.addLine('()') + + def addTemperatureLineIfDifferent(self, temperature): + 'Add a line of temperature if different.' + if temperature == None or temperature == self.oldTemperatureInput: + return + self.distanceFeedRate.addLine('M104 S' + euclidean.getRoundedToThreePlaces(temperature)) + self.oldTemperatureInput = temperature + + def createSegmentDictionaries(self, loopCrossDictionary): + 'Create horizontal and vertical segment dictionaries.' + loopCrossDictionary.horizontalDictionary = self.getHorizontalXIntersectionsTable(loopCrossDictionary.loop) + flippedLoop = euclidean.getDiagonalFlippedLoop(loopCrossDictionary.loop) + loopCrossDictionary.verticalDictionary = self.getHorizontalXIntersectionsTable(flippedLoop) + + def createSkirtLoops(self): + 'Create the skirt loops.' + points = euclidean.getPointsByHorizontalDictionary(self.edgeWidth, self.unifiedLoop.horizontalDictionary) + points += euclidean.getPointsByVerticalDictionary(self.edgeWidth, self.unifiedLoop.verticalDictionary) + loops = triangle_mesh.getDescendingAreaOrientedLoops(points, points, 2.5 * self.edgeWidth) + outerLoops = getOuterLoops(loops) + outsetLoops = intercircle.getInsetSeparateLoopsFromLoops(outerLoops, -self.skirtOutset) + self.outsetLoops = getOuterLoops(outsetLoops) + if self.repository.convex.value: + self.outsetLoops = [euclidean.getLoopConvex(euclidean.getConcatenatedList(self.outsetLoops))] + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the skirt gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + self.parseBoundaries() + self.createSkirtLoops() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) + + def getHorizontalXIntersectionsTable(self, loop): + 'Get the horizontal x intersections table from the loop.' + horizontalXIntersectionsTable = {} + euclidean.addXIntersectionsFromLoopForTable(loop, horizontalXIntersectionsTable, self.edgeWidth) + return horizontalXIntersectionsTable + + def parseBoundaries(self): + 'Parse the boundaries and union them.' + self.createSegmentDictionaries(self.unifiedLoop) + if self.repository.layersTo.value < 1: + return + loopCrossDictionary = None + layerIndex = -1 + for line in self.lines[self.lineIndex :]: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == '()' or firstWord == '()': + self.createSegmentDictionaries(loopCrossDictionary) + self.unifyLayer(loopCrossDictionary) + loopCrossDictionary = None + elif firstWord == '(' or firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + if loopCrossDictionary == None: + loopCrossDictionary = LoopCrossDictionary() + loopCrossDictionary.loop.append(location.dropAxis()) + elif firstWord == '(': + layerIndex += 1 + if layerIndex > self.repository.layersTo.value: + return + settings.printProgress(layerIndex, 'skirt') + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('skirt') + return + elif firstWord == '(': + self.oldTemperatureInput = float(splitLine[1]) + self.skirtTemperature = self.oldTemperatureInput + elif firstWord == '(': + self.feedRateMinute = 60.0 * float(splitLine[1]) + elif firstWord == '(': + self.oldFlowRate = float(splitLine[1]) + self.skirtFlowRate = self.oldFlowRate + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.skirtOutset = (self.repository.gapOverEdgeWidth.value + 0.5) * self.edgeWidth + self.distanceFeedRate.addTagRoundedLine('skirtOutset', self.skirtOutset) + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the skirt skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()' or firstWord == '()' or firstWord == '(': + return + self.distanceFeedRate.addLine(line) + if firstWord == 'G1': + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + elif firstWord == '(': + self.layerIndex += 1 + if self.layerIndex < self.repository.layersTo.value: + self.addSkirt(float(splitLine[1])) + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + elif firstWord == 'M104': + self.oldTemperatureInput = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + self.skirtTemperature = self.oldTemperatureInput + elif firstWord == 'M108': + self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + self.skirtFlowRate = self.oldFlowRate + elif firstWord == '()': + self.isSupportLayer = True + elif firstWord == '()': + self.isSupportLayer = False + + def setSkirtFeedFlowTemperature(self): + 'Set the skirt feed rate, flow rate and temperature to that of the next extrusion.' + isExtruderActive = self.isExtruderActive + isSupportLayer = self.isSupportLayer + for lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + if isExtruderActive: + if not isSupportLayer: + return + elif firstWord == 'M101': + isExtruderActive = True + elif firstWord == 'M103': + isExtruderActive = False + elif firstWord == 'M104': + self.skirtTemperature = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + elif firstWord == 'M108': + self.skirtFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1]) + elif firstWord == '()': + isSupportLayer = True + elif firstWord == '()': + isSupportLayer = False + + def unifyLayer(self, loopCrossDictionary): + 'Union the loopCrossDictionary with the unifiedLoop.' + euclidean.joinXIntersectionsTables(loopCrossDictionary.horizontalDictionary, self.unifiedLoop.horizontalDictionary) + euclidean.joinXIntersectionsTables(loopCrossDictionary.verticalDictionary, self.unifiedLoop.verticalDictionary) + + +def main(): + 'Display the skirt dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/smooth.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/smooth.py new file mode 100644 index 0000000..0a386e3 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/smooth.py @@ -0,0 +1,241 @@ +""" +This page is in the table of contents. +This plugin smooths jagged extruder paths. It takes shortcuts through jagged paths and decreases the feed rate to compensate. + +Smooth is based on ideas in Nophead's frequency limit post: + +http://hydraraptor.blogspot.com/2010/12/frequency-limit.html + +The smooth manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Smooth + +==Operation== +The default 'Activate Smooth' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Layers From=== +Default: 1 + +Defines which layer of the print the smoothing process starts from. If this is set this to zero, that might cause the smoothed parts of the bottom edge not to adhere well to the print surface. However, this is just a potential problem in theory, no bottom adhesion problem has been reported. + +===Maximum Shortening over Width=== +Default: 1.2 + +Defines the maximum shortening of the shortcut compared to the original path. Smooth goes over the path and if the shortcut between the midpoint of one line and the midpoint of the second line after is not too short compared to the original and the shortcut is not too long, it replaces the jagged original with the shortcut. If the maximum shortening is too much, smooth will shorten paths which should not of been shortened and will leave blobs and holes in the model. If the maximum shortening is too little, even jagged paths that could be shortened safely won't be smoothed. + +==Examples== +The following examples smooth the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and smooth.py. + +> python smooth.py +This brings up the smooth dialog. + +> python smooth.py Screw Holder Bottom.stl +The smooth tool is parsing the file: +Screw Holder Bottom.stl +.. +The smooth tool has created the file: +.. Screw Holder Bottom_smooth.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique aht yahoo.com) & James Blackwell (jim_blag ahht hotmail.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, gcodeText, repository=None): + 'Smooth a gcode linear move text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, gcodeText), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Smooth a gcode linear move text.' + if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'smooth'): + return gcodeText + if repository == None: + repository = settings.getReadRepository(SmoothRepository()) + if not repository.activateSmooth.value: + return gcodeText + return SmoothSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return SmoothRepository() + +def writeOutput(fileName, shouldAnalyze=True): + 'Smooth a gcode linear move file. Chain smooth the gcode if it is not already smoothed.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'smooth', shouldAnalyze) + + +class SmoothRepository: + 'A class to handle the smooth settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.smooth.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Smooth', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Smooth') + self.activateSmooth = settings.BooleanSetting().getFromValue('Activate Smooth', self, False) + self.layersFrom = settings.IntSpin().getSingleIncrementFromValue(0, 'Layers From (index):', self, 912345678, 1) + self.maximumShorteningOverWidth = settings.FloatSpin().getFromValue(0.2, 'Maximum Shortening over Width (float):', self, 2.0, 1.2) + self.executeTitle = 'Smooth' + + def execute(self): + 'Smooth button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class SmoothSkein: + 'A class to smooth a skein of extrusions.' + def __init__(self): + 'Initialize.' + self.boundaryLayerIndex = -1 + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 959.0 + self.infill = None + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.travelFeedRateMinute = 957.0 + + def addSmoothedInfill(self): + 'Add smoothed infill.' + if len(self.infill) < 4: + self.distanceFeedRate.addGcodeFromFeedRateThreadZ(self.feedRateMinute, self.infill, self.travelFeedRateMinute, self.oldLocation.z) + return + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.travelFeedRateMinute, self.infill[0], self.oldLocation.z) + self.distanceFeedRate.addLine('M101') + lengthMinusOne = len(self.infill) - 1 + lengthMinusTwo = lengthMinusOne - 1 + wasOriginalPoint = True + pointIndex = 0 + while pointIndex < lengthMinusOne: + nextPoint = self.infill[pointIndex + 1] + afterNextIndex = pointIndex + 2 + if afterNextIndex < lengthMinusTwo: + point = self.infill[pointIndex] + midpoint = 0.5 * (point + nextPoint) + afterNextPoint = self.infill[afterNextIndex] + afterNextNextPoint = self.infill[afterNextIndex + 1] + afterNextMidpoint = 0.5 * (afterNextPoint + afterNextNextPoint) + shortcutDistance = abs(afterNextMidpoint - midpoint) + originalDistance = abs(midpoint - point) + abs(afterNextPoint - nextPoint) + abs(afterNextMidpoint - afterNextPoint) + segment = euclidean.getNormalized(nextPoint - point) + afterNextSegment = euclidean.getNormalized(afterNextNextPoint - afterNextPoint) + sameDirection = self.getIsParallelToRotation(segment) and self.getIsParallelToRotation(afterNextSegment) + if originalDistance - shortcutDistance < self.maximumShortening and shortcutDistance < self.maximumDistance and sameDirection: + if wasOriginalPoint: + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, midpoint, self.oldLocation.z) + feedrate = self.feedRateMinute + if originalDistance != 0.0: + feedrate *= shortcutDistance / originalDistance + self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedrate, afterNextMidpoint, self.oldLocation.z) + wasOriginalPoint = False + pointIndex += 1 + else: + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, nextPoint, self.oldLocation.z) + wasOriginalPoint = True + else: + self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, nextPoint, self.oldLocation.z) + wasOriginalPoint = True + pointIndex += 1 + self.distanceFeedRate.addLine('M103') + + def getCraftedGcode( self, gcodeText, repository ): + 'Parse gcode text and store the smooth gcode.' + self.lines = archive.getTextLines(gcodeText) + self.repository = repository + self.layersFromBottom = repository.layersFrom.value + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getIsParallelToRotation(self, segment): + 'Determine if the segment is parallel to the rotation.' + return abs(euclidean.getDotProduct(segment, self.rotation)) > 0.99999 + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('smooth') + return + elif firstWord == '(': + self.infillWidth = float(splitLine[1]) + self.maximumShortening = self.repository.maximumShorteningOverWidth.value * self.infillWidth + self.maximumDistance = 1.5 * self.maximumShortening + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the smooth skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()': + if self.boundaryLayerIndex < 0: + self.boundaryLayerIndex = 0 + elif firstWord == 'G1': + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation = location + if self.infill != None: + self.infill.append(location.dropAxis()) + return + elif firstWord == '()': + if self.boundaryLayerIndex >= self.layersFromBottom: + self.infill = [] + elif firstWord == '()': + self.infill = None + elif firstWord == '(': + self.layerCount.printProgressIncrement('smooth') + if self.boundaryLayerIndex >= 0: + self.boundaryLayerIndex += 1 + elif firstWord == 'M101': + if self.infill != None: + if len(self.infill) > 1: + self.infill = [self.infill[0]] + return + elif firstWord == 'M103': + if self.infill != None: + self.addSmoothedInfill() + self.infill = [] + return + elif firstWord == '(': + self.rotation = gcodec.getRotationBySplitLine(splitLine) + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the smooth dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/speed.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/speed.py new file mode 100644 index 0000000..370a315 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/speed.py @@ -0,0 +1,356 @@ +""" +This page is in the table of contents. +Speed is a plugin to set the feed rate and flow rate. + +The speed manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Speed + +==Operation== +The default 'Activate Speed' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Add Flow Rate=== +Default is on. + +When selected, the flow rate will be added to the gcode. + +===Bridge=== +====Bridge Feed Rate Multiplier==== +Default is one. + +Defines the ratio of the feed rate (head speed) on the bridge layers over the feed rate of the typical non bridge layers. + +====Bridge Flow Rate Multiplier==== +Default is one. + +Defines the ratio of the flow rate (extruder speed) on the bridge layers over the flow rate of the typical non bridge layers. + +===Duty Cyle=== +====Duty Cyle at Beginning==== +Default is one, which will set the extruder motor to full current. + +Defines the duty cycle of the stepper motor pulse width modulation by adding an M113 command toward the beginning of the gcode text. If the hardware has the option of using a potentiometer to set the duty cycle, to select the potentiometer option set 'Duty Cyle at Beginning' to an empty string. To turn off the extruder, set the 'Duty Cyle at Beginning' to zero. + +====Duty Cyle at Ending==== +Default is zero, which will turn off the extruder motor. + +Defines the duty cycle of the stepper motor pulse width modulation by adding an M113 command toward the ending of the gcode text. If the hardware has the option of using a potentiometer to set the duty cycle, to select the potentiometer option set 'Duty Cyle at Beginning' to an empty string. To turn off the extruder, set the 'Duty Cyle at Ending' to zero. + +===Feed Rate=== +Default is sixteen millimeters per second. + +Defines the operating feed rate, the speed your printing head moves in XY plane, before any modifiers. + +===Flow Rate Setting=== +Default is 210. + +Defines the operating flow rate. + +RapMan uses this parameter to define the RPM of the extruder motor. The extruder motor RPM is flow rate / 10 so if your flow rate is 150.0 that will set the extruder stepper to run at 15 RPM, different printers might read this value differently. + +===Maximum Z Feed Rate=== +Default is one millimeter per second. + +Defines the speed of a vertical hop, like the infill hop in skin. Also, if the Limit plugin is activated, it will limit the maximum speed of the tool head in the z direction to this value. + +===Object First Layer=== + +====Object First Layer Feed Rate Infill Multiplier==== +Default is 0.4. + +Defines the object first layer infill feed rate multiplier. The greater the 'Object First Layer Feed Rate Infill Multiplier, the thinner the infill, the lower the 'Object First Layer Feed Rate Infill Multiplier', the thicker the infill. + +====Object First Layer Feed Rate Perimeter Multiplier==== +Default is 0.4. + +Defines the object first layer edge feed rate multiplier. The greater the 'Object First Layer Feed Rate Perimeter Multiplier, the thinner the edge, the lower the 'Object First Layer Feed Rate Perimeter Multiplier', the thicker the edge. + +====Object First Layer Flow Rate Infill Multiplier==== +Default is 0.4. + +Defines the object first layer infill flow rate multiplier. The greater the 'Object First Layer Flow Rate Infill Multiplier', the thicker the infill, the lower the 'Object First Layer Flow Rate Infill Multiplier, the thinner the infill. + +====Object First Layer Flow Rate Perimeter Multiplier==== +Default is 0.4. + +Defines the object first layer edge flow rate multiplier. The greater the 'Object First Layer Flow Rate Perimeter Multiplier', the thicker the edge, the lower the 'Object First Layer Flow Rate Perimeter Multiplier, the thinner the edge. + +===Orbital Feed Rate over Operating Feed Rate=== +Default is 0.5. + +Defines the speed when the head is orbiting compared to the operating extruder speed. If you want the orbit to be very short, set the "Orbital Feed Rate over Operating Feed Rate" setting to a low value like 0.1. + +===Perimeter=== +To have higher build quality on the outside at the expense of slower build speed, a typical setting for the 'Perimeter Feed Rate over Operating Feed Rate' would be 0.5. To go along with that, if you are using a speed controlled extruder like a stepper extruder, the 'Perimeter Flow Rate over Operating Flow Rate' should also be 0.5. + +A stepper motor is the best way of driving the extruder; however, if you are stuck with a DC motor extruder using Pulse Width Modulation to control the speed, then you'll probably need a slightly higher ratio because there is a minimum voltage 'Flow Rate PWM Setting' required for the extruder motor to turn. The flow rate PWM ratio would be determined by trial and error, with the first trial being: +Perimeter Flow Rate over Operating Flow Rate ~ Perimeter Feed Rate over Operating Feed Rate * (Flow Rate PWM Setting - Minimum Flow Rate PWM Setting) + Minimum Flow Rate PWM Setting + +====Perimeter Feed Rate Multiplier==== +Default: 1.0 + +Defines the ratio of the feed rate of the edge (outside shell) over the feed rate of the infill. If you for example set this to 0.8 you will have a "stronger" outside edge than inside extrusion as the outside edge will be printed slower hence better lamination will occur and more filament will be placed there. + +====Perimeter Flow Rate Multiplier==== +Default: 1.0 + +Defines the ratio of the flow rate of the edge (outside shell) over the flow rate of the infill. If you want the same thickness of the edge but better lamination you need to compensate for the slower feed rate by slowing down the flow rate, but all combinations are possible for different results. + +===Travel Feed Rate=== +Default is sixteen millimeters per second. + +Defines the feed rate when the extruder is off (not printing). The 'Travel Feed Rate' could be set as high as the extruder can be moved, it is not limited by the maximum extrusion rate. + +==Examples== +The following examples speed the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and speed.py. + +> python speed.py +This brings up the speed dialog. + +> python speed.py Screw Holder Bottom.stl +The speed tool is parsing the file: +Screw Holder Bottom.stl +.. +The speed tool has created the file: +.. Screw Holder Bottom_speed.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', repository=None): + "Speed the file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Speed a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'speed'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( SpeedRepository() ) + if not repository.activateSpeed.value: + return gcodeText + return SpeedSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return SpeedRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Speed a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'speed', shouldAnalyze) + + +class SpeedRepository: + "A class to handle the speed settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.speed.html', self ) + self.baseNameSynonymDictionary = { + 'Object First Layer Feed Rate Infill Multiplier (ratio):' : 'raft.csv', + 'Object First Layer Feed Rate Perimeter Multiplier (ratio):' : 'raft.csv', + 'Object First Layer Flow Rate Infill Multiplier (ratio):' : 'raft.csv', + 'Object First Layer Flow Rate Perimeter Multiplier (ratio):' : 'raft.csv'} + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Speed', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Speed') + self.activateSpeed = settings.BooleanSetting().getFromValue('Activate Speed', self, True ) + self.addFlowRate = settings.BooleanSetting().getFromValue('Add Flow Rate:', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Bridge -', self ) + self.bridgeFeedRateMultiplier = settings.FloatSpin().getFromValue( 0.8, 'Bridge Feed Rate Multiplier (ratio):', self, 1.2, 1.0 ) + self.bridgeFlowRateMultiplier = settings.FloatSpin().getFromValue( 0.8, 'Bridge Flow Rate Multiplier (ratio):', self, 1.2, 1.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Duty Cyle -', self ) + self.dutyCycleAtBeginning = settings.FloatSpin().getFromValue( 0.0, 'Duty Cyle at Beginning (portion):', self, 1.0, 1.0 ) + self.dutyCycleAtEnding = settings.FloatSpin().getFromValue( 0.0, 'Duty Cyle at Ending (portion):', self, 1.0, 0.0 ) + settings.LabelSeparator().getFromRepository(self) + self.feedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Feed Rate (mm/s):', self, 250.0, 50.0 ) + self.flowRateSetting = settings.FloatSpin().getFromValue( 50.0, 'Flow Rate Setting (float):', self, 250.0, 50.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Object First Layers -', self) + self.objectFirstLayerFeedRateInfillMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Feed Rate Infill Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFeedRatePerimeterMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Feed Rate Perimeter Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFeedRateTravelMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Feed Rate Travel Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFlowRateInfillMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Flow Rate Infill Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFlowRatePerimeterMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Flow Rate Perimeter Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayersLayerAmount = settings.IntSpin().getFromValue( + 1, 'Object First Layers Amount Of Layers For Speed Change:', self, 10, 3) + settings.LabelSeparator().getFromRepository(self) + self.orbitalFeedRateOverOperatingFeedRate = settings.FloatSpin().getFromValue( 0.1, 'Orbital Feed Rate over Operating Feed Rate (ratio):', self, 0.9, 0.5 ) + self.maximumZFeedRatePerSecond = settings.FloatSpin().getFromValue(0.5, 'Maximum Z Feed Rate (mm/s):', self, 10.0, 1.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Perimeter -', self ) + self.perimeterFeedRateMultiplier = settings.FloatSpin().getFromValue(0.5, 'Perimeter Feed Rate Multiplier (ratio):', self, 1.0, 1.0) + self.perimeterFlowRateMultiplier = settings.FloatSpin().getFromValue(0.5, 'Perimeter Flow Rate Multiplier (ratio):', self, 1.0, 1.0) + settings.LabelSeparator().getFromRepository(self) + self.travelFeedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Travel Feed Rate (mm/s):', self, 350.0, 250.0 ) + self.executeTitle = 'Speed' + + def execute(self): + "Speed button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class SpeedSkein: + "A class to speed a skein of extrusions." + def __init__(self): + 'Initialize.' + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRatePerSecond = 16.0 + self.isBridgeLayer = False + self.isEdgePath = False + self.isExtruderActive = False + self.layerIndex = -1 + self.lineIndex = 0 + self.lines = None + self.oldFlowRate = None + + def addFlowRateLine(self): + "Add flow rate line." + if not self.repository.addFlowRate.value: + return + flowRate = self.repository.flowRateSetting.value + if self.isBridgeLayer: + flowRate *= self.repository.bridgeFlowRateMultiplier.value + if self.isEdgePath: + flowRate *= self.repository.perimeterFlowRateMultiplier.value + if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: + if self.isEdgePath: + flowRate *= ((self.repository.objectFirstLayerFlowRatePerimeterMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + else: + flowRate *= ((self.repository.objectFirstLayerFlowRateInfillMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + if flowRate != self.oldFlowRate: + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + self.oldFlowRate = flowRate + + def addParameterString( self, firstWord, parameterWord ): + "Add parameter string." + if parameterWord == '': + self.distanceFeedRate.addLine(firstWord) + return + self.distanceFeedRate.addParameter( firstWord, parameterWord ) + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the speed gcode." + self.repository = repository + self.feedRatePerSecond = repository.feedRatePerSecond.value + self.travelFeedRateMinute = 60.0 * self.repository.travelFeedRatePerSecond.value + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + self.addParameterString('M113', self.repository.dutyCycleAtEnding.value ) # Set duty cycle . + return self.distanceFeedRate.output.getvalue() + + def getSpeededLine(self, line, splitLine): + 'Get gcode line with feed rate.' + if gcodec.getIndexOfStartingWithSecond('F', splitLine) > 0: + return line + feedRateMinute = 60.0 * self.feedRatePerSecond + if self.isBridgeLayer: + feedRateMinute *= self.repository.bridgeFeedRateMultiplier.value + if self.isEdgePath: + feedRateMinute *= self.repository.perimeterFeedRateMultiplier.value + if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: + if self.isEdgePath: + feedRateMinute *= ((self.repository.objectFirstLayerFeedRatePerimeterMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + else: + feedRateMinute *= ((self.repository.objectFirstLayerFeedRateInfillMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + self.addFlowRateLine() + if not self.isExtruderActive: + feedRateMinute = self.travelFeedRateMinute + if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: + feedRateMinute *= ((self.repository.objectFirstLayerFeedRateTravelMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + return self.distanceFeedRate.getLineWithFeedRate(feedRateMinute, line, splitLine) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('speed') + return + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + self.distanceFeedRate.addTagBracketedLine('maximumZFeedRatePerSecond', self.repository.maximumZFeedRatePerSecond.value ) + self.distanceFeedRate.addTagBracketedLine('objectFirstLayerFeedRateInfillMultiplier', self.repository.objectFirstLayerFeedRateInfillMultiplier.value) + self.distanceFeedRate.addTagBracketedLine('operatingFeedRatePerSecond', self.feedRatePerSecond ) + if self.repository.addFlowRate.value: + self.distanceFeedRate.addTagBracketedLine('objectFirstLayerFlowRateInfillMultiplier', self.repository.objectFirstLayerFlowRateInfillMultiplier.value) + self.distanceFeedRate.addTagBracketedLine('operatingFlowRate', self.repository.flowRateSetting.value ) + orbitalFeedRatePerSecond = self.feedRatePerSecond * self.repository.orbitalFeedRateOverOperatingFeedRate.value + self.distanceFeedRate.addTagBracketedLine('orbitalFeedRatePerSecond', orbitalFeedRatePerSecond ) + self.distanceFeedRate.addTagBracketedLine('travelFeedRatePerSecond', self.repository.travelFeedRatePerSecond.value ) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the speed skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '()': + self.distanceFeedRate.addLine(line) + self.addParameterString('M113', self.repository.dutyCycleAtBeginning.value ) # Set duty cycle . + return + elif firstWord == 'G1': + line = self.getSpeededLine(line, splitLine) + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + elif firstWord == '(': + self.isBridgeLayer = True + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'speed') + self.isBridgeLayer = False + self.addFlowRateLine() + elif firstWord == '(' or firstWord == '()': + self.isEdgePath = True + elif firstWord == '()' or firstWord == '()': + self.isEdgePath = False + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the speed dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/splodge.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/splodge.py new file mode 100644 index 0000000..7404029 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/splodge.py @@ -0,0 +1,327 @@ +""" +This page is in the table of contents. +Splodge turns the extruder on just before the start of a thread. This is to give the extrusion a bit anchoring at the beginning. + +The splodge manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Splodge + +==Operation== +The default 'Activate Splodge' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Initial=== +====Initial Lift over Extra Thickness==== +Default is one. + +Defines the amount the extruder will be lifted over the extra thickness of the initial splodge thread. The higher the ratio, the more the extruder will be lifted over the splodge, if the ratio is too low the extruder might plow through the splodge extrusion. + +====Initial Splodge Feed Rate==== +Default is one millimeter per second. + +Defines the feed rate at which the initial extra extrusion will be added. With the default feed rate, the splodge will be added slower so it will be thicker than the regular extrusion. + +====Initial Splodge Quantity Length==== +Default is thirty millimeters. + +Defines the quantity length of extra extrusion at the operating feed rate that will be added to the initial thread. If a splodge quantity length is smaller than 0.1 times the edge width, no splodge of that type will be added. + +===Operating=== +====Operating Lift over Extra Thickness==== +Default is one. + +Defines the amount the extruder will be lifted over the extra thickness of the operating splodge thread. + +====Operating Splodge Feed Rate==== +Default is one millimeter per second. + +Defines the feed rate at which the next extra extrusions will be added. + +====Operating Splodge Quantity Length==== +Default is thirty millimeters. + +Defines the quantity length of extra extrusion at the operating feed rate that will be added for the next threads. + +==Examples== +The following examples splodge the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and splodge.py. + +> python splodge.py +This brings up the splodge dialog. + +> python splodge.py Screw Holder Bottom.stl +The splodge tool is parsing the file: +Screw Holder Bottom.stl +.. +The splodge tool has created the file: +.. Screw Holder Bottom_splodge.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, splodgeRepository = None ): + "Splodge a gcode linear move file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), splodgeRepository ) + +def getCraftedTextFromText( gcodeText, splodgeRepository = None ): + "Splodge a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'splodge'): + return gcodeText + if splodgeRepository == None: + splodgeRepository = settings.getReadRepository( SplodgeRepository() ) + if not splodgeRepository.activateSplodge.value: + return gcodeText + return SplodgeSkein().getCraftedGcode( gcodeText, splodgeRepository ) + +def getNewRepository(): + 'Get new repository.' + return SplodgeRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Splodge a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'splodge', shouldAnalyze) + + +class SplodgeRepository: + "A class to handle the splodge settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.splodge.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Splodge', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Splodge') + self.activateSplodge = settings.BooleanSetting().getFromValue('Activate Splodge', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Initial -', self ) + self.initialLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Initial Lift over Extra Thickness (ratio):', self, 1.5, 1.0 ) + self.initialSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Initial Splodge Feed Rate (mm/s):', self, 2.4, 1.0 ) + self.initialSplodgeQuantityLength = settings.FloatSpin().getFromValue( 10.0, 'Initial Splodge Quantity Length (millimeters):', self, 90.0, 30.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Operating -', self ) + self.operatingLiftOverExtraThickness = settings.FloatSpin().getFromValue( 0.5, 'Operating Lift over Extra Thickness (ratio):', self, 1.5, 1.0 ) + self.operatingSplodgeFeedRate = settings.FloatSpin().getFromValue( 0.4, 'Operating Splodge Feed Rate (mm/s):', self, 2.4, 1.0 ) + self.operatingSplodgeQuantityLength = settings.FloatSpin().getFromValue(0.4, 'Operating Splodge Quantity Length (millimeters):', self, 2.4, 1.0) + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Splodge' + + def execute(self): + "Splodge button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class SplodgeSkein: + "A class to splodge a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 961.0 + self.isExtruderActive = False + self.hasInitialSplodgeBeenAdded = False + self.isLastExtruderCommandActivate = False + self.lastLineOutput = None + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.operatingFeedRatePerSecond = 15.0 + + def addLineUnlessIdentical(self, line): + "Add a line, unless it is identical to the last line." + if line == self.lastLineOutput: + return + self.lastLineOutput = line + self.distanceFeedRate.addLine(line) + + def addLineUnlessIdenticalReactivate(self, line): + "Add a line, unless it is identical to the last line or another M101." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'M101': + if not self.isLastExtruderCommandActivate: + self.addLineUnlessIdentical(line) + self.isLastExtruderCommandActivate = True + return + if firstWord == 'M103': + self.isLastExtruderCommandActivate = False + self.addLineUnlessIdentical(line) + + def getCraftedGcode( self, gcodeText, splodgeRepository ): + "Parse gcode text and store the splodge gcode." + self.lines = archive.getTextLines(gcodeText) + self.setRotations() + self.splodgeRepository = splodgeRepository + self.parseInitialization( splodgeRepository ) + self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines( self.lines[self.lineIndex :], 0.5 * self.edgeWidth ) + self.initialSplodgeFeedRateMinute = 60.0 * splodgeRepository.initialSplodgeFeedRate.value + self.initialStartupDistance = splodgeRepository.initialSplodgeQuantityLength.value * splodgeRepository.initialSplodgeFeedRate.value / self.operatingFeedRatePerSecond + self.operatingSplodgeFeedRateMinute = 60.0 * splodgeRepository.operatingSplodgeFeedRate.value + self.operatingStartupDistance = splodgeRepository.operatingSplodgeQuantityLength.value * splodgeRepository.operatingSplodgeFeedRate.value / self.operatingFeedRatePerSecond + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getInitialSplodgeLine( self, line, location ): + "Add the initial splodge line." + if not self.isJustBeforeExtrusion(): + return line + self.hasInitialSplodgeBeenAdded = True + if self.splodgeRepository.initialSplodgeQuantityLength.value < self.minimumQuantityLength: + return line + return self.getSplodgeLineGivenDistance( self.initialSplodgeFeedRateMinute, line, self.splodgeRepository.initialLiftOverExtraThickness.value, location, self.initialStartupDistance ) + + def getNextActiveLocationComplex(self): + "Get the next active line." + isActive = False + for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'M101': + isActive = True + if firstWord == 'G1' and isActive: + return gcodec.getLocationFromSplitLine(self.oldLocation, splitLine).dropAxis() + return None + + def getOperatingSplodgeLine( self, line, location ): + "Get the operating splodge line." + if not self.isJustBeforeExtrusion(): + return line + if self.splodgeRepository.operatingSplodgeQuantityLength.value < self.minimumQuantityLength: + return line + return self.getSplodgeLineGivenDistance( self.operatingSplodgeFeedRateMinute, line, self.splodgeRepository.operatingLiftOverExtraThickness.value, location, self.operatingStartupDistance ) + + def getSplodgeLine(self, line, location, splitLine): + "Get splodged gcode line." + self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) + if self.hasInitialSplodgeBeenAdded: + return self.getOperatingSplodgeLine(line, location) + return self.getInitialSplodgeLine(line, location) + + def getSplodgeLineGivenDistance( self, feedRateMinute, line, liftOverExtraThickness, location, startupDistance ): + "Add the splodge line." + locationComplex = location.dropAxis() + relativeStartComplex = None + nextLocationComplex = self.getNextActiveLocationComplex() + if nextLocationComplex != None: + if nextLocationComplex != locationComplex: + relativeStartComplex = locationComplex - nextLocationComplex + if relativeStartComplex == None: + relativeStartComplex = complex( 19.9, 9.9 ) + if self.oldLocation != None: + oldLocationComplex = self.oldLocation.dropAxis() + if oldLocationComplex != locationComplex: + relativeStartComplex = oldLocationComplex - locationComplex + relativeStartComplex *= startupDistance / abs( relativeStartComplex ) + startComplex = self.getStartInsideBoundingRectangle( locationComplex, relativeStartComplex ) + feedRateMultiplier = feedRateMinute / self.operatingFeedRatePerSecond / 60.0 + splodgeLayerThickness = self.layerHeight / math.sqrt( feedRateMultiplier ) + extraLayerThickness = splodgeLayerThickness - self.layerHeight + lift = extraLayerThickness * liftOverExtraThickness + startLine = self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.feedRateMinute, startComplex, location.z + lift ) + self.addLineUnlessIdenticalReactivate( startLine ) + self.addLineUnlessIdenticalReactivate('M101') + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + lineLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.distanceFeedRate.addGcodeMovementZWithFeedRate( feedRateMinute, locationComplex, lineLocation.z + lift ) + return '' + + def getStartInsideBoundingRectangle( self, locationComplex, relativeStartComplex ): + "Get a start inside the bounding rectangle." + startComplex = locationComplex + relativeStartComplex + if self.boundingRectangle.isPointInside( startComplex ): + return startComplex + for rotation in self.rotations: + rotatedRelativeStartComplex = relativeStartComplex * rotation + startComplex = locationComplex + rotatedRelativeStartComplex + if self.boundingRectangle.isPointInside( startComplex ): + return startComplex + return startComplex + + def isJustBeforeExtrusion(self): + "Determine if activate command is before linear move command." + for lineIndex in xrange(self.lineIndex + 1, len(self.lines)): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1' or firstWord == 'M103': + return False + if firstWord == 'M101': + return True + return False + + def parseInitialization( self, splodgeRepository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.addLineUnlessIdenticalReactivate(gcodec.getTagBracketedProcedure('splodge')) + return + elif firstWord == '(': + self.layerHeight = float(splitLine[1]) + elif firstWord == '(': + self.operatingFeedRatePerSecond = float(splitLine[1]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.minimumQuantityLength = 0.1 * self.edgeWidth + self.addLineUnlessIdenticalReactivate(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + line = self.getSplodgeLine(line, location, splitLine) + self.oldLocation = location + elif firstWord == 'M101': + self.isExtruderActive = True + elif firstWord == 'M103': + self.isExtruderActive = False + self.addLineUnlessIdenticalReactivate(line) + + def setRotations(self): + "Set the rotations." + self.rootHalf = math.sqrt( 0.5 ) + self.rotations = [] + self.rotations.append( complex( self.rootHalf, self.rootHalf ) ) + self.rotations.append( complex( self.rootHalf, - self.rootHalf ) ) + self.rotations.append( complex( 0.0, 1.0 ) ) + self.rotations.append( complex(0.0, -1.0) ) + self.rotations.append( complex( - self.rootHalf, self.rootHalf ) ) + self.rotations.append( complex( - self.rootHalf, - self.rootHalf ) ) + + +def main(): + "Display the splodge dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/stretch.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/stretch.py new file mode 100644 index 0000000..cb82ed3 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/stretch.py @@ -0,0 +1,427 @@ +""" +This page is in the table of contents. +Stretch is very important Skeinforge plugin that allows you to partially compensate for the fact that extruded holes are smaller then they should be. It stretches the threads to partially compensate for filament shrinkage when extruded. + +The stretch manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Stretch + +Extruded holes are smaller than the model because while printing an arc the head is depositing filament on both sides of the arc but in the inside of the arc you actually need less material then on the outside of the arc. You can read more about this on the RepRap ArcCompensation page: +http://reprap.org/bin/view/Main/ArcCompensation + +In general, stretch will widen holes and push corners out. In practice the filament contraction will not be identical to the algorithm, so even once the optimal parameters are determined, the stretch script will not be able to eliminate the inaccuracies caused by contraction, but it should reduce them. + +All the defaults assume that the thread sequence choice setting in fill is the edge being extruded first, then the loops, then the infill. If the thread sequence choice is different, the optimal thread parameters will also be different. In general, if the infill is extruded first, the infill would have to be stretched more so that even after the filament shrinkage, it would still be long enough to connect to the loop or edge. + +Holes should be made with the correct area for their radius. In other words, for example if your modeling program approximates a hole of radius one (area = pi) by making a square with the points at [(1,0), (0,1), (-1,0), (0,-1)] (area = 2), the radius should be increased by sqrt(pi/2). This can be done in fabmetheus xml by writing: +radiusAreal='True' + +in the attributes of the object or any parent of that object. In other modeling programs, you'll have to this manually or make a script. If area compensation is not done, then changing the stretch parameters to over compensate for too small hole areas will lead to incorrect compensation in other shapes. + +==Operation== +The default 'Activate Stretch' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called. + +==Settings== +===Loop Stretch Over Perimeter Width=== +Default is 0.1. + +Defines the ratio of the maximum amount the loop aka inner shell threads will be stretched compared to the edge width, in general this value should be the same as the 'Perimeter Outside Stretch Over Perimeter Width' setting. + +===Path Stretch Over Perimeter Width=== +Default is zero. + +Defines the ratio of the maximum amount the threads which are not loops, like the infill threads, will be stretched compared to the edge width. + +===Perimeter=== +====Perimeter Inside Stretch Over Perimeter Width==== +Default is 0.32. + +Defines the ratio of the maximum amount the inside edge thread will be stretched compared to the edge width, this is the most important setting in stretch. The higher the value the more it will stretch the edge and the wider holes will be. If the value is too small, the holes could be drilled out after fabrication, if the value is too high, the holes would be too wide and the part would have to junked. + +====Perimeter Outside Stretch Over Perimeter Width==== +Default is 0.1. + +Defines the ratio of the maximum amount the outside edge thread will be stretched compared to the edge width, in general this value should be around a third of the 'Perimeter Inside Stretch Over Perimeter Width' setting. + +===Stretch from Distance over Perimeter Width=== +Default is two. + +The stretch algorithm works by checking at each turning point on the extrusion path what the direction of the thread is at a distance of 'Stretch from Distance over Perimeter Width' times the edge width, on both sides, and moves the thread in the opposite direction. So it takes the current turning-point, goes "Stretch from Distance over Perimeter Width" * "Perimeter Width" ahead, reads the direction at that point. Then it goes the same distance in back in time, reads the direction at that other point. It then moves the thread in the opposite direction, away from the center of the arc formed by these 2 points+directions. + +The magnitude of the stretch increases with: +the amount that the direction of the two threads is similar and +by the '..Stretch Over Perimeter Width' ratio. + +==Examples== +The following examples stretch the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and stretch.py. + +> python stretch.py +This brings up the stretch dialog. + +> python stretch.py Screw Holder Bottom.stl +The stretch tool is parsing the file: +Screw Holder Bottom.stl +.. +The stretch tool has created the file: +.. Screw Holder Bottom_stretch.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +#maybe speed up feedRate option +def getCraftedText( fileName, gcodeText, stretchRepository = None ): + "Stretch a gcode linear move text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), stretchRepository ) + +def getCraftedTextFromText( gcodeText, stretchRepository = None ): + "Stretch a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'stretch'): + return gcodeText + if stretchRepository == None: + stretchRepository = settings.getReadRepository( StretchRepository() ) + if not stretchRepository.activateStretch.value: + return gcodeText + return StretchSkein().getCraftedGcode( gcodeText, stretchRepository ) + +def getNewRepository(): + 'Get new repository.' + return StretchRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Stretch a gcode linear move file. Chain stretch the gcode if it is not already stretched." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'stretch', shouldAnalyze) + + +class LineIteratorBackward: + "Backward line iterator class." + def __init__( self, isLoop, lineIndex, lines ): + self.firstLineIndex = None + self.isLoop = isLoop + self.lineIndex = lineIndex + self.lines = lines + + def getIndexBeforeNextDeactivate(self): + "Get index two lines before the deactivate command." + for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'M103': + return lineIndex - 2 + print('This should never happen in stretch, no deactivate command was found for this thread.') + raise StopIteration, "You've reached the end of the line." + + def getNext(self): + "Get next line going backward or raise exception." + while self.lineIndex > 3: + if self.lineIndex == self.firstLineIndex: + raise StopIteration, "You've reached the end of the line." + if self.firstLineIndex == None: + self.firstLineIndex = self.lineIndex + nextLineIndex = self.lineIndex - 1 + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'M103': + if self.isLoop: + nextLineIndex = self.getIndexBeforeNextDeactivate() + else: + raise StopIteration, "You've reached the end of the line." + if firstWord == 'G1': + if self.isBeforeExtrusion(): + if self.isLoop: + nextLineIndex = self.getIndexBeforeNextDeactivate() + else: + raise StopIteration, "You've reached the end of the line." + else: + self.lineIndex = nextLineIndex + return line + self.lineIndex = nextLineIndex + raise StopIteration, "You've reached the end of the line." + + def isBeforeExtrusion(self): + "Determine if index is two or more before activate command." + linearMoves = 0 + for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + linearMoves += 1 + if firstWord == 'M101': + return linearMoves > 0 + if firstWord == 'M103': + return False + print('This should never happen in isBeforeExtrusion in stretch, no activate command was found for this thread.') + return False + + +class LineIteratorForward: + "Forward line iterator class." + def __init__( self, isLoop, lineIndex, lines ): + self.firstLineIndex = None + self.isLoop = isLoop + self.lineIndex = lineIndex + self.lines = lines + + def getIndexJustAfterActivate(self): + "Get index just after the activate command." + for lineIndex in xrange( self.lineIndex - 1, 3, - 1 ): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'M101': + return lineIndex + 1 + print('This should never happen in stretch, no activate command was found for this thread.') + raise StopIteration, "You've reached the end of the line." + + def getNext(self): + "Get next line or raise exception." + while self.lineIndex < len(self.lines): + if self.lineIndex == self.firstLineIndex: + raise StopIteration, "You've reached the end of the line." + if self.firstLineIndex == None: + self.firstLineIndex = self.lineIndex + nextLineIndex = self.lineIndex + 1 + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'M103': + if self.isLoop: + nextLineIndex = self.getIndexJustAfterActivate() + else: + raise StopIteration, "You've reached the end of the line." + self.lineIndex = nextLineIndex + if firstWord == 'G1': + return line + raise StopIteration, "You've reached the end of the line." + + +class StretchRepository: + "A class to handle the stretch settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.stretch.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Stretch', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Stretch') + self.activateStretch = settings.BooleanSetting().getFromValue('Activate Stretch', self, False ) + self.crossLimitDistanceOverEdgeWidth = settings.FloatSpin().getFromValue( 3.0, 'Cross Limit Distance Over Perimeter Width (ratio):', self, 10.0, 5.0 ) + self.loopStretchOverEdgeWidth = settings.FloatSpin().getFromValue( 0.05, 'Loop Stretch Over Perimeter Width (ratio):', self, 0.25, 0.11 ) + self.pathStretchOverEdgeWidth = settings.FloatSpin().getFromValue( 0.0, 'Path Stretch Over Perimeter Width (ratio):', self, 0.2, 0.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Perimeter -', self ) + self.edgeInsideStretchOverEdgeWidth = settings.FloatSpin().getFromValue( 0.12, 'Perimeter Inside Stretch Over Perimeter Width (ratio):', self, 0.52, 0.32 ) + self.edgeOutsideStretchOverEdgeWidth = settings.FloatSpin().getFromValue( 0.05, 'Perimeter Outside Stretch Over Perimeter Width (ratio):', self, 0.25, 0.1 ) + settings.LabelSeparator().getFromRepository(self) + self.stretchFromDistanceOverEdgeWidth = settings.FloatSpin().getFromValue( 1.0, 'Stretch From Distance Over Perimeter Width (ratio):', self, 3.0, 2.0 ) + self.executeTitle = 'Stretch' + + def execute(self): + "Stretch button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class StretchSkein: + "A class to stretch a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = 0.4 + self.extruderActive = False + self.feedRateMinute = 959.0 + self.isLoop = False + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + + def getCraftedGcode( self, gcodeText, stretchRepository ): + "Parse gcode text and store the stretch gcode." + self.lines = archive.getTextLines(gcodeText) + self.stretchRepository = stretchRepository + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseStretch(line) + return self.distanceFeedRate.output.getvalue() + + def getCrossLimitedStretch( self, crossLimitedStretch, crossLineIterator, locationComplex ): + "Get cross limited relative stretch for a location." + try: + line = crossLineIterator.getNext() + except StopIteration: + return crossLimitedStretch + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + pointComplex = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine).dropAxis() + pointMinusLocation = locationComplex - pointComplex + pointMinusLocationLength = abs( pointMinusLocation ) + if pointMinusLocationLength <= self.crossLimitDistanceFraction: + return crossLimitedStretch + parallelNormal = pointMinusLocation / pointMinusLocationLength + parallelStretch = euclidean.getDotProduct( parallelNormal, crossLimitedStretch ) * parallelNormal + if pointMinusLocationLength > self.crossLimitDistance: + return parallelStretch + crossNormal = complex( parallelNormal.imag, - parallelNormal.real ) + crossStretch = euclidean.getDotProduct( crossNormal, crossLimitedStretch ) * crossNormal + crossPortion = ( self.crossLimitDistance - pointMinusLocationLength ) / self.crossLimitDistanceRemainder + return parallelStretch + crossStretch * crossPortion + + def getRelativeStretch( self, locationComplex, lineIterator ): + "Get relative stretch for a location." + lastLocationComplex = locationComplex + oldTotalLength = 0.0 + pointComplex = locationComplex + totalLength = 0.0 + while 1: + try: + line = lineIterator.getNext() + except StopIteration: + locationMinusPoint = locationComplex - pointComplex + locationMinusPointLength = abs( locationMinusPoint ) + if locationMinusPointLength > 0.0: + return locationMinusPoint / locationMinusPointLength + return complex() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = splitLine[0] + pointComplex = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine).dropAxis() + locationMinusPoint = lastLocationComplex - pointComplex + locationMinusPointLength = abs( locationMinusPoint ) + totalLength += locationMinusPointLength + if totalLength >= self.stretchFromDistance: + distanceFromRatio = ( self.stretchFromDistance - oldTotalLength ) / locationMinusPointLength + totalPoint = distanceFromRatio * pointComplex + ( 1.0 - distanceFromRatio ) * lastLocationComplex + locationMinusTotalPoint = locationComplex - totalPoint + return locationMinusTotalPoint / self.stretchFromDistance + lastLocationComplex = pointComplex + oldTotalLength = totalLength + + def getStretchedLine( self, splitLine ): + "Get stretched gcode line." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + self.oldLocation = location + if self.extruderActive and self.threadMaximumAbsoluteStretch > 0.0: + return self.getStretchedLineFromIndexLocation( self.lineIndex - 1, self.lineIndex + 1, location ) + if self.isJustBeforeExtrusion() and self.threadMaximumAbsoluteStretch > 0.0: + return self.getStretchedLineFromIndexLocation( self.lineIndex - 1, self.lineIndex + 1, location ) + return self.lines[self.lineIndex] + + def getStretchedLineFromIndexLocation( self, indexPreviousStart, indexNextStart, location ): + "Get stretched gcode line from line index and location." + crossIteratorForward = LineIteratorForward( self.isLoop, indexNextStart, self.lines ) + crossIteratorBackward = LineIteratorBackward( self.isLoop, indexPreviousStart, self.lines ) + iteratorForward = LineIteratorForward( self.isLoop, indexNextStart, self.lines ) + iteratorBackward = LineIteratorBackward( self.isLoop, indexPreviousStart, self.lines ) + locationComplex = location.dropAxis() + relativeStretch = self.getRelativeStretch( locationComplex, iteratorForward ) + self.getRelativeStretch( locationComplex, iteratorBackward ) + relativeStretch *= 0.8 + relativeStretch = self.getCrossLimitedStretch( relativeStretch, crossIteratorForward, locationComplex ) + relativeStretch = self.getCrossLimitedStretch( relativeStretch, crossIteratorBackward, locationComplex ) + relativeStretchLength = abs( relativeStretch ) + if relativeStretchLength > 1.0: + relativeStretch /= relativeStretchLength + absoluteStretch = relativeStretch * self.threadMaximumAbsoluteStretch + stretchedPoint = location.dropAxis() + absoluteStretch + return self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.feedRateMinute, stretchedPoint, location.z ) + + def isJustBeforeExtrusion(self): + "Determine if activate command is before linear move command." + for lineIndex in xrange( self.lineIndex + 1, len(self.lines) ): + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1' or firstWord == 'M103': + return False + if firstWord == 'M101': + return True +# print('This should never happen in isJustBeforeExtrusion in stretch, no activate or deactivate command was found for this thread.') + return False + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('stretch') + return + elif firstWord == '(': + edgeWidth = float(splitLine[1]) + self.crossLimitDistance = self.edgeWidth * self.stretchRepository.crossLimitDistanceOverEdgeWidth.value + self.loopMaximumAbsoluteStretch = self.edgeWidth * self.stretchRepository.loopStretchOverEdgeWidth.value + self.pathAbsoluteStretch = self.edgeWidth * self.stretchRepository.pathStretchOverEdgeWidth.value + self.edgeInsideAbsoluteStretch = self.edgeWidth * self.stretchRepository.edgeInsideStretchOverEdgeWidth.value + self.edgeOutsideAbsoluteStretch = self.edgeWidth * self.stretchRepository.edgeOutsideStretchOverEdgeWidth.value + self.stretchFromDistance = self.stretchRepository.stretchFromDistanceOverEdgeWidth.value * edgeWidth + self.threadMaximumAbsoluteStretch = self.pathAbsoluteStretch + self.crossLimitDistanceFraction = 0.333333333 * self.crossLimitDistance + self.crossLimitDistanceRemainder = self.crossLimitDistance - self.crossLimitDistanceFraction + self.distanceFeedRate.addLine(line) + + def parseStretch(self, line): + "Parse a gcode line and add it to the stretch skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + line = self.getStretchedLine(splitLine) + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.setStretchToPath() + elif firstWord == '(': + self.layerCount.printProgressIncrement('stretch') + elif firstWord == '(': + self.isLoop = True + self.threadMaximumAbsoluteStretch = self.loopMaximumAbsoluteStretch + elif firstWord == '()': + self.setStretchToPath() + elif firstWord == '(': + self.isLoop = True + self.threadMaximumAbsoluteStretch = self.edgeInsideAbsoluteStretch + if splitLine[1] == 'outer': + self.threadMaximumAbsoluteStretch = self.edgeOutsideAbsoluteStretch + elif firstWord == '()': + self.setStretchToPath() + self.distanceFeedRate.addLine(line) + + def setStretchToPath(self): + "Set the thread stretch to path stretch and is loop false." + self.isLoop = False + self.threadMaximumAbsoluteStretch = self.pathAbsoluteStretch + + +def main(): + "Display the stretch dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py new file mode 100644 index 0000000..0a1a8b9 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py @@ -0,0 +1,204 @@ +""" +This page is in the table of contents. +Temperature is a plugin to set the temperature for the entire extrusion. + +The temperature manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Temperature + +==Operation== +The default 'Activate Temperature' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Rate=== +The default cooling rate and heating rate for the extruder were both been derived from bothacker's graph at: +http://bothacker.com/wp-content/uploads/2009/09/18h5m53s9.29.2009.png + +====Cooling Rate==== +Default is three degrees Celcius per second. + +Defines the cooling rate of the extruder. + +====Heating Rate==== +Default is ten degrees Celcius per second. + +Defines the heating rate of the extruder. + +===Temperature=== +====Base Temperature==== +Default for ABS is two hundred degrees Celcius. + +Defines the raft base temperature. + +====Interface Temperature==== +Default for ABS is two hundred degrees Celcius. + +Defines the raft interface temperature. + +====Object First Layer Infill Temperature==== +Default for ABS is 195 degrees Celcius. + +Defines the infill temperature of the first layer of the object. + +====Object First Layer Perimeter Temperature==== +Default for ABS is two hundred and twenty degrees Celcius. + +Defines the edge temperature of the first layer of the object. + +====Object Next Layers Temperature==== +Default for ABS is two hundred and thirty degrees Celcius. + +Defines the temperature of the next layers of the object. + +====Support Layers Temperature==== +Default for ABS is two hundred degrees Celcius. + +Defines the support layers temperature. + +====Supported Layers Temperature==== +Default for ABS is two hundred and thirty degrees Celcius. + +Defines the temperature of the supported layers of the object, those layers which are right above a support layer. + +==Examples== +The following examples add temperature information to the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and temperature.py. + +> python temperature.py +This brings up the temperature dialog. + +> python temperature.py Screw Holder Bottom.stl +The temperature tool is parsing the file: +Screw Holder Bottom.stl +.. +The temperature tool has created the file: +.. Screw Holder Bottom_temperature.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', repository=None): + "Temperature the file or text." + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + "Temperature a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'temperature'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( TemperatureRepository() ) + if not repository.activateTemperature.value: + return gcodeText + return TemperatureSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return TemperatureRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Temperature a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'temperature', shouldAnalyze) + + +class TemperatureRepository: + "A class to handle the temperature settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.temperature.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Temperature', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Temperature') + self.activateTemperature = settings.BooleanSetting().getFromValue('Activate Temperature', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Rate -', self ) + self.coolingRate = settings.FloatSpin().getFromValue( 1.0, 'Cooling Rate (Celcius/second):', self, 20.0, 3.0 ) + self.heatingRate = settings.FloatSpin().getFromValue( 1.0, 'Heating Rate (Celcius/second):', self, 20.0, 10.0 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Temperature -', self ) + self.baseTemperature = settings.FloatSpin().getFromValue( 140.0, 'Base Temperature (Celcius):', self, 260.0, 200.0 ) + self.interfaceTemperature = settings.FloatSpin().getFromValue( 140.0, 'Interface Temperature (Celcius):', self, 260.0, 200.0 ) + self.objectFirstLayerInfillTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object First Layer Infill Temperature (Celcius):', self, 260.0, 195.0 ) + self.objectFirstLayerPerimeterTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object First Layer Perimeter Temperature (Celcius):', self, 260.0, 220.0 ) + self.objectNextLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object Next Layers Temperature (Celcius):', self, 260.0, 230.0 ) + self.supportLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Support Layers Temperature (Celcius):', self, 260.0, 200.0 ) + self.supportedLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Supported Layers Temperature (Celcius):', self, 260.0, 230.0 ) + self.executeTitle = 'Temperature' + + def execute(self): + "Temperature button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class TemperatureSkein: + "A class to temperature a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.lineIndex = 0 + self.lines = None + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the temperature gcode." + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + if self.repository.coolingRate.value < 0.1: + print('The cooling rate should be more than 0.1, any cooling rate less than 0.1 will be treated as 0.1.') + self.repository.coolingRate.value = 0.1 + if self.repository.heatingRate.value < 0.1: + print('The heating rate should be more than 0.1, any heating rate less than 0.1 will be treated as 0.1.') + self.repository.heatingRate.value = 0.1 + self.parseInitialization() + self.distanceFeedRate.addLines( self.lines[self.lineIndex :] ) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('temperature') + return + elif firstWord == '(': + self.distanceFeedRate.addTagBracketedLine('coolingRate', self.repository.coolingRate.value ) + self.distanceFeedRate.addTagBracketedLine('heatingRate', self.repository.heatingRate.value ) + self.distanceFeedRate.addTagBracketedLine('baseTemperature', self.repository.baseTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('interfaceTemperature', self.repository.interfaceTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('objectFirstLayerInfillTemperature', self.repository.objectFirstLayerInfillTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('objectFirstLayerPerimeterTemperature', self.repository.objectFirstLayerPerimeterTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('objectNextLayersTemperature', self.repository.objectNextLayersTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('supportLayersTemperature', self.repository.supportLayersTemperature.value ) + self.distanceFeedRate.addTagBracketedLine('supportedLayersTemperature', self.repository.supportedLayersTemperature.value ) + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the temperature dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/tower.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/tower.py new file mode 100644 index 0000000..500ad86 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/tower.py @@ -0,0 +1,380 @@ +""" +This page is in the table of contents. +Tower commands the fabricator to extrude a disconnected region for a few layers, then go to another disconnected region and extrude there. Its purpose is to reduce the number of stringers between a shape and reduce extruder travel. + +The tower manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Tower + +==Operation== +The default 'Activate Tower' checkbox is off. The default is off because tower could result in the extruder colliding with an already extruded part of the shape and because extruding in one region for more than one layer could result in the shape melting. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Maximum Tower Height=== +Default: 5 + +Defines the maximum number of layers that the extruder will extrude in one region before going to another. This is the most important value for tower. + +===Extruder Possible Collision Cone Angle=== +Default: 60 degrees + +Tower works by looking for islands in each layer and if it finds another island in the layer above, it goes to the next layer above instead of going across to other regions on the original layer. It checks for collision with shapes already extruded within a cone from the nozzle tip. The 'Extruder Possible Collision Cone Angle' setting is the angle of that cone. Realistic values for the cone angle range between zero and ninety. The higher the angle, the less likely a collision with the rest of the shape is, generally the extruder will stay in the region for only a few layers before a collision is detected with the wide cone. + +===Tower Start Layer=== +Default: 1 + +Defines the layer index which the script starts extruding towers, after the last raft layer which does not have support material. It is best to not tower at least the first layer because the temperature of the first layer is sometimes different than that of the other layers. + +==Examples== +The following examples tower the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and tower.py. + +> python tower.py +This brings up the tower dialog. + +> python tower.py Screw Holder Bottom.stl +The tower tool is parsing the file: +Screw Holder Bottom.stl +.. +The tower tool has created the file: +.. Screw Holder Bottom_tower.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +def getCraftedText( fileName, text, towerRepository = None ): + "Tower a gcode linear move file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), towerRepository ) + +def getCraftedTextFromText( gcodeText, towerRepository = None ): + "Tower a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'tower'): + return gcodeText + if towerRepository == None: + towerRepository = settings.getReadRepository( TowerRepository() ) + if not towerRepository.activateTower.value: + return gcodeText + return TowerSkein().getCraftedGcode( gcodeText, towerRepository ) + +def getNewRepository(): + 'Get new repository.' + return TowerRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Tower a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'tower', shouldAnalyze) + + +class Island: + "A class to hold the boundary and lines." + def __init__(self): + self.boundary = [] + self.boundingLoop = None + self.lines = [] + + def addToBoundary( self, splitLine ): + "Add to the boundary if it is not complete." + if self.boundingLoop == None: + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.boundary.append(location.dropAxis()) + self.z = location.z + + def createBoundingLoop(self): + "Create the bounding loop if it is not already created." + if self.boundingLoop == None: + self.boundingLoop = intercircle.BoundingLoop().getFromLoop( self.boundary ) + + +class ThreadLayer: + "A layer of loops and paths." + def __init__(self): + "Thread layer constructor." + self.afterExtrusionLines = [] + self.beforeExtrusionLines = [] + self.islands = [] + + def __repr__(self): + "Get the string representation of this thread layer." + return '%s' % self.islands + + +class TowerRepository: + "A class to handle the tower settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.tower.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Tower', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Tower') + self.activateTower = settings.BooleanSetting().getFromValue('Activate Tower', self, False ) + self.extruderPossibleCollisionConeAngle = settings.FloatSpin().getFromValue( 40.0, 'Extruder Possible Collision Cone Angle (degrees):', self, 80.0, 60.0 ) + self.maximumTowerHeight = settings.IntSpin().getFromValue( 2, 'Maximum Tower Height (layers):', self, 10, 5 ) + self.towerStartLayer = settings.IntSpin().getFromValue( 1, 'Tower Start Layer (integer):', self, 5, 1 ) + self.executeTitle = 'Tower' + + def execute(self): + "Tower button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class TowerSkein: + "A class to tower a skein of extrusions." + def __init__(self): + self.afterExtrusionLines = [] + self.beforeExtrusionLines = [] + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.edgeWidth = 0.6 + self.highestZ = - 987654321.0 + self.island = None + self.layerIndex = 0 + self.lineIndex = 0 + self.lines = None + self.minimumBelow = 0.1 + self.oldLayerIndex = None + self.oldLocation = None + self.oldOrderedLocation = Vector3() + self.shutdownLineIndex = sys.maxint + self.nestedRingCount = 0 + self.threadLayer = None + self.threadLayers = [] + self.travelFeedRateMinute = None + + def addEntireLayer( self, threadLayer ): + "Add entire thread layer." + self.distanceFeedRate.addLines( threadLayer.beforeExtrusionLines ) + for island in threadLayer.islands: + self.distanceFeedRate.addLines( island.lines ) + self.distanceFeedRate.addLines( threadLayer.afterExtrusionLines ) + + def addHighThread(self, location): + "Add thread with a high move if necessary to clear the previous extrusion." + if self.oldLocation != None: + if self.oldLocation.z + self.minimumBelow < self.highestZ: + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.travelFeedRateMinute, self.oldLocation.dropAxis(), self.highestZ ) + if location.z + self.minimumBelow < self.highestZ: + self.distanceFeedRate.addGcodeMovementZWithFeedRate( self.travelFeedRateMinute, location.dropAxis(), self.highestZ ) + + def addThreadLayerIfNone(self): + "Add a thread layer if it is none." + if self.threadLayer != None: + return + self.threadLayer = ThreadLayer() + self.threadLayers.append( self.threadLayer ) + self.threadLayer.beforeExtrusionLines = self.beforeExtrusionLines + self.beforeExtrusionLines = [] + + def addTowers(self): + "Add towers." + bottomLayerIndex = self.getBottomLayerIndex() + if bottomLayerIndex == None: + return + removedIsland = self.getRemovedIslandAddLayerLinesIfDifferent( self.threadLayers[ bottomLayerIndex ].islands, bottomLayerIndex ) + while 1: + self.climbTower( removedIsland ) + bottomLayerIndex = self.getBottomLayerIndex() + if bottomLayerIndex == None: + return + removedIsland = self.getRemovedIslandAddLayerLinesIfDifferent( self.threadLayers[ bottomLayerIndex ].islands, bottomLayerIndex ) + + def climbTower( self, removedIsland ): + "Climb up the island to any islands directly above." + outsetDistance = 1.5 * self.edgeWidth + for step in xrange( self.towerRepository.maximumTowerHeight.value ): + aboveIndex = self.oldLayerIndex + 1 + if aboveIndex >= len( self.threadLayers ): + return + outsetRemovedLoop = removedIsland.boundingLoop.getOutsetBoundingLoop( outsetDistance ) + islandsWithin = [] + for island in self.threadLayers[ aboveIndex ].islands: + if self.isInsideRemovedOutsideCone( island, outsetRemovedLoop, aboveIndex ): + islandsWithin.append( island ) + if len( islandsWithin ) < 1: + return + removedIsland = self.getRemovedIslandAddLayerLinesIfDifferent( islandsWithin, aboveIndex ) + self.threadLayers[ aboveIndex ].islands.remove( removedIsland ) + + def getBottomLayerIndex(self): + "Get the index of the first island layer which has islands." + for islandLayerIndex in xrange( len( self.threadLayers ) ): + if len( self.threadLayers[ islandLayerIndex ].islands ) > 0: + return islandLayerIndex + return None + + def getCraftedGcode( self, gcodeText, towerRepository ): + "Parse gcode text and store the tower gcode." + self.lines = archive.getTextLines(gcodeText) + self.towerRepository = towerRepository + self.parseInitialization() + self.parseIfWordUntilWord('(') + self.parseIfWordUntilWord('()') + for lineIndex in xrange(self.lineIndex, len(self.lines)): + self.parseLine( lineIndex ) + concatenateEndIndex = min( len( self.threadLayers ), towerRepository.towerStartLayer.value ) + for threadLayer in self.threadLayers[ : concatenateEndIndex ]: + self.addEntireLayer( threadLayer ) + self.threadLayers = self.threadLayers[ concatenateEndIndex : ] + self.addTowers() + self.distanceFeedRate.addLines( self.lines[ self.shutdownLineIndex : ] ) + return self.distanceFeedRate.output.getvalue() + + def getRemovedIslandAddLayerLinesIfDifferent( self, islands, layerIndex ): + "Add gcode lines for the layer if it is different than the old bottom layer index." + threadLayer = None + if layerIndex != self.oldLayerIndex: + self.oldLayerIndex = layerIndex + threadLayer = self.threadLayers[layerIndex] + self.distanceFeedRate.addLines( threadLayer.beforeExtrusionLines ) + removedIsland = self.getTransferClosestNestedRingLines( self.oldOrderedLocation, islands ) + if threadLayer != None: + self.distanceFeedRate.addLines( threadLayer.afterExtrusionLines ) + return removedIsland + + def getTransferClosestNestedRingLines( self, oldOrderedLocation, remainingNestedRings ): + "Get and transfer the closest remaining nested ring." + if len( remainingNestedRings ) > 0: + oldOrderedLocation.z = remainingNestedRings[0].z + closestDistance = 999999999987654321.0 + closestNestedRing = None + for remainingNestedRing in remainingNestedRings: + distance = euclidean.getClosestDistanceIndexToLine(oldOrderedLocation.dropAxis(), remainingNestedRing.boundary).distance + if distance < closestDistance: + closestDistance = distance + closestNestedRing = remainingNestedRing + remainingNestedRings.remove(closestNestedRing) + hasTravelledHighRoad = False + for line in closestNestedRing.lines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + if firstWord == 'G1': + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if not hasTravelledHighRoad: + hasTravelledHighRoad = True + self.addHighThread(location) + if location.z > self.highestZ: + self.highestZ = location.z + self.oldLocation = location + self.distanceFeedRate.addLine(line) + return closestNestedRing + + def isInsideRemovedOutsideCone( self, island, removedBoundingLoop, untilLayerIndex ): + "Determine if the island is entirely inside the removed bounding loop and outside the collision cone of the remaining islands." + if not island.boundingLoop.isEntirelyInsideAnother( removedBoundingLoop ): + return False + bottomLayerIndex = self.getBottomLayerIndex() + coneAngleTangent = math.tan( math.radians( self.towerRepository.extruderPossibleCollisionConeAngle.value ) ) + for layerIndex in xrange( bottomLayerIndex, untilLayerIndex ): + islands = self.threadLayers[layerIndex].islands + outsetDistance = self.edgeWidth * ( untilLayerIndex - layerIndex ) * coneAngleTangent + 0.5 * self.edgeWidth + for belowIsland in self.threadLayers[layerIndex].islands: + outsetIslandLoop = belowIsland.boundingLoop.getOutsetBoundingLoop( outsetDistance ) + if island.boundingLoop.isOverlappingAnother( outsetIslandLoop ): + return False + return True + + def parseIfWordUntilWord(self, word): + "Parse gcode if there is a word until the word is reached." + for self.lineIndex in xrange(self.lineIndex, gcodec.getFirstWordIndexReverse(word, self.lines, self.lineIndex)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.addLine(line) + if firstWord == 'G1': + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.oldLocation.z > self.highestZ: + self.highestZ = self.oldLocation.z + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('tower') + elif firstWord == '(': + return + elif firstWord == '(': + self.minimumBelow = 0.1 * float(splitLine[1]) + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine( self, lineIndex ): + "Parse a gcode line." + line = self.lines[lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + self.afterExtrusionLines.append(line) + if firstWord == 'M103': + self.afterExtrusionLines = [] + elif firstWord == '()': + self.island.createBoundingLoop() + elif firstWord == '(': + self.island.addToBoundary(splitLine) + elif firstWord == '()': + self.shutdownLineIndex = lineIndex + elif firstWord == '(': + self.beforeExtrusionLines = [ line ] + self.island = None + self.nestedRingCount = 0 + self.threadLayer = None + return + elif firstWord == '()': + if self.threadLayer != None: + self.threadLayer.afterExtrusionLines = self.afterExtrusionLines + self.afterExtrusionLines = [] + elif firstWord == '()': + self.afterExtrusionLines = [] + elif firstWord == '()': + self.nestedRingCount += 1 + if self.island == None: + self.island = Island() + self.addThreadLayerIfNone() + self.threadLayer.islands.append( self.island ) + elif firstWord == '()': + self.afterExtrusionLines = [] + if self.island != None: + self.island.lines.append(line) + if firstWord == '()': + self.afterExtrusionLines = [] + self.nestedRingCount -= 1 + if self.nestedRingCount == 0: + self.island = None + if len( self.beforeExtrusionLines ) > 0: + self.beforeExtrusionLines.append(line) + + +def main(): + "Display the tower dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/unpause.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/unpause.py new file mode 100644 index 0000000..1827bd4 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/unpause.py @@ -0,0 +1,193 @@ +""" +This page is in the table of contents. +The unpause plugin is based on the Shane Hathaway's patch to speed up a line segment to compensate for the delay of the microprocessor. The description is at: +http://shane.willowrise.com/archives/delay-compensation-in-firmware/ + +The unpause manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Unpause + +==Operation== +The default 'Activate Unpause' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Delay=== +Default is 28 milliseconds, which Shane found for the Arduino. + +Defines the delay on the microprocessor that will be at least partially compensated for. + +===Maximum Speed=== +Default is 1.3. + +Defines the maximum amount that the feed rate will be sped up to, compared to the original feed rate. + +==Examples== +The following examples unpause the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and unpause.py. + +> python unpause.py +This brings up the unpause dialog. + +> python unpause.py Screw Holder Bottom.stl +The unpause tool is parsing the file: +Screw Holder Bottom.stl +.. +The unpause tool has created the file: +.. Screw Holder Bottom_unpause.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, gcodeText, repository=None): + "Unpause a gcode linear move file or text." + return getCraftedTextFromText( archive.getTextIfEmpty( fileName, gcodeText ), repository ) + +def getCraftedTextFromText(gcodeText, repository=None): + "Unpause a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'unpause'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( UnpauseRepository() ) + if not repository.activateUnpause.value: + return gcodeText + return UnpauseSkein().getCraftedGcode(gcodeText, repository) + +def getNewRepository(): + 'Get new repository.' + return UnpauseRepository() + +def getSelectedPlugin(repository): + "Get the selected plugin." + for plugin in repository.unpausePlugins: + if plugin.value: + return plugin + return None + +def writeOutput(fileName, shouldAnalyze=True): + "Unpause a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'unpause', shouldAnalyze) + + +class UnpauseRepository: + "A class to handle the unpause settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.unpause.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Unpause', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Unpause') + self.activateUnpause = settings.BooleanSetting().getFromValue('Activate Unpause', self, False ) + self.delay = settings.FloatSpin().getFromValue( 2.0, 'Delay (milliseconds):', self, 42.0, 28.0 ) + self.maximumSpeed = settings.FloatSpin().getFromValue( 1.1, 'Maximum Speed (ratio):', self, 1.9, 1.3 ) + self.executeTitle = 'Unpause' + + def execute(self): + "Unpause button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class UnpauseSkein: + "A class to unpause a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.feedRateMinute = 959.0 + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + + def getCraftedGcode(self, gcodeText, repository): + "Parse gcode text and store the unpause gcode." + self.delaySecond = repository.delay.value * 0.001 + self.maximumSpeed = repository.maximumSpeed.value + self.minimumSpeedUpReciprocal = 1.0 / self.maximumSpeed + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getUnpausedArcMovement( self, line, splitLine ): + "Get an unpaused arc movement." + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + if self.oldLocation == None: + return line + relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.oldLocation += relativeLocation + distance = gcodec.getArcDistance(relativeLocation, splitLine) + return self.getUnpausedMovement(distance, line, splitLine) + + def getUnpausedLinearMovement( self, line, splitLine ): + "Get an unpaused linear movement." + self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + if self.oldLocation == None: + self.oldLocation = location + return line + distance = abs(self.oldLocation - location) + self.oldLocation = location + return self.getUnpausedMovement(distance, line, splitLine) + + def getUnpausedMovement(self, distance, line, splitLine): + "Get an unpaused movement." + if distance <= 0.0: + return line + resultantReciprocal = 1.0 - self.delaySecond / distance * self.feedRateMinute / 60.0 + resultantReciprocal = max(self.minimumSpeedUpReciprocal, resultantReciprocal) + return self.distanceFeedRate.getLineWithFeedRate(self.feedRateMinute / resultantReciprocal, line, splitLine) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('unpause') + return + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + line = self.getUnpausedLinearMovement( line, splitLine ) + if firstWord == 'G2' or firstWord == 'G3': + line = self.getUnpausedArcMovement( line, splitLine ) + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the unpause dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/whittle.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/whittle.py new file mode 100644 index 0000000..38130dc --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/whittle.py @@ -0,0 +1,172 @@ +""" +This page is in the table of contents. +Whittle will convert each polygon of a gcode file into a helix which has a vertical step down on each rotation. + +==Operation== +The default 'Activate Whittle' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. If the cutting tool can cut the slab in one cut, the 'Activate Whittle' checkbox should be off, the default is off. + +==Settings== +===Maximum Vertical Step'=== +Default is 0.1 mm. + +Defines the maximum distance that the helix will step down on each rotation. The number of steps in the helix will be the layer height divided by the 'Maximum Vertical Step', rounded up. The amount the helix will step down is the layer height divided by the number of steps. The thinner the 'Maximum Vertical Step', the more times the cutting tool will circle around on its way to the bottom of the slab. + +==Examples== +The following examples whittle the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and whittle.py. + +> python whittle.py +This brings up the whittle dialog. + +> python whittle.py Screw Holder Bottom.stl +The whittle tool is parsing the file: +Screw Holder Bottom.stl +.. +The whittle tool has created the file: +.. Screw Holder Bottom_whittle.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/02/05 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text='', whittleRepository = None ): + "Whittle the preface file or text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), whittleRepository ) + +def getCraftedTextFromText( gcodeText, whittleRepository = None ): + "Whittle the preface gcode text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'whittle'): + return gcodeText + if whittleRepository == None: + whittleRepository = settings.getReadRepository( WhittleRepository() ) + if not whittleRepository.activateWhittle.value: + return gcodeText + return WhittleSkein().getCraftedGcode( whittleRepository, gcodeText ) + +def getNewRepository(): + 'Get new repository.' + return WhittleRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Whittle the carving of a gcode file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'whittle', shouldAnalyze) + + +class WhittleRepository: + "A class to handle the whittle settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.whittle.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Whittled', self, '') + self.activateWhittle = settings.BooleanSetting().getFromValue('Activate Whittle', self, False ) + self.maximumVerticalStep = settings.FloatSpin().getFromValue( 0.02, 'Maximum Vertical Step (mm):', self, 0.42, 0.1 ) + self.executeTitle = 'Whittle' + + def execute(self): + "Whittle button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class WhittleSkein: + "A class to whittle a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.layerHeight = 0.3333333333 + self.lineIndex = 0 + self.movementLines = [] + self.oldLocation = None + + def getCraftedGcode( self, whittleRepository, gcodeText ): + "Parse gcode text and store the whittle gcode." + self.whittleRepository = whittleRepository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getLinearMove( self, line, splitLine ): + "Get the linear move." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.movementLines.append(line) + z = location.z + self.layerDeltas[0] + self.oldLocation = location + return self.distanceFeedRate.getLineWithZ( line, splitLine, z ) + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex].lstrip() + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('whittle') + return + elif firstWord == '(': + self.setLayerThinknessVerticalDeltas(splitLine) + self.distanceFeedRate.addTagBracketedLine('layerStep', self.layerStep ) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the whittle skein." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + line = self.getLinearMove( line, splitLine ) + elif firstWord == 'M103': + self.repeatLines() + self.distanceFeedRate.addLine(line) + + def repeatLines(self): + "Repeat the lines at decreasing altitude." + for layerDelta in self.layerDeltas[1 :]: + for movementLine in self.movementLines: + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(movementLine) + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + z = location.z + layerDelta + self.distanceFeedRate.addLine( self.distanceFeedRate.getLineWithZ( movementLine, splitLine, z ) ) + self.movementLines = [] + + def setLayerThinknessVerticalDeltas( self, splitLine ): + "Set the layer height and the vertical deltas." + self.layerHeight = float(splitLine[1]) + numberOfSteps = int( math.ceil( self.layerHeight / self.whittleRepository.maximumVerticalStep.value ) ) + self.layerStep = self.layerHeight / float( numberOfSteps ) + self.layerDeltas = [] + halfDeltaMinusHalfTop = 0.5 * self.layerStep * ( 1.0 - numberOfSteps ) + for layerDeltaIndex in xrange( numberOfSteps - 1, - 1, - 1 ): + layerDelta = layerDeltaIndex * self.layerStep + halfDeltaMinusHalfTop + self.layerDeltas.append( layerDelta ) + + +def main(): + "Display the whittle dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/widen.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/widen.py new file mode 100644 index 0000000..432b34c --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/widen.py @@ -0,0 +1,225 @@ +#! /usr/bin/env python +""" +This page is in the table of contents. +Widen will widen the outside edges away from the inside edges, so that the outsides will be at least two edge widths away from the insides and therefore the outside filaments will not overlap the inside filaments. + +For example, if a mug has a very thin wall, widen would widen the outside of the mug so that the wall of the mug would be two edge widths wide, and the outside wall filament would not overlap the inside filament. + +For another example, if the outside of the object runs right next to a hole, widen would widen the wall around the hole so that the wall would bulge out around the hole, and the outside filament would not overlap the hole filament. + +The widen manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Widen + +==Operation== +The default 'Activate Widen' checkbox is off. When it is on, widen will work, when it is off, nothing will be done. + +==Examples== +The following examples widen the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and widen.py. + +> python widen.py +This brings up the widen dialog. + +> python widen.py Screw Holder Bottom.stl +The widen tool is parsing the file: +Screw Holder Bottom.stl +.. +The widen tool has created the file: +.. Screw Holder Bottom_widen.gcode + +""" + +from __future__ import absolute_import +try: + import psyco + psyco.full() +except: + pass +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.geometry.geometry_utilities import boolean_solid +from fabmetheus_utilities.geometry.solids import triangle_mesh +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import intercircle +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/28/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText(fileName, text='', repository=None): + 'Widen the preface file or text.' + return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) + +def getCraftedTextFromText(gcodeText, repository=None): + 'Widen the preface gcode text.' + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'widen'): + return gcodeText + if repository == None: + repository = settings.getReadRepository( WidenRepository() ) + if not repository.activateWiden.value: + return gcodeText + return WidenSkein().getCraftedGcode(gcodeText, repository) + +def getIntersectingWithinLoops(loop, loopList, outsetLoop): + 'Get the loops which are intersecting or which it is within.' + intersectingWithinLoops = [] + for otherLoop in loopList: + if getIsIntersectingWithinLoop(loop, otherLoop, outsetLoop): + intersectingWithinLoops.append(otherLoop) + return intersectingWithinLoops + +def getIsIntersectingWithinLoop(loop, otherLoop, outsetLoop): + 'Determine if the loop is intersecting or is within the other loop.' + if euclidean.isLoopIntersectingLoop(loop, otherLoop): + return True + return euclidean.isPathInsideLoop(otherLoop, loop) != euclidean.isPathInsideLoop(otherLoop, outsetLoop) + +def getIsPointInsideALoop(loops, point): + 'Determine if a point is inside a loop of a loop list.' + for loop in loops: + if euclidean.isPointInsideLoop(loop, point): + return True + return False + +def getNewRepository(): + 'Get new repository.' + return WidenRepository() + +def getWidenedLoop(loop, loopList, outsetLoop, radius): + 'Get the widened loop.' + intersectingWithinLoops = getIntersectingWithinLoops(loop, loopList, outsetLoop) + if len(intersectingWithinLoops) < 1: + return loop + loopsUnified = boolean_solid.getLoopsUnion(radius, [[loop], intersectingWithinLoops]) + if len(loopsUnified) < 1: + return loop + return euclidean.getLargestLoop(loopsUnified) + +def writeOutput(fileName, shouldAnalyze=True): + 'Widen the carving of a gcode file.' + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'widen', shouldAnalyze) + + +class WidenRepository: + 'A class to handle the widen settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.widen.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Widen', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute( + 'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Widen') + self.activateWiden = settings.BooleanSetting().getFromValue('Activate Widen', self, False) + self.executeTitle = 'Widen' + + def execute(self): + 'Widen button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( + self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class WidenSkein: + 'A class to widen a skein of extrusions.' + def __init__(self): + self.boundary = None + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.layerCount = settings.LayerCount() + self.lineIndex = 0 + self.loopLayer = None + + def addWiden(self, loopLayer): + 'Add widen to the layer.' + triangle_mesh.sortLoopsInOrderOfArea(False, loopLayer.loops) + widdershinsLoops = [] + clockwiseInsetLoops = [] + for loopIndex in xrange(len(loopLayer.loops)): + loop = loopLayer.loops[loopIndex] + if euclidean.isWiddershins(loop): + otherLoops = loopLayer.loops[: loopIndex] + loopLayer.loops[loopIndex + 1 :] + leftPoint = euclidean.getLeftPoint(loop) + if getIsPointInsideALoop(otherLoops, leftPoint): + self.distanceFeedRate.addGcodeFromLoop(loop, loopLayer.z) + else: + widdershinsLoops.append(loop) + else: +# clockwiseInsetLoop = intercircle.getLargestInsetLoopFromLoop(loop, self.doubleEdgeWidth) +# clockwiseInsetLoop.reverse() +# clockwiseInsetLoops.append(clockwiseInsetLoop) + clockwiseInsetLoops += intercircle.getInsetLoopsFromLoop(loop, self.doubleEdgeWidth) + self.distanceFeedRate.addGcodeFromLoop(loop, loopLayer.z) + for widdershinsLoop in widdershinsLoops: + outsetLoop = intercircle.getLargestInsetLoopFromLoop(widdershinsLoop, -self.doubleEdgeWidth) + widenedLoop = getWidenedLoop(widdershinsLoop, clockwiseInsetLoops, outsetLoop, self.edgeWidth) + self.distanceFeedRate.addGcodeFromLoop(widenedLoop, loopLayer.z) + + def getCraftedGcode(self, gcodeText, repository): + 'Parse gcode text and store the widen gcode.' + self.repository = repository + self.lines = archive.getTextLines(gcodeText) + self.parseInitialization() + for line in self.lines[self.lineIndex :]: + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def parseInitialization(self): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('widen') + elif firstWord == '()': + self.distanceFeedRate.addLine(line) + return + elif firstWord == '(': + self.edgeWidth = float(splitLine[1]) + self.doubleEdgeWidth = 2.0 * self.edgeWidth + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + 'Parse a gcode line and add it to the widen skein.' + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == '(': + location = gcodec.getLocationFromSplitLine(None, splitLine) + self.boundary.append(location.dropAxis()) + elif firstWord == '(': + self.layerCount.printProgressIncrement('widen') + self.loopLayer = euclidean.LoopLayer(float(splitLine[1])) + self.distanceFeedRate.addLine(line) + elif firstWord == '()': + self.addWiden( self.loopLayer ) + self.loopLayer = None + elif firstWord == '()': + self.boundary = [] + self.loopLayer.loops.append( self.boundary ) + if self.loopLayer == None: + self.distanceFeedRate.addLine(line) + + +def main(): + 'Display the widen dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/wipe.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/wipe.py new file mode 100644 index 0000000..ac662ec --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/craft_plugins/wipe.py @@ -0,0 +1,267 @@ +""" +This page is in the table of contents. +At the beginning of a layer, depending on the settings, wipe will move the nozzle with the extruder off to the arrival point, then to the wipe point, then to the departure point, then back to the layer. + +The wipe path is machine specific, so you'll probably have to change all the default locations. + +The wipe manual page is at: +http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Wipe + +==Operation== +The default 'Activate Wipe' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. + +==Settings== +===Arrival Location=== +====Arrival X==== +Default is minus seventy millimeters. + +Defines the x coordinate of the arrival location. + +====Arrival Y==== +Default is minus fifty millimeters. + +Defines the y coordinate of the arrival location. + +====Arrival Z==== +Default is fifty millimeters. + +Defines the z coordinate of the arrival location. + +===Departure Location=== +====Departure X==== +Default is minus seventy millimeters. + +Defines the x coordinate of the departure location. + +====Departure Y==== +Default is minus forty millimeters. + +Defines the y coordinate of the departure location. + +====Departure Z==== +Default is fifty millimeters. + +Defines the z coordinate of the departure location. + +===Wipe Location=== +====Wipe X==== +Default is minus seventy millimeters. + +Defines the x coordinate of the wipe location. + +====Wipe Y==== +Default is minus seventy millimeters. + +Defines the y coordinate of the wipe location. + +====Wipe Z==== +Default is fifty millimeters. + +Defines the z coordinate of the wipe location. + +===Wipe Period=== +Default is three. + +Defines the number of layers between wipes. Wipe will always wipe just before layer zero, afterwards it will wipe every "Wipe Period" layers. With the default of three, wipe will wipe just before layer zero, layer three, layer six and so on. + +==Examples== +The following examples wipe the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and wipe.py. + +> python wipe.py +This brings up the wipe dialog. + +> python wipe.py Screw Holder Bottom.stl +The wipe tool is parsing the file: +Screw Holder Bottom.stl +.. +The wipe tool has created the file: +.. Screw Holder Bottom_wipe.gcode + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities.vector3 import Vector3 +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import math +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftedText( fileName, text, wipeRepository = None ): + "Wipe a gcode linear move text." + return getCraftedTextFromText( archive.getTextIfEmpty(fileName, text), wipeRepository ) + +def getCraftedTextFromText( gcodeText, wipeRepository = None ): + "Wipe a gcode linear move text." + if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'wipe'): + return gcodeText + if wipeRepository == None: + wipeRepository = settings.getReadRepository( WipeRepository() ) + if not wipeRepository.activateWipe.value: + return gcodeText + return WipeSkein().getCraftedGcode( gcodeText, wipeRepository ) + +def getNewRepository(): + 'Get new repository.' + return WipeRepository() + +def writeOutput(fileName, shouldAnalyze=True): + "Wipe a gcode linear move file." + skeinforge_craft.writeChainTextWithNounMessage(fileName, 'wipe', shouldAnalyze) + + +class WipeRepository: + "A class to handle the wipe settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.wipe.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName(fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Wipe', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Wipe') + self.activateWipe = settings.BooleanSetting().getFromValue('Activate Wipe', self, False) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Arrival Location -', self) + self.locationArrivalX = settings.FloatSpin().getFromValue(-100.0, 'Arrival X (mm):', self, 100.0, -70.0) + self.locationArrivalY = settings.FloatSpin().getFromValue(-100.0, 'Arrival Y (mm):', self, 100.0, -50.0) + self.locationArrivalZ = settings.FloatSpin().getFromValue(-100.0, 'Arrival Z (mm):', self, 100.0, 50.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Departure Location -', self) + self.locationDepartureX = settings.FloatSpin().getFromValue(-100.0, 'Departure X (mm):', self, 100.0, -70.0) + self.locationDepartureY = settings.FloatSpin().getFromValue(-100.0, 'Departure Y (mm):', self, 100.0, -40.0) + self.locationDepartureZ = settings.FloatSpin().getFromValue(-100.0, 'Departure Z (mm):', self, 100.0, 50.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Wipe Location -', self) + self.locationWipeX = settings.FloatSpin().getFromValue(-100.0, 'Wipe X (mm):', self, 100.0, -70.0) + self.locationWipeY = settings.FloatSpin().getFromValue(-100.0, 'Wipe Y (mm):', self, 100.0, -70.0) + self.locationWipeZ = settings.FloatSpin().getFromValue(-100.0, 'Wipe Z (mm):', self, 100.0, 50.0) + settings.LabelSeparator().getFromRepository(self) + self.wipePeriod = settings.IntSpin().getFromValue(1, 'Wipe Period (layers):', self, 5, 3) + self.executeTitle = 'Wipe' + + def execute(self): + "Wipe button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) + for fileName in fileNames: + writeOutput(fileName) + + +class WipeSkein: + "A class to wipe a skein of extrusions." + def __init__(self): + self.distanceFeedRate = gcodec.DistanceFeedRate() + self.extruderActive = False + self.highestZ = None + self.layerIndex = -1 + self.lineIndex = 0 + self.lines = None + self.oldLocation = None + self.shouldWipe = False + self.travelFeedRateMinute = 957.0 + + def addHop( self, begin, end ): + "Add hop to highest point." + beginEndDistance = begin.distance(end) + if beginEndDistance < 3.0 * self.absoluteEdgeWidth: + return + alongWay = self.absoluteEdgeWidth / beginEndDistance + closeToOldLocation = euclidean.getIntermediateLocation( alongWay, begin, end ) + closeToOldLocation.z = self.highestZ + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( self.travelFeedRateMinute, closeToOldLocation ) ) + closeToOldArrival = euclidean.getIntermediateLocation( alongWay, end, begin ) + closeToOldArrival.z = self.highestZ + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( self.travelFeedRateMinute, closeToOldArrival ) ) + + def addWipeTravel( self, splitLine ): + "Add the wipe travel gcode." + location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + self.highestZ = max( self.highestZ, location.z ) + if not self.shouldWipe: + return + self.shouldWipe = False + if self.extruderActive: + self.distanceFeedRate.addLine('M103') + if self.oldLocation != None: + self.addHop( self.oldLocation, self.locationArrival ) + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( self.travelFeedRateMinute, self.locationArrival ) ) + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( self.travelFeedRateMinute, self.locationWipe ) ) + self.distanceFeedRate.addLine( self.getLinearMoveWithFeedRate( self.travelFeedRateMinute, self.locationDeparture ) ) + self.addHop( self.locationDeparture, location ) + if self.extruderActive: + self.distanceFeedRate.addLine('M101') + + def getCraftedGcode( self, gcodeText, wipeRepository ): + "Parse gcode text and store the wipe gcode." + self.lines = archive.getTextLines(gcodeText) + self.wipePeriod = wipeRepository.wipePeriod.value + self.parseInitialization( wipeRepository ) + self.locationArrival = Vector3( wipeRepository.locationArrivalX.value, wipeRepository.locationArrivalY.value, wipeRepository.locationArrivalZ.value ) + self.locationDeparture = Vector3( wipeRepository.locationDepartureX.value, wipeRepository.locationDepartureY.value, wipeRepository.locationDepartureZ.value ) + self.locationWipe = Vector3( wipeRepository.locationWipeX.value, wipeRepository.locationWipeY.value, wipeRepository.locationWipeZ.value ) + for self.lineIndex in xrange(self.lineIndex, len(self.lines)): + line = self.lines[self.lineIndex] + self.parseLine(line) + return self.distanceFeedRate.output.getvalue() + + def getLinearMoveWithFeedRate( self, feedRate, location ): + "Get a linear move line with the feedRate." + return self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( feedRate, location.dropAxis(), location.z ) + + def parseInitialization( self, wipeRepository ): + 'Parse gcode initialization and store the parameters.' + for self.lineIndex in xrange(len(self.lines)): + line = self.lines[self.lineIndex] + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + firstWord = gcodec.getFirstWord(splitLine) + self.distanceFeedRate.parseSplitLine(firstWord, splitLine) + if firstWord == '()': + self.distanceFeedRate.addTagBracketedProcedure('wipe') + return + elif firstWord == '(': + self.absoluteEdgeWidth = abs(float(splitLine[1])) + elif firstWord == '(': + self.travelFeedRateMinute = 60.0 * float(splitLine[1]) + self.distanceFeedRate.addLine(line) + + def parseLine(self, line): + "Parse a gcode line and add it to the bevel gcode." + splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) + if len(splitLine) < 1: + return + firstWord = splitLine[0] + if firstWord == 'G1': + self.addWipeTravel(splitLine) + self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) + elif firstWord == '(': + self.layerIndex += 1 + settings.printProgress(self.layerIndex, 'wipe') + if self.layerIndex % self.wipePeriod == 0: + self.shouldWipe = True + elif firstWord == 'M101': + self.extruderActive = True + elif firstWord == 'M103': + self.extruderActive = False + self.distanceFeedRate.addLine(line) + + +def main(): + "Display the wipe dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/help.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/help.py new file mode 100644 index 0000000..8725328 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/help.py @@ -0,0 +1,82 @@ +""" +This page is in the table of contents. +Help has buttons and menu items to open help, blog and forum pages in your primary browser. + + +==Link Buttons== +===Announcements=== +====Fabmetheus Blog==== +The skeinforge announcements blog and the place to post questions, bugs and skeinforge requests. + +===Documentation=== +====Index of Local Documentation==== +The list of the pages in the documentation folder. + +====Wiki Manual==== +The skeinforge wiki with pictures and charts. It is the best and most readable source of skeinforge information and you are welcome to contribute. + +====Skeinforge Overview==== +A general description of skeinforge, has answers to frequently asked questions and has many links to skeinforge, fabrication and python pages. It is also the help page of the skeinforge tool. + +===Forums=== +====Bits from Bytes Printing Board==== +Board about printing questions, problems and solutions. Most of the people on that forum use the rapman, but many of the solutions apply to any reprap. + +====Bits from Bytes Software Board==== +Board about software, and has some skeinforge threads. + +====Skeinforge Contributions Thread==== +Forum thread about how to contribute to skeinforge development. + +====Skeinforge Settings Thread==== +Forum thread for people to post, download and discuss skeinforge settings. + +==Settings== +===Wiki Manual Primary=== +Default is on. + +The help menu has an item for each button on the help page. Also, at the very top, it has a link to the local documentation and if there is a separate page for that tool in the wiki manual, a link to that page on the manual. If the 'Wiki Manual Primary' checkbutton is selected and there is a separate wiki manual page, the wiki page will be the primary document page, otherwise the local page will be primary. The help button (? symbol button) on the tool page will open the primary page, as will pressing . For example, if you click the the help button from the chamber tool, which has a separate page in the wiki, and 'Wiki Manual Primary' is selected, the wiki manual chamber page will be opened. Clicking F1 will also open the wiki manual chamber page. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_help +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addToMenu( master, menu, repository, window ): + "Add a tool plugin menu." + path = settings.getPathInFabmetheusFromFileNameHelp( repository.fileNameHelp ) + capitalizedBasename = os.path.basename(path).capitalize() + helpRepository = settings.getReadRepository( skeinforge_help.HelpRepository() ) + if repository.openWikiManualHelpPage != None and helpRepository.wikiManualPrimary.value: + menu.add_command( label = 'Local ' + capitalizedBasename, command = repository.openLocalHelpPage ) + else: + settings.addAcceleratorCommand('', repository.openLocalHelpPage, master, menu, 'Local ' + capitalizedBasename ) + if repository.openWikiManualHelpPage != None: + if helpRepository.wikiManualPrimary.value: + settings.addAcceleratorCommand('', repository.openWikiManualHelpPage, master, menu, 'Wiki Manual ' + capitalizedBasename ) + else: + menu.add_command( label = 'Wiki Manual ' + capitalizedBasename, command = repository.openWikiManualHelpPage ) + menu.add_separator() + settings.addMenuEntitiesToMenu( menu, helpRepository.menuEntities ) + +def getNewRepository(): + 'Get new repository.' + return skeinforge_help.HelpRepository() + +def main(): + "Display the help dialog." + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta.py new file mode 100644 index 0000000..8b9ec49 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta.py @@ -0,0 +1,36 @@ +""" +This page is in the table of contents. +Meta is a script to access the plugins which handle meta information. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_meta + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addToMenu( master, menu, repository, window ): + "Add a tool plugin menu." + metaFilePath = archive.getSkeinforgePluginsPath('meta.py') + settings.addPluginsParentToMenu(skeinforge_meta.getPluginsDirectoryPath(), menu, metaFilePath, skeinforge_meta.getPluginFileNames()) + +def getNewRepository(): + 'Get new repository.' + return skeinforge_meta.MetaRepository() + + +def main(): + "Display the meta dialog." + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/description.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/description.py new file mode 100644 index 0000000..8ce2a72 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/description.py @@ -0,0 +1,51 @@ +""" +This page is in the table of contents. +Description is a script to store a description of the profile. + +==Settings== +===Description Text=== +Default is 'Write your profile description here.' + +The suggested format is a description, followed by a link to a profile post or web page. + +==Example== +Example of using description follows below. + +> python description.py +This brings up the description dialog. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return DescriptionRepository() + + +class DescriptionRepository: + "A class to handle the description settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.meta_plugins.description.html', self) + description = 'Write your description of the profile here.\n\nSuggested format is a description, followed by a link to the profile post or web page.' + self.descriptionText = settings.TextSetting().getFromValue('Description Text:', self, description) + + +def main(): + "Display the file or directory dialog." + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/polyfile.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/polyfile.py new file mode 100644 index 0000000..6d377b7 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/meta_plugins/polyfile.py @@ -0,0 +1,45 @@ +""" +This page is in the table of contents. +Polyfile is a script to choose whether the skeinforge toolchain will operate on one file or all the files in a directory. + +==Settings== +===Polyfile Choice=== +Default is 'Execute File', + +====Execute File==== +When selected, the toolchain will operate on only the chosen file. + +====Execute All Unmodified Files in a Directory'==== +When selected, the toolchain will operate on all the unmodifed files in the directory that the chosen file is in. + +==Example== +Example of using polyfile follows below. + +> python polyfile.py +This brings up the polyfile dialog. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return skeinforge_polyfile.PolyfileRepository() + + +def main(): + "Display the file or directory dialog." + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile.py new file mode 100644 index 0000000..808d482 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile.py @@ -0,0 +1,116 @@ +""" +This page is in the table of contents. +Profile is a script to set the craft types setting for the skeinforge chain. + +Profile presents the user with a choice of the craft types in the profile_plugins folder. The chosen craft type is used to determine the craft type profile for the skeinforge chain. The default craft type is extrusion. + +The setting is the selection. If you hit 'Save and Close' the selection will be saved, if you hit 'Cancel' the selection will not be saved. + +To change the profile setting, in a shell in the profile folder type: +> python profile.py + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def addSubmenus( craftTypeName, menu, pluginFileName, pluginPath, profileRadioVar ): + "Add a tool plugin menu." + submenu = settings.Tkinter.Menu( menu, tearoff = 0 ) + menu.add_cascade( label = pluginFileName.capitalize(), menu = submenu ) + settings.ToolDialog().addPluginToMenu( submenu, pluginPath ) + submenu.add_separator() + pluginModule = skeinforge_profile.getCraftTypePluginModule( pluginFileName ) + profilePluginSettings = settings.getReadRepository( pluginModule.getNewRepository() ) + isSelected = ( craftTypeName == pluginFileName ) + for profileName in profilePluginSettings.profileList.value: + value = isSelected and profileName == profilePluginSettings.profileListbox.value + ProfileMenuRadio( pluginFileName, submenu, profileName, profileRadioVar, value ) + +def addToMenu( master, menu, repository, window ): + "Add a tool plugin menu." + ProfileMenuSaveListener( menu, window ) + +def addToProfileMenu( menu ): + "Add a profile menu." + settings.ToolDialog().addPluginToMenu(menu, archive.getUntilDot(archive.getSkeinforgePluginsPath('profile.py'))) + menu.add_separator() + directoryPath = skeinforge_profile.getPluginsDirectoryPath() + pluginFileNames = skeinforge_profile.getPluginFileNames() + craftTypeName = skeinforge_profile.getCraftTypeName() + profileRadioVar = settings.Tkinter.StringVar() + for pluginFileName in pluginFileNames: + addSubmenus( craftTypeName, menu, pluginFileName, os.path.join( directoryPath, pluginFileName ), profileRadioVar ) + +def getNewRepository(): + 'Get new repository.' + return skeinforge_profile.ProfileRepository() + + +class ProfileMenuRadio: + "A class to display a profile menu radio button." + def __init__( self, profilePluginFileName, menu, name, radioVar, value ): + "Create a profile menu radio." + self.activate = False + self.menu = menu + self.name = name + self.profileJoinName = profilePluginFileName + '.& /' + name + self.profilePluginFileName = profilePluginFileName + self.radioVar = radioVar + menu.add_radiobutton( label = name.replace('_', ' '), command = self.clickRadio, value = self.profileJoinName, variable = self.radioVar ) + self.menuLength = menu.index( settings.Tkinter.END ) + if value: + self.radioVar.set( self.profileJoinName ) + self.menu.invoke( self.menuLength ) + self.activate = True + + def clickRadio(self): + "Workaround for Tkinter bug, invoke and set the value when clicked." + if not self.activate: + return + self.radioVar.set( self.profileJoinName ) + pluginModule = skeinforge_profile.getCraftTypePluginModule( self.profilePluginFileName ) + profilePluginSettings = settings.getReadRepository( pluginModule.getNewRepository() ) + profilePluginSettings.profileListbox.value = self.name + settings.writeSettings( profilePluginSettings ) + profileSettings = skeinforge_profile.getReadProfileRepository() + plugins = profileSettings.craftRadios + for plugin in plugins: + plugin.value = ( plugin.name == self.profilePluginFileName ) + settings.writeSettings( profileSettings ) + skeinforge_profile.updateProfileSaveListeners() + + +class ProfileMenuSaveListener: + "A class to update a profile menu." + def __init__( self, menu, window ): + "Set the menu." + self.menu = menu + addToProfileMenu( menu ) + euclidean.addElementToListDictionaryIfNotThere( self, window, settings.globalProfileSaveListenerListTable ) + + def save(self): + "Profile has been saved and profile menu should be updated." + settings.deleteMenuItems( self.menu ) + addToProfileMenu( self.menu ) + + +def main(): + "Display the profile dialog." + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/__init__.py new file mode 100644 index 0000000..1121e8a --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 3 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/cutting.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/cutting.py new file mode 100644 index 0000000..0cc3c51 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/cutting.py @@ -0,0 +1,53 @@ +""" +This page is in the table of contents. +Cutting is a script to set the cutting profile for the skeinforge chain. + +The displayed craft sequence is the sequence in which the tools craft the model and export the output. + +On the cutting dialog, clicking the 'Add Profile' button will duplicate the selected profile and give it the name in the input field. For example, if laser is selected and the name laser_10mm is in the input field, clicking the 'Add Profile' button will duplicate laser and save it as laser_10mm. The 'Delete Profile' button deletes the selected profile. + +The profile selection is the setting. If you hit 'Save and Close' the selection will be saved, if you hit 'Cancel' the selection will not be saved. However; adding and deleting a profile is a permanent action, for example 'Cancel' will not bring back any deleted profiles. + +To change the cutting profile, in a shell in the profile_plugins folder type: +> python cutting.py + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftSequence(): + "Get the cutting craft sequence." + return 'chop preface outset multiply whittle drill lift flow feed home lash fillet limit unpause alteration export'.split() + +def getNewRepository(): + 'Get new repository.' + return CuttingRepository() + + +class CuttingRepository: + "A class to handle the cutting settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsSetCraftProfile( getCraftSequence(), 'end_mill', self, 'skeinforge_application.skeinforge_plugins.profile_plugins.cutting.html') + + +def main(): + "Display the export dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/extrusion.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/extrusion.py new file mode 100644 index 0000000..e32917b --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/extrusion.py @@ -0,0 +1,53 @@ +""" +This page is in the table of contents. +Extrusion is a script to set the extrusion profile for the skeinforge chain. + +The displayed craft sequence is the sequence in which the tools craft the model and export the output. + +On the extrusion dialog, clicking the 'Add Profile' button will duplicate the selected profile and give it the name in the input field. For example, if ABS is selected and the name ABS_black is in the input field, clicking the 'Add Profile' button will duplicate ABS and save it as ABS_black. The 'Delete Profile' button deletes the selected profile. + +The profile selection is the setting. If you hit 'Save and Close' the selection will be saved, if you hit 'Cancel' the selection will not be saved. However; adding and deleting a profile is a permanent action, for example 'Cancel' will not bring back any deleted profiles. + +To change the extrusion profile, in a shell in the profile_plugins folder type: +> python extrusion.py + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftSequence(): + 'Get the extrusion craft sequence.' + return 'carve scale bottom preface widen inset fill multiply speed temperature raft skirt chamber tower jitter clip smooth stretch skin comb cool hop wipe oozebane dwindle splodge home lash fillet limit unpause dimension alteration export'.split() + +def getNewRepository(): + 'Get new repository.' + return ExtrusionRepository() + + +class ExtrusionRepository: + 'A class to handle the export settings.' + def __init__(self): + 'Set the default settings, execute title & settings fileName.' + skeinforge_profile.addListsSetCraftProfile( getCraftSequence(), 'ABS', self, 'skeinforge_application.skeinforge_plugins.profile_plugins.extrusion.html') + + +def main(): + 'Display the export dialog.' + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == '__main__': + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/milling.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/milling.py new file mode 100644 index 0000000..fd682b7 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/milling.py @@ -0,0 +1,53 @@ +""" +This page is in the table of contents. +Milling is a script to set the milling profile for the skeinforge chain. + +The displayed craft sequence is the sequence in which the tools craft the model and export the output. + +On the milling dialog, clicking the 'Add Profile' button will duplicate the selected profile and give it the name in the input field. For example, if laser is selected and the name laser_10mm is in the input field, clicking the 'Add Profile' button will duplicate laser and save it as laser_10mm. The 'Delete Profile' button deletes the selected profile. + +The profile selection is the setting. If you hit 'Save and Close' the selection will be saved, if you hit 'Cancel' the selection will not be saved. However; adding and deleting a profile is a permanent action, for example 'Cancel' will not bring back any deleted profiles. + +To change the milling profile, in a shell in the profile_plugins folder type: +> python milling.py + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftSequence(): + "Get the milling craft sequence." + return 'chop preface outset mill multiply drill lift flow feed home lash fillet limit unpause alteration export'.split() + +def getNewRepository(): + 'Get new repository.' + return MillingRepository() + + +class MillingRepository: + "A class to handle the milling settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsSetCraftProfile( getCraftSequence(), 'end_mill', self, 'skeinforge_application.skeinforge_plugins.profile_plugins.milling.html') + + +def main(): + "Display the export dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/winding.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/winding.py new file mode 100644 index 0000000..4cbd165 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_plugins/profile_plugins/winding.py @@ -0,0 +1,53 @@ +""" +This page is in the table of contents. +Winding is a script to set the winding profile for the skeinforge chain. + +The displayed craft sequence is the sequence in which the tools craft the model and export the output. + +On the winding dialog, clicking the 'Add Profile' button will duplicate the selected profile and give it the name in the input field. For example, if laser is selected and the name laser_10mm is in the input field, clicking the 'Add Profile' button will duplicate laser and save it as laser_10mm. The 'Delete Profile' button deletes the selected profile. + +The profile selection is the setting. If you hit 'Save and Close' the selection will be saved, if you hit 'Cancel' the selection will not be saved. However; adding and deleting a profile is a permanent action, for example 'Cancel' will not bring back any deleted profiles. + +To change the winding profile, in a shell in the profile_plugins folder type: +> python winding.py + +""" + + +from __future__ import absolute_import +import __init__ +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import sys + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getCraftSequence(): + "Get the winding craft sequence." + return 'cleave preface coil flow feed home lash fillet limit unpause alteration export'.split() + +def getNewRepository(): + 'Get new repository.' + return WindingRepository() + + +class WindingRepository: + "A class to handle the winding settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsSetCraftProfile( getCraftSequence(), 'free_wire', self, 'skeinforge_application.skeinforge_plugins.profile_plugins.winding.html') + + +def main(): + "Display the export dialog." + if len(sys.argv) > 1: + writeOutput(' '.join(sys.argv[1 :])) + else: + settings.startMainLoopFromConstructor(getNewRepository()) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/__init__.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/__init__.py new file mode 100644 index 0000000..2dc8ddc --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/__init__.py @@ -0,0 +1,9 @@ +#This is required to workaround the python import bug where relative imports don't work if the module is imported as a main module. +import os +import sys +numberOfLevelsDeepInPackageHierarchy = 2 +packageFilePath = os.path.abspath(__file__) +for level in range( numberOfLevelsDeepInPackageHierarchy + 1 ): + packageFilePath = os.path.dirname( packageFilePath ) +if packageFilePath not in sys.path: + sys.path.insert( 0, packageFilePath ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_analyze.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_analyze.py new file mode 100644 index 0000000..26e30ae --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_analyze.py @@ -0,0 +1,82 @@ +""" +Analyze is a script to access the plugins which analyze a gcode file. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys +import traceback + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return AnalyzeRepository() + +def getPluginFileNames(): + "Get analyze plugin fileNames." + return archive.getPluginFileNamesFromDirectoryPath( getPluginsDirectoryPath() ) + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getAnalyzePluginsDirectoryPath() + +def writeOutput(fileName, fileNamePenultimate, fileNameSuffix, filePenultimateWritten, gcodeText=''): + "Analyze a gcode file." + gcodeText = archive.getTextIfEmpty(fileName, gcodeText) + pluginFileNames = getPluginFileNames() + window = None + for pluginFileName in pluginFileNames: + analyzePluginsDirectoryPath = getPluginsDirectoryPath() + pluginModule = archive.getModuleWithDirectoryPath( analyzePluginsDirectoryPath, pluginFileName ) + if pluginModule != None: + try: + newWindow = pluginModule.writeOutput(fileName, fileNamePenultimate, fileNameSuffix, + filePenultimateWritten, gcodeText ) + if newWindow != None: + window = newWindow + except: + print('Warning, the tool %s could not analyze the output.' % pluginFileName ) + print('Exception traceback in writeOutput in skeinforge_analyze:') + traceback.print_exc(file=sys.stdout) + return window + + +class AnalyzeRepository: + "A class to handle the analyze settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_utilities.skeinforge_analyze.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File for Analyze', self, '') + importantFileNames = ['skeiniso', 'skeinlayer', 'statistic'] + settings.getRadioPluginsAddPluginFrame( getPluginsDirectoryPath(), importantFileNames, getPluginFileNames(), self ) + self.executeTitle = 'Analyze' + + def execute(self): + "Analyze button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, [], self.fileNameInput.wasCancelled ) + for fileName in fileNames: + writeOutput( fileName, fileName ) + + +def main(): + "Write analyze output." + fileName = ' '.join(sys.argv[1 :]) + settings.startMainLoopFromWindow(writeOutput(fileName, fileName)) + + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_craft.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_craft.py new file mode 100644 index 0000000..d441373 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_craft.py @@ -0,0 +1,232 @@ +""" +Craft is a script to access the plugins which craft a gcode file. + +The plugin buttons which are commonly used are bolded and the ones which are rarely used have normal font weight. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret +from fabmetheus_utilities import archive +from fabmetheus_utilities import euclidean +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_analyze +from skeinforge_application.skeinforge_utilities import skeinforge_polyfile +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os +import sys +import time + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getChainText( fileName, procedure ): + "Get a crafted shape file." + text='' + if fileName.endswith('.gcode') or fileName.endswith('.svg'): + text = archive.getFileText(fileName) + procedures = getProcedures( procedure, text ) + return getChainTextFromProcedures( fileName, procedures, text ) + +def getChainTextFromProcedures(fileName, procedures, text): + 'Get a crafted shape file from a list of procedures.' + lastProcedureTime = time.time() + for procedure in procedures: + craftModule = getCraftModule(procedure) + if craftModule != None: + text = craftModule.getCraftedText(fileName, text) + if text == '': + print('Warning, the text was not recognized in getChainTextFromProcedures in skeinforge_craft for') + print(fileName) + return '' + if gcodec.isProcedureDone( text, procedure ): + print('%s procedure took %s.' % (procedure.capitalize(), euclidean.getDurationString(time.time() - lastProcedureTime))) + lastProcedureTime = time.time() + return text + +def getCraftModule(pluginName): + 'Get craft module.' + return archive.getModuleWithDirectoryPath(getPluginsDirectoryPath(), pluginName) + +def getCraftPreferences(pluginName): + 'Get craft preferences.' + return settings.getReadRepository(getCraftModule(pluginName).getNewRepository()).preferences + +def getCraftValue(preferenceName, preferences): + "Get craft preferences value." + for preference in preferences: + if preference.name.startswith(preferenceName): + return preference.value + return None + +def getLastModule(): + "Get the last tool." + craftSequence = getReadCraftSequence() + if len( craftSequence ) < 1: + return None + return getCraftModule( craftSequence[-1] ) + +def getNewRepository(): + 'Get new repository.' + return CraftRepository() + +def getPluginFileNames(): + "Get craft plugin fileNames." + craftSequence = getReadCraftSequence() + craftSequence.sort() + return craftSequence + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getCraftPluginsDirectoryPath() + +def getProcedures( procedure, text ): + "Get the procedures up to and including the given procedure." + craftSequence = getReadCraftSequence() + sequenceIndexFromProcedure = 0 + if procedure in craftSequence: + sequenceIndexFromProcedure = craftSequence.index(procedure) + sequenceIndexPlusOneFromText = getSequenceIndexPlusOneFromText(text) + return craftSequence[ sequenceIndexPlusOneFromText : sequenceIndexFromProcedure + 1 ] + +def getReadCraftSequence(): + "Get profile sequence." + return skeinforge_profile.getCraftTypePluginModule().getCraftSequence() + +def getSequenceIndexPlusOneFromText(fileText): + "Get the profile sequence index of the file plus one. Return zero if the procedure is not in the file" + craftSequence = getReadCraftSequence() + for craftSequenceIndex in xrange( len( craftSequence ) - 1, - 1, - 1 ): + procedure = craftSequence[ craftSequenceIndex ] + if gcodec.isProcedureDone( fileText, procedure ): + return craftSequenceIndex + 1 + return 0 + +def writeChainTextWithNounMessage(fileName, procedure, shouldAnalyze=True): + 'Get and write a crafted shape file.' + print('') + print('The %s tool is parsing the file:' % procedure) + print(os.path.basename(fileName)) + print('') + startTime = time.time() + fileNameSuffix = fileName[: fileName.rfind('.')] + '_' + procedure + '.gcode' + craftText = getChainText(fileName, procedure) + if craftText == '': + print('Warning, there was no text output in writeChainTextWithNounMessage in skeinforge_craft for:') + print(fileName) + return + archive.writeFileText(fileNameSuffix, craftText) + window = None + if shouldAnalyze: + window = skeinforge_analyze.writeOutput(fileName, fileNameSuffix, fileNameSuffix, True, craftText) + print('') + print('The %s tool has created the file:' % procedure) + print(fileNameSuffix) + print('') + print('It took %s to craft the file.' % euclidean.getDurationString(time.time() - startTime)) + return window + +def writeOutput(fileName, shouldAnalyze=True): + "Craft a gcode file with the last module." + pluginModule = getLastModule() + if pluginModule != None: + return pluginModule.writeOutput(fileName, shouldAnalyze) + +def writeSVGTextWithNounMessage(fileName, repository, shouldAnalyze=True): + 'Get and write an svg text and print messages.' + print('') + print('The %s tool is parsing the file:' % repository.lowerName) + print(os.path.basename(fileName)) + print('') + startTime = time.time() + fileNameSuffix = fileName[: fileName.rfind('.')] + '_' + repository.lowerName + '.svg' + craftText = getChainText(fileName, repository.lowerName) + if craftText == '': + return + archive.writeFileText(fileNameSuffix, craftText) + print('') + print('The %s tool has created the file:' % repository.lowerName) + print(fileNameSuffix) + print('') + print('It took %s to craft the file.' % euclidean.getDurationString(time.time() - startTime)) + if shouldAnalyze: + settings.getReadRepository(repository) + settings.openSVGPage(fileNameSuffix, repository.svgViewer.value) + + +class CraftRadioButtonsSaveListener: + "A class to update the craft radio buttons." + def addToDialog( self, gridPosition ): + "Add this to the dialog." + euclidean.addElementToListDictionaryIfNotThere( self, self.repository.repositoryDialog, settings.globalProfileSaveListenerListTable ) + self.gridPosition = gridPosition.getCopy() + self.gridPosition.row = gridPosition.rowStart + self.gridPosition.increment() + self.setRadioButtons() + + def getFromRadioPlugins( self, radioPlugins, repository ): + "Initialize." + self.name = 'CraftRadioButtonsSaveListener' + self.radioPlugins = radioPlugins + self.repository = repository + repository.displayEntities.append(self) + return self + + def save(self): + "Profile has been saved and craft radio plugins should be updated." + self.setRadioButtons() + + def setRadioButtons(self): + "Profile has been saved and craft radio plugins should be updated." + activeRadioPlugins = [] + craftSequence = skeinforge_profile.getCraftTypePluginModule().getCraftSequence() + gridPosition = self.gridPosition.getCopy() + isRadioPluginSelected = False + settings.getReadRepository(self.repository) + for radioPlugin in self.radioPlugins: + if radioPlugin.name in craftSequence: + activeRadioPlugins.append(radioPlugin) + radioPlugin.incrementGridPosition(gridPosition) + if radioPlugin.value: + radioPlugin.setSelect() + isRadioPluginSelected = True + else: + radioPlugin.radiobutton.grid_remove() + if not isRadioPluginSelected: + radioPluginNames = self.repository.importantFileNames + [activeRadioPlugins[0].name] + settings.getSelectedRadioPlugin(radioPluginNames , activeRadioPlugins).setSelect() + self.repository.pluginFrame.update() + + +class CraftRepository: + "A class to handle the craft settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_utilities.skeinforge_craft.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Craft', self, '') + self.importantFileNames = ['carve', 'chop', 'feed', 'flow', 'lift', 'raft', 'speed'] + allCraftNames = archive.getPluginFileNamesFromDirectoryPath(getPluginsDirectoryPath()) + self.radioPlugins = settings.getRadioPluginsAddPluginFrame(getPluginsDirectoryPath(), self.importantFileNames, allCraftNames, self) + CraftRadioButtonsSaveListener().getFromRadioPlugins(self.radioPlugins, self) + self.executeTitle = 'Craft' + + def execute(self): + "Craft button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, [], self.fileNameInput.wasCancelled ) + for fileName in fileNames: + writeOutput(fileName) + + +def main(): + "Write craft output." + writeOutput(' '.join(sys.argv[1 :]), False) + +if __name__ == "__main__": + main() diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_help.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_help.py new file mode 100644 index 0000000..6dad5c7 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_help.py @@ -0,0 +1,62 @@ +""" +Help has buttons and menu items to open help, blog and forum pages in your primary browser. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return HelpRepository() + + +class HelpRepository: + "A class to handle the help settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_utilities.skeinforge_help.html', self) + announcementsText = '- Announcements - ' + announcementsLabel = settings.LabelDisplay().getFromName(announcementsText, self ) + announcementsLabel.columnspan = 6 + settings.LabelDisplay().getFromName('Fabmetheus Blog, Announcements & Questions:', self ) + settings.HelpPage().getFromNameAfterHTTP('fabmetheus.blogspot.com/', 'Fabmetheus Blog', self ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Documentation -', self ) + settings.LabelDisplay().getFromName('Local Documentation Table of Contents: ', self ) + settings.HelpPage().getFromNameSubName('Contents', self, 'contents.html') + settings.LabelDisplay().getFromName('Wiki Manual with Pictures & Charts: ', self ) + settings.HelpPage().getFromNameAfterHTTP('fabmetheus.crsndoo.com/wiki/index.php/Skeinforge', 'Wiki Manual', self ) + settings.LabelDisplay().getFromName('Skeinforge Overview: ', self ) + settings.HelpPage().getFromNameSubName('Skeinforge Overview', self, 'skeinforge_application.skeinforge.html') + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Search -', self ) + settings.LabelDisplay().getFromName('Reprap Search:', self ) + settings.HelpPage().getFromNameAfterHTTP('members.axion.net/~enrique/search_reprap.html', 'Reprap Search', self ) + settings.LabelDisplay().getFromName('Skeinforge Search:', self ) + settings.HelpPage().getFromNameAfterHTTP('members.axion.net/~enrique/search_skeinforge.html', 'Skeinforge Search', self ) + settings.LabelDisplay().getFromName('Web Search:', self ) + settings.HelpPage().getFromNameAfterHTTP('members.axion.net/~enrique/search_web.html', 'Web Search', self ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Troubleshooting -', self ) + settings.LabelDisplay().getFromName('Skeinforge Forum:', self) + settings.HelpPage().getFromNameAfterHTTP('forums.reprap.org/list.php?154', ' Skeinforge Forum ', self ) + settings.LabelSeparator().getFromRepository(self) + self.version = settings.LabelDisplay().getFromName('Version: ' + archive.getFileText(archive.getVersionFileName()), self) + self.wikiManualPrimary = settings.BooleanSetting().getFromValue('Wiki Manual Primary', self, True ) + self.wikiManualPrimary.setUpdateFunction( self.save ) + + def save(self): + "Write the entities." + settings.writeSettingsPrintMessage(self) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_meta.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_meta.py new file mode 100644 index 0000000..b03b9f1 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_meta.py @@ -0,0 +1,41 @@ +""" +Meta is a script to access the plugins which handle meta information. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import gcodec +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile +import os + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getNewRepository(): + 'Get new repository.' + return MetaRepository() + +def getPluginFileNames(): + "Get meta plugin file names." + return archive.getPluginFileNamesFromDirectoryPath( getPluginsDirectoryPath() ) + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getSkeinforgePluginsPath('meta_plugins') + + +class MetaRepository: + "A class to handle the meta settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_utilities.skeinforge_meta.html', self) + importantFileNames = ['polyfile'] + settings.getRadioPluginsAddPluginFrame( getPluginsDirectoryPath(), importantFileNames, getPluginFileNames(), self ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_polyfile.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_polyfile.py new file mode 100644 index 0000000..03f4c41 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_polyfile.py @@ -0,0 +1,70 @@ +""" +Polyfile is a script to choose whether the skeinforge toolchain will operate on one file or all the files in a directory. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive +from fabmetheus_utilities import settings +from skeinforge_application.skeinforge_utilities import skeinforge_profile + + +__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' +__date__ = '$Date: 2008/21/04 $' +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +def getFileOrDirectoryTypes( fileName, fileTypes, wasCancelled ): + "Get the gcode files in the directory the file is in if directory setting is true. Otherwise, return the file in a list." + if isEmptyOrCancelled( fileName, wasCancelled ): + return [] + if isDirectorySetting(): + return archive.getFilesWithFileTypesWithoutWords( fileTypes, [], fileName ) + return [ fileName ] + +def getFileOrDirectoryTypesUnmodifiedGcode(fileName, fileTypes, wasCancelled): + "Get the gcode files in the directory the file is in if directory setting is true. Otherwise, return the file in a list." + if isEmptyOrCancelled(fileName, wasCancelled): + return [] + if isDirectorySetting(): + return archive.getFilesWithFileTypesWithoutWords(fileTypes, [], fileName) + return [fileName] + +def getFileOrGcodeDirectory( fileName, wasCancelled, words = [] ): + "Get the gcode files in the directory the file is in if directory setting is true. Otherwise, return the file in a list." + if isEmptyOrCancelled( fileName, wasCancelled ): + return [] + if isDirectorySetting(): + dotIndex = fileName.rfind('.') + if dotIndex < 0: + print('The file name should have a suffix, like myfile.xml.') + print('Since the file name does not have a suffix, nothing will be done') + suffix = fileName[ dotIndex + 1 : ] + return archive.getFilesWithFileTypeWithoutWords( suffix, words, fileName ) + return [ fileName ] + +def getNewRepository(): + 'Get new repository.' + return PolyfileRepository() + +def isDirectorySetting(): + "Determine if the directory setting is true." + return settings.getReadRepository( PolyfileRepository() ).directorySetting.value + +def isEmptyOrCancelled( fileName, wasCancelled ): + "Determine if the fileName is empty or the dialog was cancelled." + return str(fileName) == '' or str(fileName) == '()' or wasCancelled + + +class PolyfileRepository: + "A class to handle the polyfile settings." + def __init__(self): + "Set the default settings, execute title & settings fileName." + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_utilities.skeinforge_polyfile.html', self) + self.directoryOrFileChoiceLabel = settings.LabelDisplay().getFromName('Directory or File Choice: ', self ) + directoryLatentStringVar = settings.LatentStringVar() + self.directorySetting = settings.Radio().getFromRadio( directoryLatentStringVar, 'Execute All Unmodified Files in a Directory', self, False ) + self.fileSetting = settings.Radio().getFromRadio( directoryLatentStringVar, 'Execute File', self, True ) diff --git a/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_profile.py b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_profile.py new file mode 100644 index 0000000..b26a208 --- /dev/null +++ b/SkeinPyPy_NewUI/skeinforge_application/skeinforge_utilities/skeinforge_profile.py @@ -0,0 +1,35 @@ +""" +Profile is a script to set the craft types setting for the skeinforge chain. + +Profile presents the user with a choice of the craft types in the profile_plugins folder. The chosen craft type is used to determine the craft type profile for the skeinforge chain. The default craft type is extrusion. + +""" + +from __future__ import absolute_import +#Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module. +import __init__ + +from fabmetheus_utilities import archive + +def getCraftTypeName(): + return 'extrusion' + +def getProfileName(craftTypeName): + return 'SkeinPyPy profile:' + craftTypeName + +def addListsToCraftTypeRepository(fileNameHelp, repository): + print('addListsToCraftTypeRepository:', fileNameHelp, repository) + repository.name = fileNameHelp.split('.')[-2] + repository.preferences = [] + +def getCraftTypePluginModule( craftTypeName = ''): + "Get the craft type plugin module" + if craftTypeName == '': + craftTypeName = getCraftTypeName() + profilePluginsDirectoryPath = getPluginsDirectoryPath() + return archive.getModuleWithDirectoryPath( profilePluginsDirectoryPath, craftTypeName ) + +def getPluginsDirectoryPath(): + "Get the plugins directory path." + return archive.getSkeinforgePluginsPath('profile_plugins') + diff --git a/SkeinPyPy_NewUI/skeinpypy.py b/SkeinPyPy_NewUI/skeinpypy.py new file mode 100644 index 0000000..a4f3afa --- /dev/null +++ b/SkeinPyPy_NewUI/skeinpypy.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +""" +This page is in the table of contents. +==Overview== +===Introduction=== +SkeinPyPy is a GPL tool chain to forge a gcode skein for a model. Based on Skeinforge. + +The slicing code is the same as Skeinforge. But the UI has been revamped to be... sane. + +""" + +from __future__ import absolute_import + +from optparse import OptionParser +from skeinforge_application.skeinforge_utilities import skeinforge_craft +from newui import mainWindow +import os +import sys +import platform +import subprocess + +__author__ = 'Daid' +__credits__ = """ +Enrique Perez (perez_enrique@yahoo.com) +Adrian Bowyer +Brendan Erwin +Greenarrow +Ian England +John Gilmore +Jonwise +Kyle Corbitt +Michael Duffin +Marius Kintel +Nophead +PJR +Reece.Arnott +Wade +Xsainnz +Zach Hoeken + +Organizations: +Art of Illusion """ + +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + +def getPyPyExe(): + "Return the path to the pypy executable if we can find it. Else return False" + if platform.system() == "Windows": + pypyExe = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../pypy/pypy.exe")); + else: + pypyExe = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../pypy/bin/pypy")); + if os.path.exists(pypyExe): + return pypyExe + pypyExe = "/bin/pypy"; + if os.path.exists(pypyExe): + return pypyExe + pypyExe = "/usr/bin/pypy"; + if os.path.exists(pypyExe): + return pypyExe + pypyExe = "/usr/local/bin/pypy"; + if os.path.exists(pypyExe): + return pypyExe + return False + +def runSkein(fileNames): + "Run the slicer on the files. If we are running with PyPy then just do the slicing action. If we are running as Python, try to find pypy." + pypyExe = getPyPyExe() + for fileName in fileNames: + if platform.python_implementation() == "PyPy": + skeinforge_craft.writeOutput(fileName) + elif pypyExe == False: + print "************************************************" + print "* Failed to find pypy, so slicing with python! *" + print "************************************************" + skeinforge_craft.writeOutput(fileName) + print "************************************************" + print "* Failed to find pypy, so sliced with python! *" + print "************************************************" + else: + subprocess.call([pypyExe, __file__, fileName]) + +def main(): + parser = OptionParser() + (options, args) = parser.parse_args() + sys.argv = [sys.argv[0]] + args + if len( args ) > 0: + runSkein(args) + else: + mainWindow.main() + +if __name__ == '__main__': + main() +