Merge commit '25687ea3b8a2583ba30b27c30d78672c7135374a'

This commit is contained in:
Lawrence Johnston 2012-03-22 19:18:47 -07:00
commit 8a2085762f
29 changed files with 359 additions and 227 deletions

View file

@ -1,2 +1,2 @@
This SkeinPyPy version is based in Skeinforge: 49
This SkeinPyPy version is based in Skeinforge: 50

View file

@ -363,41 +363,41 @@ def compareSegmentLength( endpoint, otherEndpoint ):
return - 1
return 0
def concatenateRemovePath( connectedPaths, pathIndex, paths, pixelDictionary, segments, width ):
def concatenateRemovePath(connectedPaths, pathIndex, paths, pixelDictionary, segments, sharpestProduct, width):
'Get connected paths from paths.'
bottomSegment = segments[ pathIndex ]
path = paths[ pathIndex ]
bottomSegment = segments[pathIndex]
path = paths[pathIndex]
if bottomSegment == None:
connectedPaths.append(path)
return
endpoints = getEndpointsFromSegments( segments[ pathIndex + 1 : ] )
endpoints = getEndpointsFromSegments(segments[pathIndex + 1 :])
bottomSegmentEndpoint = bottomSegment[0]
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath( endpoints, bottomSegmentEndpoint.path, pixelDictionary, width )
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
bottomSegmentEndpoint = bottomSegment[1]
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath( endpoints, bottomSegmentEndpoint.path, pixelDictionary, width )
nextEndpoint = bottomSegmentEndpoint.getClosestMissCheckEndpointPath(endpoints, bottomSegmentEndpoint.path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
connectedPaths.append(path)
return
if len( bottomSegmentEndpoint.path ) > 0 and len( nextEndpoint.path ) > 0:
if len(bottomSegmentEndpoint.path) > 0 and len(nextEndpoint.path) > 0:
bottomEnd = bottomSegmentEndpoint.path[-1]
nextBegin = nextEndpoint.path[-1]
nextMinusBottomNormalized = getNormalized( nextBegin - bottomEnd )
nextMinusBottomNormalized = getNormalized(nextBegin - bottomEnd)
if len( bottomSegmentEndpoint.path ) > 1:
bottomPenultimate = bottomSegmentEndpoint.path[-2]
if getDotProduct( getNormalized( bottomPenultimate - bottomEnd ), nextMinusBottomNormalized ) > 0.9:
if getDotProduct(getNormalized(bottomPenultimate - bottomEnd), nextMinusBottomNormalized) > 0.99:
connectedPaths.append(path)
return
if len( nextEndpoint.path ) > 1:
nextPenultimate = nextEndpoint.path[-2]
if getDotProduct( getNormalized( nextPenultimate - nextBegin ), - nextMinusBottomNormalized ) > 0.9:
if getDotProduct(getNormalized(nextPenultimate - nextBegin), - nextMinusBottomNormalized) > 0.99:
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 )
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.'
@ -668,18 +668,18 @@ def getConcatenatedList(originalLists):
concatenatedList += originalList
return concatenatedList
def getConnectedPaths( paths, pixelDictionary, width ):
def getConnectedPaths(paths, pixelDictionary, sharpestProduct, 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] )
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, sharpestProduct, width)
connectedPaths.append(paths[-1])
return connectedPaths
def getCrossProduct(firstComplex, secondComplex):
@ -1327,7 +1327,7 @@ def getPathLength(path):
pathLength += abs(firstPoint - secondPoint)
return pathLength
def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, width):
def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, sharpestProduct, width):
'Get paths from endpoints.'
if len(endpoints) < 2:
return []
@ -1343,7 +1343,7 @@ def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, w
path = []
paths = [path]
if len(endpoints) > 1:
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, width)
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
if nextEndpoint != None:
if abs(nextEndpoint.point - endpointFirst.point) < abs(nextEndpoint.point - otherEndpoint.point):
endpointFirst = endpointFirst.otherEndpoint
@ -1359,7 +1359,7 @@ def getPathsFromEndpoints(endpoints, maximumConnectionLength, pixelDictionary, w
if len(endpointTable.values()[0]) < 2:
return []
endpoints = getSquareValuesFromPoint(endpointTable, otherEndpoint.point * oneOverEndpointWidth)
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, width)
nextEndpoint = otherEndpoint.getClosestMiss(endpoints, path, pixelDictionary, sharpestProduct, width)
if nextEndpoint == None:
path = []
paths.append(path)
@ -2096,7 +2096,7 @@ class Endpoint:
closestEndpoint = endpoint
return closestEndpoint
def getClosestMiss(self, endpoints, path, pixelDictionary, width):
def getClosestMiss(self, endpoints, path, pixelDictionary, sharpestProduct, width):
'Get the closest endpoint which the segment to that endpoint misses the other extrusions.'
pathMaskTable = {}
smallestDistance = 987654321.0
@ -2115,7 +2115,7 @@ class 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
isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
if not isOverlappingSelf:
if len(path) > 2:
segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag)
@ -2132,14 +2132,14 @@ class Endpoint:
return endpoint
return None
def getClosestMissCheckEndpointPath( self, endpoints, path, pixelDictionary, width ):
def getClosestMissCheckEndpointPath(self, endpoints, path, pixelDictionary, sharpestProduct, 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 )
addSegmentToPixelTable(penultimatePoint, self.point, pathMaskTable, 0, 0, width)
penultimateMinusPoint = penultimatePoint - self.point
if abs(penultimateMinusPoint) > 0.0:
penultimateMinusPoint /= abs(penultimateMinusPoint)
@ -2151,27 +2151,27 @@ class 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
isOverlappingSelf = getDotProduct(penultimateMinusPoint, normalizedSegment) > sharpestProduct
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 ):
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:
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 ):
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 )
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 ):
addSegmentToPixelTable(self.point, endpoint.point, segmentTable, 0, 0, width)
if not isPixelTableIntersecting(pixelDictionary, segmentTable, totalMaskTable):
return endpoint
return None

View file

@ -311,7 +311,7 @@ def getDescendingAreaLoops(allPoints, corners, importRadius):
sortLoopsInOrderOfArea(True, loops)
pointDictionary = {}
for loop in loops:
if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.3:
if len(loop) > 2 and getOverlapRatio(loop, pointDictionary) < 0.3 and intercircle.getIsLarge(loop, importRadius):
intercircle.directLoop(not euclidean.getIsInFilledRegion(descendingAreaLoops, loop[0]), loop)
descendingAreaLoops.append(loop)
addLoopToPointTable(loop, pointDictionary)
@ -441,11 +441,18 @@ def getLoopsFromCorrectMesh( edges, faces, vertexes, z ):
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)
warning = False
for idx in xrange(0, len(loops)-1):
loop = loops[idx]
p0 = loop[-1]
for p1 in loop:
if euclidean.isLineIntersectingLoops(loops[idx+1:], p0, p1):
print('Warning, the triangle mesh slice intersects itself in getLoopsFromCorrectMesh in triangle_mesh.')
print('Model error(intersect): (%f, %f, %f) (%f, %f, %f)' % (p0.real, p0.imag, z, p1.real, p1.imag, z))
warning = True
p0 = p1
if warning:
return []
return loops
# untouchables = []

