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