chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / fabmetheus_utilities / geometry / creation / heightmap.py
1 """
2 Heightmap.
3 http://www.cs.otago.ac.nz/graphics/Mirage/node59.html
4 http://en.wikipedia.org/wiki/Heightmap
5 http://en.wikipedia.org/wiki/Netpbm_format
6
7 """
8
9 from __future__ import absolute_import
10
11 from fabmetheus_utilities.geometry.creation import solid
12 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
13 from fabmetheus_utilities.geometry.solids import triangle_mesh
14 from fabmetheus_utilities.vector3 import Vector3
15 from fabmetheus_utilities.vector3index import Vector3Index
16 from fabmetheus_utilities import archive
17 from fabmetheus_utilities import euclidean
18
19
20 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
21 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
22 __date__ = '$Date: 2008/02/05 $'
23 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
24
25
26 def addHeightsByBitmap(heights, textLines):
27         'Add heights by bitmap.'
28         for line in textLines[3:]:
29                 for integerWord in line.split():
30                         heights.append(float(integerWord))
31
32 def addHeightsByGraymap(heights, textLines):
33         'Add heights by graymap.'
34         divisor = float(textLines[3])
35         for line in textLines[4:]:
36                 for integerWord in line.split():
37                         heights.append(float(integerWord) / divisor)
38
39 def getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes):
40         'Get and add an indexed heightGrid.'
41         indexedHeightGrid = []
42         for rowIndex, row in enumerate(heightGrid):
43                 indexedRow = []
44                 indexedHeightGrid.append(indexedRow)
45                 rowOffset = step.imag * float(rowIndex) + minimumXY.imag
46                 for columnIndex, element in enumerate(row):
47                         columnOffset = step.real * float(columnIndex) + minimumXY.real
48                         vector3index = Vector3Index(len(vertexes), columnOffset, rowOffset, top * element)
49                         indexedRow.append(vector3index)
50                         vertexes.append(vector3index)
51         return indexedHeightGrid
52
53 def getAddIndexedSegmentedPerimeter(heightGrid, maximumXY, minimumXY, step, vertexes, z=0.0):
54         'Get and add an indexed segmented perimeter.'
55         indexedSegmentedPerimeter = []
56         firstRow = heightGrid[0]
57         columnOffset = minimumXY.real
58         numberOfRowsMinusTwo = len(heightGrid) - 2
59         for column in firstRow:
60                 vector3index = Vector3Index(len(vertexes), columnOffset, minimumXY.imag, z)
61                 vertexes.append(vector3index)
62                 indexedSegmentedPerimeter.append(vector3index)
63                 columnOffset += step.real
64         rowOffset = minimumXY.imag
65         for rowIndex in xrange(numberOfRowsMinusTwo):
66                 rowOffset += step.imag
67                 vector3index = Vector3Index(len(vertexes), maximumXY.real, rowOffset, z)
68                 vertexes.append(vector3index)
69                 indexedSegmentedPerimeter.append(vector3index)
70         columnOffset = maximumXY.real
71         for column in firstRow:
72                 vector3index = Vector3Index(len(vertexes), columnOffset, maximumXY.imag, z)
73                 vertexes.append(vector3index)
74                 indexedSegmentedPerimeter.append(vector3index)
75                 columnOffset -= step.real
76         rowOffset = maximumXY.imag
77         for rowIndex in xrange(numberOfRowsMinusTwo):
78                 rowOffset -= step.imag
79                 vector3index = Vector3Index(len(vertexes), minimumXY.real, rowOffset, z)
80                 vertexes.append(vector3index)
81                 indexedSegmentedPerimeter.append(vector3index)
82         return indexedSegmentedPerimeter
83
84 def getGeometryOutput(elementNode):
85         'Get vector3 vertexes from attribute dictionary.'
86         derivation = HeightmapDerivation(elementNode)
87         heightGrid = derivation.heightGrid
88         if derivation.fileName != '':
89                 heightGrid = getHeightGrid(archive.getAbsoluteFolderPath(elementNode.getOwnerDocument().fileName, derivation.fileName))
90         return getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid)
91
92 def getGeometryOutputByArguments(arguments, elementNode):
93         'Get vector3 vertexes from attribute dictionary by arguments.'
94         evaluate.setAttributesByArguments(['file', 'start'], arguments, elementNode)
95         return getGeometryOutput(elementNode)
96
97 def getGeometryOutputByHeightGrid(derivation, elementNode, heightGrid):
98         'Get vector3 vertexes from attribute dictionary.'
99         numberOfColumns = len(heightGrid)
100         if numberOfColumns < 2:
101                 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two rows for:')
102                 print(heightGrid)
103                 print(elementNode)
104                 return None
105         numberOfRows = len(heightGrid[0])
106         if numberOfRows < 2:
107                 print('Warning, in getGeometryOutputByHeightGrid in heightmap there are fewer than two columns for:')
108                 print(heightGrid)
109                 print(elementNode)
110                 return None
111         for row in heightGrid:
112                 if len(row) != numberOfRows:
113                         print('Warning, in getGeometryOutputByHeightGrid in heightmap the heightgrid is not rectangular for:')
114                         print(heightGrid)
115                         print(elementNode)
116                         return None
117         inradiusComplex = derivation.inradius.dropAxis()
118         minimumXY = -inradiusComplex
119         step = complex(derivation.inradius.x / float(numberOfRows - 1), derivation.inradius.y / float(numberOfColumns - 1))
120         step += step
121         faces = []
122         heightGrid = getRaisedHeightGrid(heightGrid, derivation.start)
123         top = derivation.inradius.z + derivation.inradius.z
124         vertexes = []
125         indexedBottomLoop = getAddIndexedSegmentedPerimeter(heightGrid, inradiusComplex, minimumXY, step, vertexes)
126         indexedLoops = [indexedBottomLoop]
127         indexedGridTop = getAddIndexedHeightGrid(heightGrid, minimumXY, step, top, vertexes)
128         indexedLoops.append(triangle_mesh.getIndexedLoopFromIndexedGrid(indexedGridTop))
129         vertexes = triangle_mesh.getUniqueVertexes(indexedLoops + indexedGridTop)
130         triangle_mesh.addPillarFromConvexLoopsGridTop(faces, indexedGridTop, indexedLoops)
131         return triangle_mesh.getGeometryOutputByFacesVertexes(faces, vertexes)
132
133 def getHeightGrid(fileName):
134         'Get heightGrid by fileName.'
135         if 'models/' not in fileName:
136                 print('Warning, models/ was not in the absolute file path, so for security nothing will be done for:')
137                 print(fileName)
138                 print('The heightmap tool can only read a file which has models/ in the file path.')
139                 print('To import the file, move the file into a folder called model/ or a subfolder which is inside the model folder tree.')
140                 return
141         pgmText = archive.getFileText(fileName)
142         textLines = archive.getTextLines(pgmText)
143         format = textLines[0].lower()
144         sizeWords = textLines[2].split()
145         numberOfColumns = int(sizeWords[0])
146         numberOfRows = int(sizeWords[1])
147         heights = []
148         if format == 'p1':
149                 addHeightsByBitmap(heights, textLines)
150         elif format == 'p2':
151                 addHeightsByGraymap(heights, textLines)
152         else:
153                 print('Warning, the file format was not recognized for:')
154                 print(fileName)
155                 print('Heightmap can only read the Netpbm Portable bitmap format and the Netpbm Portable graymap format.')
156                 print('The Netpbm formats are described at:')
157                 print('http://en.wikipedia.org/wiki/Netpbm_format')
158                 return []
159         heightGrid = []
160         heightIndex = 0
161         for rowIndex in xrange(numberOfRows):
162                 row = []
163                 heightGrid.append(row)
164                 for columnIndex in xrange(numberOfColumns):
165                         row.append(heights[heightIndex])
166                         heightIndex += 1
167         return heightGrid
168
169 def getNewDerivation(elementNode):
170         'Get new derivation.'
171         return HeightmapDerivation(elementNode)
172
173 def getRaisedHeightGrid(heightGrid, start):
174         'Get heightGrid raised above start.'
175         raisedHeightGrid = []
176         remainingHeight = 1.0 - start
177         for row in heightGrid:
178                 raisedRow = []
179                 raisedHeightGrid.append(raisedRow)
180                 for element in row:
181                         raisedElement = remainingHeight * element + start
182                         raisedRow.append(raisedElement)
183         return raisedHeightGrid
184
185 def processElementNode(elementNode):
186         'Process the xml element.'
187         solid.processElementNodeByGeometry(elementNode, getGeometryOutput(elementNode))
188
189
190 class HeightmapDerivation(object):
191         'Class to hold heightmap variables.'
192         def __init__(self, elementNode):
193                 'Set defaults.'
194                 self.fileName = evaluate.getEvaluatedString('', elementNode, 'file')
195                 self.heightGrid = evaluate.getEvaluatedValue([], elementNode, 'heightGrid')
196                 self.inradius = evaluate.getVector3ByPrefixes(elementNode, ['demisize', 'inradius'], Vector3(10.0, 10.0, 5.0))
197                 self.inradius = evaluate.getVector3ByMultiplierPrefix(elementNode, 2.0, 'size', self.inradius)
198                 self.start = evaluate.getEvaluatedFloat(0.0, elementNode, 'start')
199
200         def __repr__(self):
201                 'Get the string representation of this HeightmapDerivation.'
202                 return euclidean.getDictionaryString(self.__dict__)