View file

@ -427,7 +427,7 @@ def getLargestInsetLoopFromLoopRegardless( loop, radius ):
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('Warning, there should always be a largestInsetLoop in getLargestInsetLoopFromLoopRegardless in intercircle.')
print(loop)
return loop

View file

@ -84,6 +84,10 @@ def calcSupportDistanceRatio(setting):
distance = float(profile.getProfileSetting('support_distance'))
return distance / edgeWidth
def calculateMultiplyDistance(setting):
edgeWidth = calculateEdgeWidth(setting)
return 10.0 / edgeWidth
def getSkeinPyPyProfileInformation():
return {
'carve': {
@ -121,10 +125,11 @@ def getSkeinPyPyProfileInformation():
'Turn_Extruder_Off_at_Start_Up': DEFSET,
},'widen': {
'Activate_Widen': DEFSET,
'Widen_Width_over_Edge_Width_ratio': DEFSET,
},'inset': {
'Add_Custom_Code_for_Temperature_Reading': DEFSET,
'Infill_in_Direction_of_Bridge': "True",
'Infill_Width_over_Thickness_ratio': DEFSET,
'Infill_Width': storedPreference("nozzle_size"),
'Loop_Order_Choice': DEFSET,
'Overlap_Removal_Width_over_Perimeter_Width_ratio': DEFSET,
'Turn_Extruder_Heater_Off_at_Shut_Down': DEFSET,
@ -153,6 +158,7 @@ def getSkeinPyPyProfileInformation():
'Infill_Perimeter_Overlap_ratio': storedPercentSetting('fill_overlap'),
'Infill_Solidity_ratio': storedPercentSetting('fill_density'),
'Infill_Width': storedPreference("nozzle_size"),
'Sharpest_Angle_degrees': DEFSET,
'Solid_Surface_Thickness_layers': calculateSolidLayerCount,
'Start_From_Choice': DEFSET,
'Surrounding_Angle_degrees': DEFSET,
@ -164,7 +170,7 @@ def getSkeinPyPyProfileInformation():
'Number_of_Columns_integer': storedSetting('model_multiply_x'),
'Number_of_Rows_integer': storedSetting('model_multiply_y'),
'Reverse_Sequence_every_Odd_Layer': DEFSET,
'Separation_over_Perimeter_Width_ratio': DEFSET,
'Separation_over_Perimeter_Width_ratio': calculateMultiplyDistance,
},'speed': {
'Activate_Speed': "True",
'Add_Flow_Rate': "True",

View file

@ -218,9 +218,7 @@ class SVGWriter:
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.svgElement.getElementNodeByID('controls').removeFromIDNameParent()
self.graphicsElementNode.removeFromIDNameParent()
self.addOriginalAsComment(elementNode)
return documentNode.__repr__()

View file

@ -1 +1 @@
12.02.10
12.03.14

View file

@ -28,27 +28,36 @@ class advancedConfigWindow(configBase.configWindowBase):
configBase.TitleRow(left, "Cool")
c = configBase.SettingRow(left, "Minimum feedrate (mm/s)", 'cool_min_feedrate', '5', 'The minimal layer time can cause the print to slow down so much it starts to ooze. The minimal feedrate protects against this. Even if a print gets slown down it will never be slower then this minimal feedrate.')
validators.validFloat(c, 0.0)
configBase.TitleRow(left, "Joris")
c = configBase.SettingRow(left, "Joris the outer edge", 'joris', False, '[Joris] is a code name for smoothing out the Z move of the outer edge. This will create a steady Z increase over the whole print. It is intended to be used with a single walled wall thickness to make cups/vases.')
configBase.TitleRow(left, "Raft (if enabled)")
c = configBase.SettingRow(left, "Raft extra margin (mm)", 'raft_margin', '3.0', 'If the raft is enabled, this is the extra raft area around the object which is also rafted. Increasing this margin will create a stronger raft.')
validators.validFloat(c, 0.0)
c = configBase.SettingRow(left, "Raft base material amount (%)", 'raft_base_material_amount', '100', 'The base layer is the first layer put down as a raft. This layer has thick strong lines and is put firmly on the bed to prevent warping. This setting adjust the amount of material used for the base layer.')
validators.validFloat(c, 0.0)
c = configBase.SettingRow(left, "Raft interface material amount (%)", 'raft_interface_material_amount', '100', 'The interface layer is a weak thin layer between the base layer and the printed object. It is designed to has little material to make it easy to break the base off the printed object. This setting adjusts the amount of material used for the interface layer.')
validators.validFloat(c, 0.0)
configBase.TitleRow(right, "Infill")
c = configBase.SettingRow(right, "Infill pattern", 'infill_type', ['Line', 'Grid Circular', 'Grid Hexagonal', 'Grid Rectangular'], 'Pattern of the none-solid infill. Line is default, but grids can provide a strong print.')
c = configBase.SettingRow(right, "Solid infill top", 'solid_top', True, 'Create a solid top surface, if set to false the top is filled with the fill percentage. Useful for cups/vases.')
c = configBase.SettingRow(right, "Infill overlap (%)", 'fill_overlap', '15', 'Amount of overlap between the infill and the walls. There is a slight overlap with the walls and the infill so the walls connect firmly to the infill.')
validators.validFloat(c, 0.0)
configBase.TitleRow(right, "Support")
c = configBase.SettingRow(right, "Support material amount (%)", 'support_rate', '100', 'Amount of material used for support, less material gives a weaker support structure which is easier to remove.')
validators.validFloat(c, 0.0)
c = configBase.SettingRow(right, "Support distance from object (mm)", 'support_distance', '0.5', 'Distance between the support structure and the object.')
validators.validFloat(c, 0.0)
configBase.TitleRow(right, "Bridge")
c = configBase.SettingRow(right, "Bridge speed (%)", 'bridge_speed', '100', 'Speed at which bridges are printed, compared to normal printing speed.')
validators.validFloat(c, 0.0)
c = configBase.SettingRow(right, "Bridge material (%)", 'bridge_material_amount', '100', 'Amount of material used for bridges, increase go extrude more material when printing a bridge.')
validators.validFloat(c, 0.0)
main.Fit()
self.Fit()

View file

@ -123,7 +123,7 @@ class SettingRow():
self.ctrl.Bind(wx.EVT_CHECKBOX, self.OnSettingChange)
else:
self.ctrl = wx.ComboBox(panel, -1, getSettingFunc(configName), choices=defaultValue, style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.ctrl.Bind(wx.EVT_TEXT, self.OnSettingChange)
self.ctrl.Bind(wx.EVT_COMBOBOX, self.OnSettingChange)
sizer.Add(self.label, (x,y), flag=wx.ALIGN_CENTER_VERTICAL)
sizer.Add(self.ctrl, (x,y+1), flag=wx.ALIGN_BOTTOM|wx.EXPAND)

View file

@ -1,3 +1,6 @@
from __future__ import absolute_import
import __init__
import sys
import math
import threading
@ -7,9 +10,18 @@ import os
from newui import util3d
class gcode():
def __init__(self, filename):
print os.stat(filename).st_size
f = open(filename, 'r')
def __init__(self):
self.regMatch = {}
self.layerCount = 0
self.pathList = []
self.extrusionAmount = 0
self.totalMoveTimeMinute = 0
self.progressCallback = None
def load(self, filename):
fileSize = os.stat(filename).st_size
filePos = 0
gcodeFile = open(filename, 'r')
pos = util3d.Vector3()
posOffset = util3d.Vector3()
currentE = 0.0
@ -23,10 +35,13 @@ class gcode():
pathType = 'CUSTOM';
layerNr = 0; #Note layer 0 will be the start code.
startCodeDone = False
self.stepsPerE = 865.888
currentPath = {'type': 'move', 'pathType': pathType, 'list': [pos.copy()], 'layerNr': layerNr}
currentPath['list'][-1].e = totalExtrusion
for line in f:
for line in gcodeFile:
if filePos != gcodeFile.tell():
filePos = gcodeFile.tell()
if self.progressCallback != None:
self.progressCallback(float(filePos) / float(fileSize))
if line.startswith(';TYPE:'):
pathType = line[6:].strip()
if pathType != "CUSTOM":
@ -128,9 +143,6 @@ class gcode():
elif M == 84: #Disable step drivers
pass
elif M == 92: #Set steps per unit
e = self.getCodeFloat(line, 'E')
if e is not None:
self.stepsPerE = e
pass
elif M == 104: #Set temperature, no wait
pass
@ -142,10 +154,13 @@ class gcode():
pass
elif M == 108: #Extruder RPM (these should not be in the final GCode, but they are)
pass
elif M == 109: #Set temperature, wait
pass
elif M == 113: #Extruder PWM (these should not be in the final GCode, but they are)
pass
else:
print "Unknown M code:" + str(M)
gcodeFile.close()
self.layerCount = layerNr
self.pathList = pathList
self.extrusionAmount = maxExtrusion
@ -153,8 +168,10 @@ class gcode():
print "Extruded a total of: %d mm of filament" % (self.extrusionAmount)
print "Estimated print duration: %.2f minutes" % (self.totalMoveTimeMinute)
def getCodeInt(self, str, id):
m = re.search(id + '([^\s]+)', str)
def getCodeInt(self, line, code):
if code not in self.regMatch:
self.regMatch[code] = re.compile(code + '([^\s]+)')
m = self.regMatch[code].search(line)
if m == None:
return None
try:
@ -162,8 +179,10 @@ class gcode():
except:
return None
def getCodeFloat(self, str, id):
m = re.search(id + '([^\s]+)', str)
def getCodeFloat(self, line, code):
if code not in self.regMatch:
self.regMatch[code] = re.compile(code + '([^\s]+)')
m = self.regMatch[code].search(line)
if m == None:
return None
try:
@ -171,3 +190,7 @@ class gcode():
except:
return None
if __name__ == '__main__':
for filename in sys.argv[1:]:
gcode().load(filename)

View file

@ -158,30 +158,6 @@ class mainWindow(configBase.configWindowBase):
nb.AddPage(alterationPanel.alterationPanel(nb), "Start/End-GCode")
(left, right) = self.CreateConfigTab(nb, '3D Model')
configBase.TitleRow(left, "Scale")
c = configBase.SettingRow(left, "Scale", 'model_scale', '1.0', '')
validators.validFloat(c, 0.01)
configBase.settingNotify(c, self.preview3d.updateModelTransform)
configBase.TitleRow(left, "Flip")
c = configBase.SettingRow(left, "Flip X", 'flip_x', False, '')
configBase.settingNotify(c, self.preview3d.updateModelTransform)
c = configBase.SettingRow(left, "Flip Y", 'flip_y', False, '')
configBase.settingNotify(c, self.preview3d.updateModelTransform)
c = configBase.SettingRow(left, "Flip Z", 'flip_z', False, '')
configBase.settingNotify(c, self.preview3d.updateModelTransform)
configBase.TitleRow(right, "Rotate")
c = configBase.SettingRow(right, "Rotate (deg)", 'model_rotate_base', '0', '')
validators.validFloat(c)
configBase.settingNotify(c, self.preview3d.updateModelTransform)
configBase.TitleRow(right, "Multiply")
c = configBase.SettingRow(right, "Multiple X", 'model_multiply_x', '1', '')
validators.validInt(c)
configBase.settingNotify(c, self.preview3d.updateModelTransform)
c = configBase.SettingRow(right, "Multiple Y", 'model_multiply_y', '1', '')
validators.validInt(c)
configBase.settingNotify(c, self.preview3d.updateModelTransform)
# load and slice buttons.
loadButton = wx.Button(self, -1, 'Load Model')
sliceButton = wx.Button(self, -1, 'Slice to GCode')

View file

@ -8,6 +8,8 @@ import os
from wx import glcanvas
import wx
try:
import OpenGL
OpenGL.ERROR_CHECKING = False
from OpenGL.GLU import *
from OpenGL.GL import *
hasOpenGLlibs = True
@ -27,13 +29,15 @@ class previewPanel(wx.Panel):
wx.Panel.__init__(self, parent,-1)
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DDKSHADOW))
self.SetMinSize((400,300))
self.SetMinSize((440,320))
self.glCanvas = PreviewGLCanvas(self)
self.init = 0
self.triangleMesh = None
self.gcode = None
self.modelFilename = None
self.loadingProgressAmount = 0
self.loadThread = None
self.machineSize = Vector3(float(profile.getPreference('machine_width')), float(profile.getPreference('machine_depth')), float(profile.getPreference('machine_height')))
self.machineCenter = Vector3(0, 0, 0)
@ -50,19 +54,107 @@ class previewPanel(wx.Panel):
self.viewSelect = wx.ComboBox(self.toolbar, -1, 'Model - Normal', choices=['Model - Normal', 'Model - Transparent', 'Model - X-Ray', 'GCode', 'Mixed'], style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.toolbar.AddControl(self.viewSelect)
self.viewSelect.Bind(wx.EVT_TEXT, self.OnViewChange)
self.viewSelect.Bind(wx.EVT_COMBOBOX, self.OnViewChange)
self.glCanvas.viewMode = self.viewSelect.GetValue()
self.layerSpin = wx.SpinCtrl(self.toolbar, -1, '', size=(21*4,21), style=wx.SP_ARROW_KEYS)
self.toolbar.AddControl(self.layerSpin)
self.Bind(wx.EVT_SPINCTRL, self.OnLayerNrChange, self.layerSpin)
self.toolbar2 = wx.ToolBar( self, -1 )
self.toolbar2.SetToolBitmapSize( ( 21, 21 ) )
self.toolbar2.AddControl(wx.StaticText(self.toolbar2, -1, 'Flip'))
self.flipX = wx.CheckBox(self.toolbar2, -1, "X")
self.flipX.SetValue(profile.getProfileSetting('flip_x') == 'True')
self.toolbar2.AddControl(self.flipX)
self.Bind(wx.EVT_CHECKBOX, self.OnFlipXClick, self.flipX)
self.flipY = wx.CheckBox(self.toolbar2, -1, "Y")
self.flipY.SetValue(profile.getProfileSetting('flip_y') == 'True')
self.toolbar2.AddControl(self.flipY)
self.Bind(wx.EVT_CHECKBOX, self.OnFlipYClick, self.flipY)
self.flipZ = wx.CheckBox(self.toolbar2, -1, "Z")
self.flipZ.SetValue(profile.getProfileSetting('flip_z') == 'True')
self.toolbar2.AddControl(self.flipZ)
self.Bind(wx.EVT_CHECKBOX, self.OnFlipZClick, self.flipZ)
self.toolbar2.InsertSeparator(self.toolbar2.GetToolsCount())
self.toolbar2.AddControl(wx.StaticText(self.toolbar2, -1, 'Scale'))
self.scale = wx.TextCtrl(self.toolbar2, -1, profile.getProfileSetting('model_scale'), size=(21*2,21))
self.toolbar2.AddControl(self.scale)
self.Bind(wx.EVT_TEXT, self.OnScale, self.scale)
self.toolbar2.InsertSeparator(self.toolbar2.GetToolsCount())
self.toolbar2.AddControl(wx.StaticText(self.toolbar2, -1, 'Copy'))
self.mulXsub = wx.Button(self.toolbar2, -1, '-', size=(21,21))
self.toolbar2.AddControl(self.mulXsub)
self.Bind(wx.EVT_BUTTON, self.OnMulXSubClick, self.mulXsub)
self.mulXadd = wx.Button(self.toolbar2, -1, '+', size=(21,21))
self.toolbar2.AddControl(self.mulXadd)
self.Bind(wx.EVT_BUTTON, self.OnMulXAddClick, self.mulXadd)
self.mulYsub = wx.Button(self.toolbar2, -1, '-', size=(21,21))
self.toolbar2.AddControl(self.mulYsub)
self.Bind(wx.EVT_BUTTON, self.OnMulYSubClick, self.mulYsub)
self.mulYadd = wx.Button(self.toolbar2, -1, '+', size=(21,21))
self.toolbar2.AddControl(self.mulYadd)
self.Bind(wx.EVT_BUTTON, self.OnMulYAddClick, self.mulYadd)
self.toolbar2.InsertSeparator(self.toolbar2.GetToolsCount())
self.toolbar2.AddControl(wx.StaticText(self.toolbar2, -1, 'Rot'))
self.rotate = wx.SpinCtrl(self.toolbar2, -1, profile.getProfileSetting('model_rotate_base'), size=(21*3,21), style=wx.SP_WRAP|wx.SP_ARROW_KEYS)
self.rotate.SetRange(0, 360)
self.toolbar2.AddControl(self.rotate)
self.Bind(wx.EVT_SPINCTRL, self.OnRotate, self.rotate)
self.toolbar2.Realize()
self.updateToolbar()
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.toolbar, 0, flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT, border=1)
sizer.Add(self.glCanvas, 1, flag=wx.EXPAND)
sizer.Add(self.toolbar2, 0, flag=wx.EXPAND|wx.BOTTOM|wx.LEFT|wx.RIGHT, border=1)
self.SetSizer(sizer)
def OnFlipXClick(self, e):
profile.putProfileSetting('flip_x', str(self.flipX.GetValue()))
self.updateModelTransform()
def OnFlipYClick(self, e):
profile.putProfileSetting('flip_y', str(self.flipY.GetValue()))
self.updateModelTransform()
def OnFlipZClick(self, e):
profile.putProfileSetting('flip_z', str(self.flipZ.GetValue()))
self.updateModelTransform()
def OnMulXAddClick(self, e):
profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))+1)))
self.updateModelTransform()
def OnMulXSubClick(self, e):
profile.putProfileSetting('model_multiply_x', str(max(1, int(profile.getProfileSetting('model_multiply_x'))-1)))
self.updateModelTransform()
def OnMulYAddClick(self, e):
profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))+1)))
self.updateModelTransform()
def OnMulYSubClick(self, e):
profile.putProfileSetting('model_multiply_y', str(max(1, int(profile.getProfileSetting('model_multiply_y'))-1)))
self.updateModelTransform()
def OnScale(self, e):
try:
scale = float(self.scale.GetValue())
except:
scale = 1.0
profile.putProfileSetting('model_scale', str(scale))
self.updateModelTransform()
def OnRotate(self, e):
profile.putProfileSetting('model_rotate_base', self.rotate.GetValue())
self.updateModelTransform()
def On3DClick(self, e):
self.glCanvas.yaw = 30
@ -102,15 +194,19 @@ class previewPanel(wx.Panel):
self.gcodeFilename = filename[: filename.rfind('.')] + "_export.gcode"
self.logFilename = filename[: filename.rfind('.')] + "_export.log"
#Do the STL file loading in a background thread so we don't block the UI.
threading.Thread(target=self.doFileLoad).start()
if self.loadThread != None and self.loadThread.isAlive():
self.loadThread.join()
self.loadThread = threading.Thread(target=self.doFileLoadThread)
self.loadThread.daemon = True
self.loadThread.start()
def loadReModelFile(self, filename):
#Only load this again if the filename matches the file we have already loaded (for auto loading GCode after slicing)
if self.modelFilename != filename:
return
threading.Thread(target=self.doFileLoad).start()
self.loadModelFile(filename)
def doFileLoad(self):
def doFileLoadThread(self):
if os.path.isfile(self.modelFilename) and self.modelFileTime != os.stat(self.modelFilename).st_mtime:
self.modelFileTime = os.stat(self.modelFilename).st_mtime
triangleMesh = fabmetheus_interpret.getCarving(self.modelFilename)
@ -127,7 +223,10 @@ class previewPanel(wx.Panel):
if os.path.isfile(self.gcodeFilename) and self.gcodeFileTime != os.stat(self.gcodeFilename).st_mtime:
self.gcodeFileTime = os.stat(self.gcodeFilename).st_mtime
gcode = gcodeInterpreter.gcode(self.gcodeFilename)
gcode = gcodeInterpreter.gcode()
gcode.progressCallback = self.loadProgress
gcode.load(self.gcodeFilename)
self.loadingProgressAmount = 0
self.gcodeDirty = False
self.errorList = []
self.gcode = gcode
@ -148,6 +247,10 @@ class previewPanel(wx.Panel):
self.errorList = errorList
wx.CallAfter(self.glCanvas.Refresh)
def loadProgress(self, progress):
self.loadingProgressAmount = progress
wx.CallAfter(self.glCanvas.Refresh)
def updateToolbar(self):
self.layerSpin.Show(self.gcode != None)
if self.gcode != None:
@ -390,15 +493,16 @@ class PreviewGLCanvas(glcanvas.GLCanvas):
glVertex3f(v3.x, v3.y, v3.z - 0.01)
glVertex3f(v2.x, v2.y, v2.z - 0.01)
glEnd()
for v in path['list']:
glBegin(GL_TRIANGLE_FAN)
glVertex3f(v.x, v.y, v.z - 0.001)
for i in xrange(0, 16+1):
if path['pathType'] == 'FILL': #Remove depth buffer fighting on infill/wall overlap
glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.02)
else:
glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.01)
glEnd()
#for v in path['list']:
# glBegin(GL_TRIANGLE_FAN)
# glVertex3f(v.x, v.y, v.z - 0.001)
# for i in xrange(0, 16+1):
# if path['pathType'] == 'FILL': #Remove depth buffer fighting on infill/wall overlap
# glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.02)
# else:
# glVertex3f(v.x + math.cos(math.pi*2/16*i) * lineWidth, v.y + math.sin(math.pi*2/16*i) * lineWidth, v.z - 0.01)
# glEnd()
else:
glBegin(GL_LINE_STRIP)
for v in path['list']:
@ -499,9 +603,11 @@ class PreviewGLCanvas(glcanvas.GLCanvas):
elif self.viewMode == "Model - Normal":
glEnable(GL_LIGHTING)
glCallList(self.modelDisplayList)
if self.viewMode == "Model - Normal" or self.viewMode == "Model - Transparent" or self.viewMode == "Model - X-Ray":
glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glDisable(GL_BLEND)
glColor3f(1,0,0)
glTranslate(self.parent.machineCenter.x, self.parent.machineCenter.y, 0)
glBegin(GL_LINES)
@ -509,6 +615,7 @@ class PreviewGLCanvas(glcanvas.GLCanvas):
glVertex3f(err[0].x, err[0].y, err[0].z)
glVertex3f(err[1].x, err[1].y, err[1].z)
glEnd()
glFlush()
def InitGL(self):

