chiark / gitweb /
Add uppercase STL and HEX to file dialog filters for linux/MacOS
[cura.git] / Cura / cura_sf / fabmetheus_utilities / geometry / creation / grid.py
1 """
2 Grid path points.
3
4 """
5
6 from __future__ import absolute_import
7 #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.
8 import __init__
9
10 from fabmetheus_utilities.geometry.creation import lineation
11 from fabmetheus_utilities.geometry.geometry_tools import path
12 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
13 from fabmetheus_utilities.vector3 import Vector3
14 from fabmetheus_utilities import euclidean
15 import math
16 import random
17
18
19 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
20 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
21 __date__ = '$Date: 2008/02/05 $'
22 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
23
24
25 def addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, x, y, zigzag):
26         'Add grid row.'
27         row = []
28         while x < maximumComplex.real:
29                 point = complex(x, y)
30                 if euclidean.getIsInFilledRegion(loopsComplex, point):
31                         row.append(point)
32                 x += diameter.real
33         if zigzag and rowIndex % 2 == 1:
34                 row.reverse()
35         gridPath += row
36
37 def getGeometryOutput(elementNode):
38         'Get vector3 vertexes from attribute dictionary.'
39         derivation = GridDerivation(elementNode)
40         diameter = derivation.radius + derivation.radius
41         typeStringTwoCharacters = derivation.typeString.lower()[: 2]
42         typeStringFirstCharacter = typeStringTwoCharacters[: 1]
43         topRight = complex(derivation.demiwidth, derivation.demiheight)
44         loopsComplex = [euclidean.getSquareLoopWiddershins(-topRight, topRight)]
45         if len(derivation.target) > 0:
46                 loopsComplex = euclidean.getComplexPaths(derivation.target)
47         maximumComplex = euclidean.getMaximumByComplexPaths(loopsComplex)
48         minimumComplex = euclidean.getMinimumByComplexPaths(loopsComplex)
49         gridPath = None
50         if typeStringTwoCharacters == 'he':
51                 gridPath = getHexagonalGrid(diameter, loopsComplex, maximumComplex, minimumComplex, derivation.zigzag)
52         elif typeStringTwoCharacters == 'ra' or typeStringFirstCharacter == 'a':
53                 gridPath = getRandomGrid(derivation, diameter, elementNode, loopsComplex, maximumComplex, minimumComplex)
54         elif typeStringTwoCharacters == 're' or typeStringFirstCharacter == 'e':
55                 gridPath = getRectangularGrid(diameter, loopsComplex, maximumComplex, minimumComplex, derivation.zigzag)
56         if gridPath == None:
57                 print('Warning, the step type was not one of (hexagonal, random or rectangular) in getGeometryOutput in grid for:')
58                 print(derivation.typeString)
59                 print(elementNode)
60                 return []
61         loop = euclidean.getVector3Path(gridPath)
62         elementNode.attributes['closed'] = 'false'
63         return lineation.getGeometryOutputByLoop(elementNode, lineation.SideLoop(loop, 0.5 * math.pi))
64
65 def getGeometryOutputByArguments(arguments, elementNode):
66         'Get vector3 vertexes from attribute dictionary by arguments.'
67         if len(arguments) < 1:
68                 return getGeometryOutput(elementNode)
69         inradius = 0.5 * euclidean.getFloatFromValue(arguments[0])
70         elementNode.attributes['inradius.x'] = str(inradius)
71         if len(arguments) > 1:
72                 inradius = 0.5 * euclidean.getFloatFromValue(arguments[1])
73         elementNode.attributes['inradius.y'] = str(inradius)
74         return getGeometryOutput(elementNode)
75
76 def getHexagonalGrid(diameter, loopsComplex, maximumComplex, minimumComplex, zigzag):
77         'Get hexagonal grid.'
78         diameter = complex(diameter.real, math.sqrt(0.75) * diameter.imag)
79         demiradius = 0.25 * diameter
80         xRadius = 0.5 * diameter.real
81         xStart = minimumComplex.real - demiradius.real
82         y = minimumComplex.imag - demiradius.imag
83         gridPath = []
84         rowIndex = 0
85         while y < maximumComplex.imag:
86                 x = xStart
87                 if rowIndex % 2 == 1:
88                         x -= xRadius
89                 addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, x, y, zigzag)
90                 y += diameter.imag
91                 rowIndex += 1
92         return gridPath
93
94 def getIsPointInsideZoneAwayOthers(diameterReciprocal, loopsComplex, point, pixelDictionary):
95         'Determine if the point is inside the loops zone and and away from the other points.'
96         if not euclidean.getIsInFilledRegion(loopsComplex, point):
97                 return False
98         pointOverDiameter = complex(point.real * diameterReciprocal.real, point.imag * diameterReciprocal.imag)
99         squareValues = euclidean.getSquareValuesFromPoint(pixelDictionary, pointOverDiameter)
100         for squareValue in squareValues:
101                 if abs(squareValue - pointOverDiameter) < 1.0:
102                         return False
103         euclidean.addElementToPixelListFromPoint(pointOverDiameter, pixelDictionary, pointOverDiameter)
104         return True
105
106 def getNewDerivation(elementNode):
107         'Get new derivation.'
108         return GridDerivation(elementNode)
109
110 def getRandomGrid(derivation, diameter, elementNode, loopsComplex, maximumComplex, minimumComplex):
111         'Get rectangular grid.'
112         gridPath = []
113         diameterReciprocal = complex(1.0 / diameter.real, 1.0 / diameter.imag)
114         diameterSquared = diameter.real * diameter.real + diameter.imag * diameter.imag
115         elements = int(math.ceil(derivation.density * euclidean.getAreaLoops(loopsComplex) / diameterSquared / math.sqrt(0.75)))
116         elements = evaluate.getEvaluatedInt(elements, elementNode, 'elements')
117         failedPlacementAttempts = 0
118         pixelDictionary = {}
119         if derivation.seed != None:
120                 random.seed(derivation.seed)
121         successfulPlacementAttempts = 0
122         while failedPlacementAttempts < 100:
123                 point = euclidean.getRandomComplex(minimumComplex, maximumComplex)
124                 if getIsPointInsideZoneAwayOthers(diameterReciprocal, loopsComplex, point, pixelDictionary):
125                         gridPath.append(point)
126                         euclidean.addElementToPixelListFromPoint(point, pixelDictionary, point)
127                         successfulPlacementAttempts += 1
128                 else:
129                         failedPlacementAttempts += 1
130                 if successfulPlacementAttempts >= elements:
131                         return gridPath
132         return gridPath
133
134 def getRectangularGrid(diameter, loopsComplex, maximumComplex, minimumComplex, zigzag):
135         'Get rectangular grid.'
136         demiradius = 0.25 * diameter
137         xStart = minimumComplex.real - demiradius.real
138         y = minimumComplex.imag - demiradius.imag
139         gridPath = []
140         rowIndex = 0
141         while y < maximumComplex.imag:
142                 addGridRow(diameter, gridPath, loopsComplex, maximumComplex, rowIndex, xStart, y, zigzag)
143                 y += diameter.imag
144                 rowIndex += 1
145         return gridPath
146
147 def processElementNode(elementNode):
148         'Process the xml element.'
149         path.convertElementNode(elementNode, getGeometryOutput(elementNode))
150
151
152 class GridDerivation:
153         'Class to hold grid variables.'
154         def __init__(self, elementNode):
155                 'Set defaults.'
156                 self.inradius = lineation.getInradius(complex(10.0, 10.0), elementNode)
157                 self.demiwidth = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiwidth', 'width', self.inradius.real)
158                 self.demiheight = lineation.getFloatByPrefixBeginEnd(elementNode, 'demiheight', 'height', self.inradius.imag)
159                 self.density = evaluate.getEvaluatedFloat(0.2, elementNode, 'density')
160                 self.radius = lineation.getComplexByPrefixBeginEnd(elementNode, 'elementRadius', 'elementDiameter', complex(1.0, 1.0))
161                 self.radius = lineation.getComplexByPrefixBeginEnd(elementNode, 'radius', 'diameter', self.radius)
162                 self.seed = evaluate.getEvaluatedInt(None, elementNode, 'seed')
163                 self.target = evaluate.getTransformedPathsByKey([], elementNode, 'target')
164                 self.typeMenuRadioStrings = 'hexagonal random rectangular'.split()
165                 self.typeString = evaluate.getEvaluatedString('rectangular', elementNode, 'type')
166                 self.zigzag = evaluate.getEvaluatedBoolean(True, elementNode, 'zigzag')