chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / multiply.py
1 """
2 This page is in the table of contents.
3 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.
4
5 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).
6
7 The multiply manual page is at:
8 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Multiply
9
10 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.
11
12 ==Operation==
13 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.
14
15 ==Settings==
16 ===Center===
17 Default is the origin.
18
19 The center of the shape will be moved to the "Center X" and "Center Y" coordinates.
20
21 ====Center X====
22 ====Center Y====
23
24 ===Number of Cells===
25 ====Number of Columns====
26 Default is one.
27
28 Defines the number of columns in the array table.
29
30 ====Number of Rows====
31 Default is one.
32
33 Defines the number of rows in the table.
34
35 ===Reverse Sequence every Odd Layer===
36 Default is off.
37
38 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.
39
40 ===Separation over Perimeter Width===
41 Default is fifteen.
42
43 Defines the ratio of separation between the shape copies over the edge width.
44
45 ==Examples==
46 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.
47
48 > python multiply.py
49 This brings up the multiply dialog.
50
51 > python multiply.py Screw Holder Bottom.stl
52 The multiply tool is parsing the file:
53 Screw Holder Bottom.stl
54 ..
55 The multiply tool has created the file:
56 .. Screw Holder Bottom_multiply.gcode
57
58 """
59
60
61 from __future__ import absolute_import
62 #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.
63 import __init__
64
65 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
66 from fabmetheus_utilities.vector3 import Vector3
67 from fabmetheus_utilities import archive
68 from fabmetheus_utilities import euclidean
69 from fabmetheus_utilities import gcodec
70 from fabmetheus_utilities import intercircle
71 from fabmetheus_utilities import settings
72 from skeinforge_application.skeinforge_utilities import skeinforge_craft
73 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
74 from skeinforge_application.skeinforge_utilities import skeinforge_profile
75 import math
76 import sys
77
78
79 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
80 __date__ = '$Date: 2008/21/04 $'
81 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
82
83
84 def getCraftedText(fileName, text='', repository=None):
85         'Multiply the fill file or text.'
86         return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
87
88 def getCraftedTextFromText(gcodeText, repository=None):
89         'Multiply the fill text.'
90         if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'multiply'):
91                 return gcodeText
92         if repository == None:
93                 repository = settings.getReadRepository(MultiplyRepository())
94         if not repository.activateMultiply.value:
95                 return gcodeText
96         return MultiplySkein().getCraftedGcode(gcodeText, repository)
97
98 def getNewRepository():
99         'Get new repository.'
100         return MultiplyRepository()
101
102 def writeOutput(fileName, shouldAnalyze=True):
103         'Multiply a gcode linear move file.'
104         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'multiply', shouldAnalyze)
105
106
107 class MultiplyRepository:
108         'A class to handle the multiply settings.'
109         def __init__(self):
110                 'Set the default settings, execute title & settings fileName.'
111                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.multiply.html', self )
112                 self.fileNameInput = settings.FileNameInput().getFromFileName(
113                         fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Multiply', self, '')
114                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Multiply')
115                 self.activateMultiply = settings.BooleanSetting().getFromValue('Activate Multiply', self, True)
116                 settings.LabelSeparator().getFromRepository(self)
117                 settings.LabelDisplay().getFromName('- Center -', self )
118                 self.centerX = settings.FloatSpin().getFromValue(-100.0, 'Center X (mm):', self, 100.0, 105.0)
119                 self.centerY = settings.FloatSpin().getFromValue(-100.0, 'Center Y (mm):', self, 100.0, 105.0)
120                 settings.LabelSeparator().getFromRepository(self)
121                 settings.LabelDisplay().getFromName('- Number of Cells -', self)
122                 self.numberOfColumns = settings.IntSpin().getFromValue(1, 'Number of Columns (integer):', self, 10, 1)
123                 self.numberOfRows = settings.IntSpin().getFromValue(1, 'Number of Rows (integer):', self, 10, 1)
124                 settings.LabelSeparator().getFromRepository(self)
125                 self.reverseSequenceEveryOddLayer = settings.BooleanSetting().getFromValue('Reverse Sequence every Odd Layer', self, False)
126                 self.separationOverEdgeWidth = settings.FloatSpin().getFromValue(5.0, 'Separation over Perimeter Width (ratio):', self, 25.0, 15.0)
127                 self.executeTitle = 'Multiply'
128
129         def execute(self):
130                 'Multiply button has been clicked.'
131                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(
132                         self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
133                 for fileName in fileNames:
134                         writeOutput(fileName)
135
136
137 class MultiplySkein:
138         'A class to multiply a skein of extrusions.'
139         def __init__(self):
140                 self.distanceFeedRate = gcodec.DistanceFeedRate()
141                 self.isExtrusionActive = False
142                 self.layerIndex = 0
143                 self.layerLines = []
144                 self.lineIndex = 0
145                 self.lines = None
146                 self.oldLocation = None
147                 self.rowIndex = 0
148                 self.shouldAccumulate = True
149
150         def addElement(self, offset):
151                 'Add moved element to the output.'
152                 for line in self.layerLines:
153                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
154                         firstWord = gcodec.getFirstWord(splitLine)
155                         if firstWord == '(<boundaryPoint>':
156                                 movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine)
157                                 line = self.distanceFeedRate.getBoundaryLine(movedLocation)
158                         elif firstWord == 'G1':
159                                 movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine)
160                                 line = self.distanceFeedRate.getLinearGcodeMovement(movedLocation.dropAxis(), movedLocation.z)
161                         elif firstWord == '(<infillPoint>':
162                                 movedLocation = self.getMovedLocationSetOldLocation(offset, splitLine)
163                                 line = self.distanceFeedRate.getInfillBoundaryLine(movedLocation)
164                         self.distanceFeedRate.addLine(line)
165
166         def addLayer(self):
167                 'Add multiplied layer to the output.'
168                 self.addRemoveThroughLayer()
169                 offset = self.centerOffset - self.arrayCenter - self.shapeCenter
170                 for rowIndex in xrange(self.repository.numberOfRows.value):
171                         yRowOffset = float(rowIndex) * self.extentPlusSeparation.imag
172                         if self.layerIndex % 2 == 1 and self.repository.reverseSequenceEveryOddLayer.value:
173                                 yRowOffset = self.arrayExtent.imag - yRowOffset
174                         for columnIndex in xrange(self.repository.numberOfColumns.value):
175                                 xColumnOffset = float(columnIndex) * self.extentPlusSeparation.real
176                                 if self.rowIndex % 2 == 1:
177                                         xColumnOffset = self.arrayExtent.real - xColumnOffset
178                                 elementOffset = complex(offset.real + xColumnOffset, offset.imag + yRowOffset)
179                                 self.addElement(elementOffset)
180                         self.rowIndex += 1
181                 settings.printProgress(self.layerIndex, 'multiply')
182                 if len(self.layerLines) > 1:
183                         self.layerIndex += 1
184                 self.layerLines = []
185
186         def addRemoveThroughLayer(self):
187                 'Parse gcode initialization and store the parameters.'
188                 for layerLineIndex in xrange(len(self.layerLines)):
189                         line = self.layerLines[layerLineIndex]
190                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
191                         firstWord = gcodec.getFirstWord(splitLine)
192                         self.distanceFeedRate.addLine(line)
193                         if firstWord == '(<layer>':
194                                 self.layerLines = self.layerLines[layerLineIndex + 1 :]
195                                 return
196
197         def getCraftedGcode(self, gcodeText, repository):
198                 'Parse gcode text and store the multiply gcode.'
199                 self.centerOffset = complex(repository.centerX.value, repository.centerY.value)
200                 self.repository = repository
201                 self.numberOfColumns = repository.numberOfColumns.value
202                 self.numberOfRows = repository.numberOfRows.value
203                 self.lines = archive.getTextLines(gcodeText)
204                 self.parseInitialization()
205                 self.setCorners()
206                 for line in self.lines[self.lineIndex :]:
207                         self.parseLine(line)
208                 return self.distanceFeedRate.output.getvalue()
209
210         def getMovedLocationSetOldLocation(self, offset, splitLine):
211                 'Get the moved location and set the old location.'
212                 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
213                 self.oldLocation = location
214                 return Vector3(location.x + offset.real, location.y + offset.imag, location.z)
215
216         def parseInitialization(self):
217                 'Parse gcode initialization and store the parameters.'
218                 for self.lineIndex in xrange(len(self.lines)):
219                         line = self.lines[self.lineIndex]
220                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
221                         firstWord = gcodec.getFirstWord(splitLine)
222                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
223                         if firstWord == '(</extruderInitialization>)':
224                                 self.distanceFeedRate.addTagBracketedProcedure('multiply')
225                                 self.distanceFeedRate.addLine(line)
226                                 self.lineIndex += 1
227                                 return
228                         elif firstWord == '(<edgeWidth>':
229                                 self.absoluteEdgeWidth = abs(float(splitLine[1]))
230                         self.distanceFeedRate.addLine(line)
231
232         def parseLine(self, line):
233                 'Parse a gcode line and add it to the multiply skein.'
234                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
235                 if len(splitLine) < 1:
236                         return
237                 firstWord = splitLine[0]
238                 if firstWord == '(</layer>)':
239                         self.addLayer()
240                         self.distanceFeedRate.addLine(line)
241                         return
242                 elif firstWord == '(</crafting>)':
243                         self.shouldAccumulate = False
244                 if self.shouldAccumulate:
245                         self.layerLines.append(line)
246                         return
247                 self.distanceFeedRate.addLine(line)
248
249         def setCorners(self):
250                 'Set maximum and minimum corners and z.'
251                 cornerMaximumComplex = complex(-987654321.0, -987654321.0)
252                 cornerMinimumComplex = -cornerMaximumComplex
253                 for line in self.lines[self.lineIndex :]:
254                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
255                         firstWord = gcodec.getFirstWord(splitLine)
256                         if firstWord == 'G1':
257                                 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
258                                 if self.isExtrusionActive:
259                                         locationComplex = location.dropAxis()
260                                         cornerMaximumComplex = euclidean.getMaximum(locationComplex,  cornerMaximumComplex)
261                                         cornerMinimumComplex = euclidean.getMinimum(locationComplex,  cornerMinimumComplex)
262                                 self.oldLocation = location
263                         elif firstWord == 'M101':
264                                 self.isExtrusionActive = True
265                         elif firstWord == 'M103':
266                                 self.isExtrusionActive = False
267                 self.extent = cornerMaximumComplex - cornerMinimumComplex
268                 self.shapeCenter = 0.5 * (cornerMaximumComplex + cornerMinimumComplex)
269                 self.separation = self.repository.separationOverEdgeWidth.value * self.absoluteEdgeWidth
270                 self.extentPlusSeparation = self.extent + complex(self.separation, self.separation)
271                 columnsMinusOne = self.numberOfColumns - 1
272                 rowsMinusOne = self.numberOfRows - 1
273                 self.arrayExtent = complex(self.extentPlusSeparation.real * columnsMinusOne, self.extentPlusSeparation.imag * rowsMinusOne)
274                 self.arrayCenter = 0.5 * self.arrayExtent
275
276
277 def main():
278         'Display the multiply dialog.'
279         if len(sys.argv) > 1:
280                 writeOutput(' '.join(sys.argv[1 :]))
281         else:
282                 settings.startMainLoopFromConstructor(getNewRepository())
283
284 if __name__ == '__main__':
285         main()