View file

@ -69,14 +69,20 @@ def getDefaultProfilePath():
return os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../current_profile.ini"))
def loadGlobalProfile(filename):
"Read a configuration file as global config"
#Read a configuration file as global config
global globalProfileParser
globalProfileParser = ConfigParser.ConfigParser()
globalProfileParser.read(filename)
def saveGlobalProfile(filename):
#Save the current profile to an ini file
globalProfileParser.write(open(filename, 'w'))
def resetGlobalProfile():
#Create an empty profile with no settings, so everything gets default settings.
global globalProfileParser
globalProfileParser = ConfigParser.ConfigParser()
def getProfileSetting(name):
if name in profileDefaultSettings:
default = profileDefaultSettings[name]

View file

@ -8,7 +8,7 @@ import threading
import subprocess
import time
from newui import skeinRun
from newui import sliceRun
class sliceProgessPanel(wx.Panel):
def __init__(self, mainWindow, parent, filename):
@ -115,7 +115,7 @@ class WorkerThread(threading.Thread):
self.start()
def run(self):
p = subprocess.Popen(skeinRun.getSkeinCommand(self.filename), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p = subprocess.Popen(sliceRun.getSliceCommand(self.filename), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
line = p.stdout.readline()
maxValue = 1
self.progressLog = []

View file

@ -23,7 +23,7 @@ def getPyPyExe():
return pypyExe
return False
def runSkein(fileNames):
def runSlice(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:
@ -40,7 +40,7 @@ def runSkein(fileNames):
else:
subprocess.call([pypyExe, os.path.join(sys.path[0], sys.argv[0]), fileName])
def getSkeinCommand(filename):
def getSliceCommand(filename):
pypyExe = getPyPyExe()
if pypyExe == False:
pypyExe = sys.executable

View file

@ -1,40 +0,0 @@
[profile]
max_z_speed = 1.0
sequence = Infill > Loops > Perimeter
nozzle_size = 0.4
machine_center_x = 100
machine_center_y = 100
flip_z = False
flip_x = False
cool_min_layer_time = 10
infill_type = Line
skirt_line_count = 3
retraction_amount = 0.0
travel_speed = 150
model_rotate_base = 0
support_rate = 100
wall_thickness = 0.4
print_temperature = 0
skirt_gap = 6.0
support = None
bottom_layer_speed = 25
filament_density = 1.00
joris = True
model_multiply_y = 1
model_multiply_x = 1
support_distance = 0.5
fill_density = 0
filament_diameter = 2.89
print_speed = 50
fill_overlap = 15
retraction_min_travel = 5.0
solid_top = False
retraction_speed = 13.5
extra_base_wall_thickness = 15
flip_y = False
solid_layer_thickness = 0.6
model_scale = 1.0
retraction_extra = 0.0
force_first_layer_sequence = False
layer_height = 0.2

View file

@ -17,6 +17,9 @@ The default 'Activate Chamber' checkbox is on. When it is on, the functions des
===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.
The idea is described at:
http://www.makerbot.com/blog/2011/03/17/if-you-cant-stand-the-heat/
====Bed Temperature====
Default: 60C

View file

@ -174,7 +174,6 @@ 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
@ -240,7 +239,6 @@ class CombSkein:
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):
@ -264,14 +262,6 @@ class CombSkein:
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:
@ -439,7 +429,6 @@ class CombSkein:
return
elif firstWord == '(<edgeWidth>':
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

View file

@ -230,7 +230,8 @@ class CoolSkein:
def addFlowRate(self, flowRate):
'Add a multipled line of flow rate if different.'
self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
if flowRate != None:
self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
def addGcodeFromFeedRateMovementZ(self, feedRateMinute, point, z):
'Add a movement to the output.'

View file

@ -1,8 +1,6 @@
"""
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:
Dwindle is a plugin to reduce the feed rate and flow rate at the end of the thread, in order to reduce the ooze when traveling. It reduces the flow rate by a bit more than the feed rate, in order to use up the pent up plastic in the thread so that there is less remaining in the ooze.
The dwindle manual page is at:
http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle
@ -11,10 +9,25 @@ http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dwindle
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
===End Rate Multiplier===
Default: 0.5
Defines the number of times the dwindle infill and edges are divided vertically.
Defines the ratio of the feed and flow rate at the end over the feed and flow rate of the rest of the thread. With reasonable values for the 'Pent Up Volume' and 'Slowdown Volume', the amount of ooze should be roughly proportional to the square of the 'End Rate Multiplier'. If the 'End Rate Multiplier' is too low, the printing will be very slow because the feed rate will be lower. If the 'End Rate Multiplier' is too high, there will still be a lot of ooze.
===Pent Up Volume===
Default: 0.4 mm3
When the filament is stopped, there is a pent up volume of plastic that comes out afterwards. For best results, the 'Pent Up Volume' in dwindle should be set to that amount. If the 'Pent Up Volume' is too small, there will still be a lot of ooze. If the 'Pent Up Volume' is too large, the end of the thread will be thinner than the rest of the thread.
===Slowdown Steps===
Default: 3
Dwindle reduces the feed rate and flow rate in steps so the thread will remain at roughly the same thickness until the end. The "Slowdown Steps" setting is the number of steps, the more steps the smaller the variation in the thread thickness, but the larger the size of the resulting gcode file and the more time spent pausing between segments.
===Slowdown Volume===
Default: 5 mm3
The 'Slowdown Volume' is the volume of the end of the thread where the feed and flow rates will be decreased. If the 'Slowdown Volume' is too small, there won't be enough time to get rid of the pent up plastic, so there will still be a lot of ooze. If the 'Slowdown Volume' is too large, a bit of time will be wasted because for a large portion of the thread, the feed rate will be slow. Overall, it is best to err on being too large, because too large would only waste machine time in production, rather than the more important string removal labor time.
==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.
@ -80,15 +93,15 @@ 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, '')
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.slowdownVolume = settings.FloatSpin().getFromValue(1.0, 'Slowdown Volume (cubic millimeters):', self, 10.0, 5.0)
self.executeTitle = 'Dwindle'
def execute(self):
@ -110,6 +123,7 @@ class DwindleSkein:
self.lines = None
self.oldFlowRate = None
self.oldLocation = None
self.operatingFlowRate = None
self.threadSections = []
def addThread(self):
@ -138,7 +152,10 @@ class DwindleSkein:
self.lines = archive.getTextLines(gcodeText)
self.repository = repository
self.parseInitialization()
self.area = self.infillWidth * self.layerHeight
if self.operatingFlowRate == None:
print('Warning, there is no operatingFlowRate so dwindle will do nothing.')
return gcodeText
self.area = self.infillWidth * self.layerHeight * self.volumeFraction
self.oneOverSteps = 1.0 / float(repository.slowdownSteps.value)
self.halfOverSteps = 0.5 * self.oneOverSteps
for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
@ -165,6 +182,8 @@ class DwindleSkein:
elif firstWord == '(<operatingFlowRate>':
self.operatingFlowRate = float(splitLine[1])
self.oldFlowRate = self.operatingFlowRate
elif firstWord == '(<volumeFraction>':
self.volumeFraction = float(splitLine[1])
self.distanceFeedRate.addLine(line)
def parseLine(self, line):

View file

@ -118,6 +118,13 @@ 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.
===Sharpest Angle===
Default: 60 degrees
Defines the sharpest angle that a thread is allowed to make before it is separated into two threads. If 'Sharpest Angle' is too low, the extruder will stop and start often, slowing printing and putting more wear and tear on the extruder. If 'Sharpest Angle' is too high, then threads will almost double back on themselves, leading to bumps in the fill, and sometimes filament being dragged by the nozzle.
This parameter is used in fill, raft and skin.
===Solid Surface Thickness===
Default is three.
@ -791,7 +798,7 @@ class FillRepository:
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.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 )
@ -800,8 +807,8 @@ class FillRepository:
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.sharpestAngle = settings.FloatSpin().getFromValue(50.0, 'Sharpest Angle (degrees):', self, 70.0, 60.0)
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)
@ -878,7 +885,8 @@ class FillSkein:
extraShells = 0
self.distanceFeedRate.addLine('(<bridgeRotation> %s )' % layerRotation)
self.distanceFeedRate.addLine('(<rotation> %s )' % layerRotation)
aroundWidth = 0.34321 * self.infillWidth
# aroundWidth = 0.34321 * self.infillWidth
aroundWidth = 0.24321 * self.infillWidth
doubleInfillWidth = 2.0 * self.infillWidth
gridPointInsetX = 0.5 * self.fillInset
self.lastExtraShells = extraShells
@ -929,7 +937,7 @@ class FillSkein:
for segments in self.horizontalSegmentsDictionary.values():
for segment in segments:
endpoints += segment
paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, aroundWidth)
paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, self.sharpestProduct, aroundWidth)
if gridCircular:
startAngle = euclidean.globalGoldenAngle * float(layerIndex)
for gridPoint in self.getGridPoints(fillLoops, reverseRotation):
@ -942,7 +950,7 @@ class FillSkein:
while oldRemovedEndpointLength - len(removedEndpoints) > 0:
oldRemovedEndpointLength = len(removedEndpoints)
removeEndpoints(self.infillWidth, paths, pixelTable, removedEndpoints, aroundWidth)
paths = euclidean.getConnectedPaths(paths, pixelTable, aroundWidth)
paths = euclidean.getConnectedPaths(paths, pixelTable, self.sharpestProduct, aroundWidth)
for path in paths:
addPath(self.infillWidth, infillPaths, path, layerRotation)
euclidean.transferPathsToNestedRings(nestedRings, infillPaths)
@ -1118,6 +1126,7 @@ class FillSkein:
'Parse gcode text and store the bevel gcode.'
self.repository = repository
self.lines = archive.getTextLines(gcodeText)
self.sharpestProduct = math.sin(math.radians(repository.sharpestAngle.value))
self.threadSequence = None
if repository.threadSequenceInfillLoops.value:
self.threadSequence = ['infill', 'loops', 'edge']
@ -1251,12 +1260,13 @@ class FillSkein:
if firstWord == '(<crafting>)':
self.distanceFeedRate.addLine(line)
return
elif firstWord == '(<infillWidth>':
self.infillWidth = float(splitLine[1])
elif firstWord == '(<layerHeight>':
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)
self.distanceFeedRate.addTagRoundedLine('sharpestProduct', self.sharpestProduct)
elif firstWord == '(<edgeWidth>':
self.edgeWidth = float(splitLine[1])
threadSequenceString = ' '.join( self.threadSequence )

View file

@ -38,6 +38,11 @@ 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.
===Volume Fraction===
Default: 0.82
The 'Volume Fraction' is the estimated volume of the thread compared to the box defined by the layer height and infill width. This is used in dwindle, splodge, and statistic. It is in inset because inset is a required extrusion tool, earlier in the chain than dwindle and splodge. In dwindle and splodge it is used to determine the filament volume, in statistic it is used to determine the extrusion diameter.
==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.
@ -296,7 +301,7 @@ class InsetRepository:
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.infillWidth = settings.FloatSpin().getFromValue(0.1, 'Infill Width:', self, 1.7, 0.4)
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)
@ -425,7 +430,7 @@ class InsetSkein:
return
elif firstWord == '(<layerHeight>':
layerHeight = float(splitLine[1])
self.infillWidth = self.repository.infillWidthOverThickness.value * layerHeight
self.infillWidth = self.repository.infillWidth.value
self.distanceFeedRate.addTagRoundedLine('infillWidth', self.infillWidth)
self.distanceFeedRate.addTagRoundedLine('volumeFraction', self.repository.volumeFraction.value)
elif firstWord == '(<edgeWidth>':

View file

@ -100,6 +100,7 @@ class JorisSkein:
self.travelFeedRateMinute = 957.0
self.perimeter = None
self.oldLocation = None
self.doJoris = False
def getCraftedGcode( self, gcodeText, repository ):
'Parse gcode text and store the joris gcode.'
@ -134,7 +135,7 @@ class JorisSkein:
if len(splitLine) < 1:
return
firstWord = splitLine[0]
if firstWord == 'G1':
if firstWord == 'G1' and self.doJoris:
self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
self.oldLocation = location
@ -148,9 +149,15 @@ class JorisSkein:
self.oldFlowRate = gcodec.getDoubleAfterFirstLetter(splitLine[1])
elif firstWord == '(<edge>':
if self.layerIndex >= self.layersFromBottom:
self.perimeter = []
elif firstWord == '(</edge>)':
self.doJoris = True
elif firstWord == 'M101' and self.doJoris:
self.perimeter = []
return
elif firstWord == 'M103' and self.doJoris:
self.addJorisedPerimeter()
return
elif firstWord == '(</edge>)':
self.doJoris = False
self.distanceFeedRate.addLine(line)
def addJorisedPerimeter(self):
@ -160,7 +167,7 @@ class JorisSkein:
#Calculate the total length of the perimeter.
p = self.oldLocation.dropAxis()
perimeterLength = 0;
for point in self.perimeter[1 :]:
for point in self.perimeter:
perimeterLength += abs( point - p );
p = point
@ -168,7 +175,7 @@ class JorisSkein:
p = self.oldLocation.dropAxis()
len = 0;
self.distanceFeedRate.addLine('M101') # Turn extruder on.
for point in self.perimeter[1 :]:
for point in self.perimeter:
len += abs( point - p );
p = point
self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, point, self.oldLocation.z + self.layerThickness * len / perimeterLength)

View file

@ -200,7 +200,7 @@ class MillSkein:
endpoints = euclidean.getEndpointsFromSegmentTable( boundaryLayer.segmentTable )
if len(endpoints) < 1:
return
paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.millWidth, self.aroundPixelTable, self.aroundWidth)
paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.millWidth, self.aroundPixelTable, 1.0, self.aroundWidth)
averageZ = self.average.getAverage()
if self.repository.addInnerLoops.value:
self.addGcodeFromLoops( boundaryLayer.innerLoops, averageZ )

View file

@ -443,6 +443,7 @@ class RaftSkein:
self.operatingLayerEndLine = '(<operatingLayerEnd> </operatingLayerEnd>)'
self.operatingJump = None
self.orbitalFeedRatePerSecond = 2.01
self.sharpestProduct = 0.94
self.supportFlowRate = None
self.supportLayers = []
self.supportLayersTemperature = None
@ -547,7 +548,7 @@ class RaftSkein:
return
aroundPixelTable = {}
aroundWidth = 0.34321 * step
paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * step, aroundPixelTable, aroundWidth)
paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * step, aroundPixelTable, self.sharpestProduct, aroundWidth)
self.addLayerLine(z)
if self.operatingFlowRate != None:
self.addFlowRate(flowRateMultiplier * self.operatingFlowRate)
@ -704,7 +705,7 @@ class RaftSkein:
aroundBoundaryLoops = intercircle.getAroundsFromLoops(boundaryLoops, halfSupportOutset)
for aroundBoundaryLoop in aroundBoundaryLoops:
euclidean.addLoopToPixelTable(aroundBoundaryLoop, aroundPixelTable, aroundWidth)
paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, aroundWidth)
paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, self.sharpestProduct, aroundWidth)
feedRateMinuteMultiplied = self.operatingFeedRateMinute
supportFlowRateMultiplied = self.supportFlowRate
if self.layerIndex == 0:
@ -894,6 +895,11 @@ class RaftSkein:
self.baseTemperature = float(splitLine[1])
elif firstWord == '(<coolingRate>':
self.coolingRate = float(splitLine[1])
elif firstWord == '(<edgeWidth>':
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 == '(</extruderInitialization>)':
self.distanceFeedRate.addTagBracketedProcedure('raft')
elif firstWord == '(<heatingRate>':
@ -925,11 +931,8 @@ class RaftSkein:
self.operatingFlowRate = float(splitLine[1])
self.oldFlowRate = self.operatingFlowRate
self.supportFlowRate = self.operatingFlowRate * self.repository.supportFlowRateOverOperatingFlowRate.value
elif firstWord == '(<edgeWidth>':
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 == '(<sharpestProduct>':
self.sharpestProduct = float(splitLine[1])
elif firstWord == '(<supportLayersTemperature>':
self.supportLayersTemperature = float(splitLine[1])
elif firstWord == '(<supportedLayersTemperature>':

View file

@ -154,11 +154,13 @@ class SkinSkein:
self.maximumZFeedRateMinute = 60.0
self.oldFlowRate = None
self.oldLocation = None
self.sharpestProduct = 0.94
self.travelFeedRateMinute = 957.0
def addFlowRateLine(self, flowRate):
'Add a flow rate line.'
self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
if flowRate != None:
self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
def addPerimeterLoop(self, thread, z):
'Add the edge loop to the gcode.'
@ -170,7 +172,8 @@ class SkinSkein:
return
bottomZ = self.oldLocation.z + self.layerHeight / self.verticalDivisionsFloat - self.layerHeight
offsetY = 0.5 * self.skinInfillWidth
self.addFlowRateLine(self.oldFlowRate / self.verticalDivisionsFloat / self.horizontalInfillDivisionsFloat)
if self.oldFlowRate != None:
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)
@ -199,7 +202,7 @@ class SkinSkein:
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)
infillPaths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.skinInfillWidth, pixelTable, self.sharpestProduct, aroundWidth)
for infillPath in infillPaths:
addPointBeforeThread = True
infillRotated = euclidean.getRotatedComplexes(self.rotation, infillPath)
@ -239,9 +242,12 @@ class SkinSkein:
for division in xrange(self.repository.horizontalPerimeterDivisions.value):
edges.append(self.getClippedSimplifiedLoopPathByLoop(intercircle.getLargestInsetLoopFromLoop(edgeThread, radius)))
radius += radiusAddition
skinnedPerimeterFlowRate = self.oldFlowRate / self.verticalDivisionsFloat
skinnedPerimeterFlowRate = None
if self.oldFlowRate != None:
skinnedPerimeterFlowRate = self.oldFlowRate / self.verticalDivisionsFloat
if getIsMinimumSides(edges):
self.addFlowRateLine(skinnedPerimeterFlowRate / self.horizontalPerimeterDivisionsFloat)
if self.oldFlowRate != None:
self.addFlowRateLine(skinnedPerimeterFlowRate / self.horizontalPerimeterDivisionsFloat)
for verticalDivisionIndex in xrange(self.verticalDivisions):
z = bottomZ + self.layerHeight / self.verticalDivisionsFloat * float(verticalDivisionIndex)
for edge in edges:
@ -314,6 +320,9 @@ class SkinSkein:
self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
if firstWord == '(<clipOverEdgeWidth>':
self.clipOverEdgeWidth = float(splitLine[1])
elif firstWord == '(<edgeWidth>':
self.edgeWidth = float(splitLine[1])
self.halfEdgeWidth = 0.5 * self.edgeWidth
elif firstWord == '(</extruderInitialization>)':
self.distanceFeedRate.addTagBracketedProcedure('skin')
return
@ -328,9 +337,8 @@ class SkinSkein:
self.maximumZFeedRateMinute = 60.0 * float(splitLine[1])
elif firstWord == '(<operatingFlowRate>':
self.oldFlowRate = float(splitLine[1])
elif firstWord == '(<edgeWidth>':
self.edgeWidth = float(splitLine[1])
self.halfEdgeWidth = 0.5 * self.edgeWidth
elif firstWord == '(<sharpestProduct>':
self.sharpestProduct = float(splitLine[1])
elif firstWord == '(<travelFeedRatePerSecond>':
self.travelFeedRateMinute = 60.0 * float(splitLine[1])
self.distanceFeedRate.addLine(line)

View file

@ -96,15 +96,15 @@ def getNewRepository():
'Get new repository.'
return WidenRepository()
def getWidenedLoop(loop, loopList, outsetLoop, radius):
def getWidenedLoops(loop, loopList, outsetLoop, radius):
'Get the widened loop.'
intersectingWithinLoops = getIntersectingWithinLoops(loop, loopList, outsetLoop)
if len(intersectingWithinLoops) < 1:
return loop
return [loop]
loopsUnified = boolean_solid.getLoopsUnion(radius, [[loop], intersectingWithinLoops])
if len(loopsUnified) < 1:
return loop
return euclidean.getLargestLoop(loopsUnified)
return [loop]
return loopsUnified
def writeOutput(fileName, shouldAnalyze=True):
'Widen the carving of a gcode file.'
@ -121,6 +121,7 @@ class WidenRepository:
self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute(
'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Widen')
self.activateWiden = settings.BooleanSetting().getFromValue('Activate Widen', self, False)
self.widenWidthOverEdgeWidth = settings.IntSpin().getFromValue(2, 'Widen Width over Edge Width (ratio):', self, 4, 2)
self.executeTitle = 'Widen'
def execute(self):
@ -155,15 +156,15 @@ class WidenSkein:
else:
widdershinsLoops.append(loop)
else:
# clockwiseInsetLoop = intercircle.getLargestInsetLoopFromLoop(loop, self.doubleEdgeWidth)
# clockwiseInsetLoop = intercircle.getLargestInsetLoopFromLoop(loop, self.widenEdgeWidth)
# clockwiseInsetLoop.reverse()
# clockwiseInsetLoops.append(clockwiseInsetLoop)
clockwiseInsetLoops += intercircle.getInsetLoopsFromLoop(loop, self.doubleEdgeWidth)
clockwiseInsetLoops += intercircle.getInsetLoopsFromLoop(loop, self.widenEdgeWidth)
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)
outsetLoop = intercircle.getLargestInsetLoopFromLoop(widdershinsLoop, -self.widenEdgeWidth)
for widenedLoop in getWidenedLoops(widdershinsLoop, clockwiseInsetLoops, outsetLoop, self.lessThanHalfEdgeWidth):
self.distanceFeedRate.addGcodeFromLoop(widenedLoop, loopLayer.z)
def getCraftedGcode(self, gcodeText, repository):
'Parse gcode text and store the widen gcode.'
@ -188,7 +189,8 @@ class WidenSkein:
return
elif firstWord == '(<edgeWidth>':
self.edgeWidth = float(splitLine[1])
self.doubleEdgeWidth = 2.0 * self.edgeWidth
self.widenEdgeWidth = float(self.repository.widenWidthOverEdgeWidth.value) * self.edgeWidth
self.lessThanHalfEdgeWidth = 0.49 * self.edgeWidth
self.distanceFeedRate.addLine(line)
def parseLine(self, line):

View file

@ -87,28 +87,23 @@ def getPluginsDirectoryPath():
"Get the plugins directory path."
return archive.getCraftPluginsDirectoryPath()
def getProcedures( procedure, text ):
"Get the procedures up to and including the given procedure."
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 ]
craftSequence = craftSequence[: sequenceIndexFromProcedure + 1]
for craftSequenceIndex in xrange(len(craftSequence) - 1, -1, -1):
procedure = craftSequence[craftSequenceIndex]
if gcodec.isProcedureDone(text, procedure):
return craftSequence[craftSequenceIndex + 1 :]
return craftSequence
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('')

View file

@ -16,10 +16,7 @@ import sys
import platform
from optparse import OptionParser
from newui import skeinRun
if platform.python_implementation() != "PyPy":
from newui import mainWindow
from newui import sliceRun
__author__ = 'Daid'
__credits__ = """
@ -50,8 +47,9 @@ def main():
(options, args) = parser.parse_args()
sys.argv = [sys.argv[0]] + args
if len( args ) > 0:
skeinRun.runSkein(args)
sliceRun.runSlice(args)
else:
from newui import mainWindow
mainWindow.main()
if __name__ == '__main__':