chiark / gitweb /
Move SF into its own directory, to seperate SF and Cura. Rename newui to gui.
[cura.git] / Cura / cura_sf / fabmetheus_utilities / geometry / creation / sponge_slice.py
1 """
2 Sponge slice.
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.evaluate_elements import setting
13 from fabmetheus_utilities.geometry.geometry_utilities import evaluate
14 from fabmetheus_utilities.vector3 import Vector3
15 from fabmetheus_utilities import euclidean
16 import math
17 import random
18 import time
19
20
21 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
22 __credits__ = 'Art of Illusion <http://www.artofillusion.org/>'
23 __date__ = '$Date: 2008/02/05 $'
24 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
25
26
27 def getGeometryOutput(derivation, elementNode):
28         "Get vector3 vertexes from attribute dictionary."
29         if derivation == None:
30                 derivation = SpongeSliceDerivation(elementNode)
31         awayPoints = []
32         vector3Path = euclidean.getVector3Path(euclidean.getSquareLoopWiddershins(-derivation.inradius, derivation.inradius))
33         geometryOutput = lineation.SideLoop(vector3Path).getManipulationPluginLoops(elementNode)
34         minimumDistanceFromOther = derivation.wallThickness + derivation.minimumRadius + derivation.minimumRadius
35         if derivation.inradiusMinusRadiusThickness.real <= 0.0 or derivation.inradiusMinusRadiusThickness.imag <= 0.0:
36                 return geometryOutput
37         for point in derivation.path:
38                 if abs(point.x) <= derivation.inradiusMinusRadiusThickness.real and abs(point.y) <= derivation.inradiusMinusRadiusThickness.imag:
39                         awayPoints.append(point)
40         awayCircles = []
41         for point in awayPoints:
42                 if getIsPointAway(minimumDistanceFromOther, point, awayCircles):
43                         awayCircles.append(SpongeCircle(point, derivation.minimumRadius))
44         averagePotentialBubbleArea = derivation.potentialBubbleArea / float(len(awayCircles))
45         averageBubbleRadius = math.sqrt(averagePotentialBubbleArea / math.pi) - 0.5 * derivation.wallThickness
46         sides = -4 * (max(evaluate.getSidesBasedOnPrecision(elementNode, averageBubbleRadius), 4) / 4)
47         sideAngle = math.pi / sides
48         cosSide = math.cos(sideAngle)
49         overlapArealRatio = (1 - cosSide) / cosSide
50         for circleIndex, circle in enumerate(awayCircles):
51                 otherCircles = awayCircles[: circleIndex] + awayCircles[circleIndex + 1 :]
52                 circle.radius = circle.getRadius(circle.center, derivation, otherCircles, overlapArealRatio)
53         if derivation.searchAttempts > 0:
54                 for circleIndex, circle in enumerate(awayCircles):
55                         otherCircles = awayCircles[: circleIndex] + awayCircles[circleIndex + 1 :]
56                         circle.moveCircle(derivation, otherCircles, overlapArealRatio)
57         for circle in awayCircles:
58                 vector3Path = euclidean.getVector3Path(euclidean.getComplexPolygon(circle.center.dropAxis(), circle.radius, sides, sideAngle))
59                 geometryOutput += lineation.SideLoop(vector3Path).getManipulationPluginLoops(elementNode)
60         return geometryOutput
61
62 def getGeometryOutputByArguments(arguments, elementNode):
63         "Get vector3 vertexes from attribute dictionary by arguments."
64         return getGeometryOutput(None, elementNode)
65
66 def getIsPointAway(minimumDistance, point, spongeCircles):
67         'Determine if the point is at least the minimumDistance away from other points.'
68         for otherSpongeCircle in spongeCircles:
69                 if abs(otherSpongeCircle.center - point) < minimumDistance:
70                         return False
71         return True
72
73 def getNewDerivation(elementNode):
74         'Get new derivation.'
75         return SpongeSliceDerivation(elementNode)
76
77 def processElementNode(elementNode):
78         "Process the xml element."
79         path.convertElementNode(elementNode, getGeometryOutput(None, elementNode))
80
81
82 class SpongeCircle:
83         "Class to hold sponge circle."
84         def __init__(self, center, radius=0.0):
85                 'Initialize.'
86                 self.center = center
87                 self.radius = radius
88
89         def getRadius(self, center, derivation, otherCircles, overlapArealRatio):
90                 'Get sponge bubble radius.'
91                 radius = 987654321.0
92                 for otherSpongeCircle in otherCircles:
93                         distance = abs(otherSpongeCircle.center.dropAxis() - center.dropAxis())
94                         radius = min(distance - derivation.wallThickness - otherSpongeCircle.radius, radius)
95                 overlapAreal = overlapArealRatio * radius
96                 radius = min(derivation.inradiusMinusThickness.real + overlapAreal - abs(center.x), radius)
97                 return min(derivation.inradiusMinusThickness.imag + overlapAreal - abs(center.y), radius)
98
99         def moveCircle(self, derivation, otherCircles, overlapArealRatio):
100                 'Move circle into an open spot.'
101                 angle = (abs(self.center) + self.radius) % euclidean.globalTau
102                 movedCenter = self.center
103                 searchRadius = derivation.searchRadiusOverRadius * self.radius
104                 distanceIncrement = searchRadius / float(derivation.searchAttempts)
105                 distance = 0.0
106                 greatestRadius = self.radius
107                 searchCircles = []
108                 searchCircleDistance = searchRadius + searchRadius + self.radius + derivation.wallThickness
109                 for otherCircle in otherCircles:
110                         if abs(self.center - otherCircle.center) <= searchCircleDistance + otherCircle.radius:
111                                 searchCircles.append(otherCircle)
112                 for attemptIndex in xrange(derivation.searchAttempts):
113                         angle += euclidean.globalGoldenAngle
114                         distance += distanceIncrement
115                         offset = distance * euclidean.getWiddershinsUnitPolar(angle)
116                         attemptCenter = self.center + Vector3(offset.real, offset.imag)
117                         radius = self.getRadius(attemptCenter, derivation, searchCircles, overlapArealRatio)
118                         if radius > greatestRadius:
119                                 greatestRadius = radius
120                                 movedCenter = attemptCenter
121                 self.center = movedCenter
122                 self.radius = greatestRadius
123
124
125 class SpongeSliceDerivation:
126         "Class to hold sponge slice variables."
127         def __init__(self, elementNode):
128                 'Initialize.'
129                 elementNode.attributes['closed'] = 'true'
130                 self.density = evaluate.getEvaluatedFloat(1.0, elementNode, 'density')
131                 self.minimumRadiusOverThickness = evaluate.getEvaluatedFloat(1.0, elementNode, 'minimumRadiusOverThickness')
132                 self.mobile = evaluate.getEvaluatedBoolean(False, elementNode, 'mobile')
133                 self.inradius = lineation.getInradius(complex(10.0, 10.0), elementNode)
134                 self.path = None
135                 if 'path' in elementNode.attributes:
136                         self.path = evaluate.getPathByKey([], elementNode, 'path')
137                 self.searchAttempts = evaluate.getEvaluatedInt(0, elementNode, 'searchAttempts')
138                 self.searchRadiusOverRadius = evaluate.getEvaluatedFloat(1.0, elementNode, 'searchRadiusOverRadius')
139                 self.seed = evaluate.getEvaluatedInt(None, elementNode, 'seed')
140                 self.wallThickness = evaluate.getEvaluatedFloat(2.0 * setting.getEdgeWidth(elementNode), elementNode, 'wallThickness')
141                 # Set derived variables.
142                 self.halfWallThickness = 0.5 * self.wallThickness
143                 self.inradiusMinusThickness = self.inradius - complex(self.wallThickness, self.wallThickness)
144                 self.minimumRadius = evaluate.getEvaluatedFloat(self.minimumRadiusOverThickness * self.wallThickness, elementNode, 'minimumRadius')
145                 self.inradiusMinusRadiusThickness = self.inradiusMinusThickness - complex(self.minimumRadius, self.minimumRadius)
146                 self.potentialBubbleArea = 4.0 * self.inradiusMinusThickness.real * self.inradiusMinusThickness.imag
147                 if self.path == None:
148                         radiusPlusHalfThickness = self.minimumRadius + self.halfWallThickness
149                         numberOfPoints = int(math.ceil(self.density * self.potentialBubbleArea / math.pi / radiusPlusHalfThickness / radiusPlusHalfThickness))
150                         self.path = []
151                         if self.seed == None:
152                                 self.seed = time.time()
153                                 print('Sponge slice seed used was: %s' % self.seed)
154                         random.seed(self.seed)
155                         for pointIndex in xrange(numberOfPoints):
156                                 point = euclidean.getRandomComplex(-self.inradiusMinusRadiusThickness, self.inradiusMinusRadiusThickness)
157                                 self.path.append(Vector3(point.real, point.imag))