utils3d.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. from reportlab.lib import colors
  2. from reportlab.lib.attrmap import *
  3. from reportlab.pdfgen.canvas import Canvas
  4. from reportlab.graphics.shapes import Group, Drawing, Ellipse, Wedge, String, STATE_DEFAULTS, Polygon, Line
  5. def _getShaded(col,shd=None,shading=0.1):
  6. if shd is None:
  7. from reportlab.lib.colors import Blacker
  8. if col: shd = Blacker(col,1-shading)
  9. return shd
  10. def _getLit(col,shd=None,lighting=0.1):
  11. if shd is None:
  12. from reportlab.lib.colors import Whiter
  13. if col: shd = Whiter(col,1-lighting)
  14. return shd
  15. def _draw_3d_bar(G, x1, x2, y0, yhigh, xdepth, ydepth,
  16. fillColor=None, fillColorShaded=None,
  17. strokeColor=None, strokeWidth=1, shading=0.1):
  18. fillColorShaded = _getShaded(fillColor,None,shading)
  19. fillColorShadedTop = _getShaded(fillColor,None,shading/2.0)
  20. def _add_3d_bar(x1, x2, y1, y2, xoff, yoff,
  21. G=G,strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor):
  22. G.add(Polygon((x1,y1, x1+xoff,y1+yoff, x2+xoff,y2+yoff, x2,y2),
  23. strokeWidth=strokeWidth, strokeColor=strokeColor, fillColor=fillColor,strokeLineJoin=1))
  24. usd = max(y0, yhigh)
  25. if xdepth or ydepth:
  26. if y0!=yhigh: #non-zero height
  27. _add_3d_bar( x2, x2, y0, yhigh, xdepth, ydepth, fillColor=fillColorShaded) #side
  28. _add_3d_bar(x1, x2, usd, usd, xdepth, ydepth, fillColor=fillColorShadedTop) #top
  29. G.add(Polygon((x1,y0,x2,y0,x2,yhigh,x1,yhigh),
  30. strokeColor=strokeColor, strokeWidth=strokeWidth, fillColor=fillColor,strokeLineJoin=1)) #front
  31. if xdepth or ydepth:
  32. G.add(Line( x1, usd, x2, usd, strokeWidth=strokeWidth, strokeColor=strokeColor or fillColorShaded))
  33. class _YStrip:
  34. def __init__(self,y0,y1, slope, fillColor, fillColorShaded, shading=0.1):
  35. self.y0 = y0
  36. self.y1 = y1
  37. self.slope = slope
  38. self.fillColor = fillColor
  39. self.fillColorShaded = _getShaded(fillColor,fillColorShaded,shading)
  40. def _ystrip_poly( x0, x1, y0, y1, xoff, yoff):
  41. return [x0,y0,x0+xoff,y0+yoff,x1+xoff,y1+yoff,x1,y1]
  42. def _make_3d_line_info( G, x0, x1, y0, y1, z0, z1,
  43. theta_x, theta_y,
  44. fillColor, fillColorShaded=None, tileWidth=1,
  45. strokeColor=None, strokeWidth=None, strokeDashArray=None,
  46. shading=0.1):
  47. zwidth = abs(z1-z0)
  48. xdepth = zwidth*theta_x
  49. ydepth = zwidth*theta_y
  50. depth_slope = xdepth==0 and 1e150 or -ydepth/float(xdepth)
  51. x = float(x1-x0)
  52. slope = x==0 and 1e150 or (y1-y0)/x
  53. c = slope>depth_slope and _getShaded(fillColor,fillColorShaded,shading) or fillColor
  54. zy0 = z0*theta_y
  55. zx0 = z0*theta_x
  56. tileStrokeWidth = 0.6
  57. if tileWidth is None:
  58. D = [(x1,y1)]
  59. else:
  60. T = ((y1-y0)**2+(x1-x0)**2)**0.5
  61. tileStrokeWidth *= tileWidth
  62. if T<tileWidth:
  63. D = [(x1,y1)]
  64. else:
  65. n = int(T/float(tileWidth))+1
  66. dx = float(x1-x0)/n
  67. dy = float(y1-y0)/n
  68. D = []
  69. a = D.append
  70. for i in range(1,n):
  71. a((x0+dx*i,y0+dy*i))
  72. a = G.add
  73. x_0 = x0+zx0
  74. y_0 = y0+zy0
  75. for x,y in D:
  76. x_1 = x+zx0
  77. y_1 = y+zy0
  78. P = Polygon(_ystrip_poly(x_0, x_1, y_0, y_1, xdepth, ydepth),
  79. fillColor = c, strokeColor=c, strokeWidth=tileStrokeWidth)
  80. a((0,z0,z1,x_0,y_0,P))
  81. x_0 = x_1
  82. y_0 = y_1
  83. from math import pi, sin, cos
  84. _pi_2 = pi*0.5
  85. _2pi = 2*pi
  86. _180_pi=180./pi
  87. def _2rad(angle):
  88. return angle/_180_pi
  89. def mod_2pi(radians):
  90. radians = radians % _2pi
  91. if radians<-1e-6: radians += _2pi
  92. return radians
  93. def _2deg(o):
  94. return o*_180_pi
  95. def _360(a):
  96. a %= 360
  97. if a<-1e-6: a += 360
  98. return a
  99. _ZERO = 1e-8
  100. _ONE = 1-_ZERO
  101. class _Segment:
  102. def __init__(self,s,i,data):
  103. S = data[s]
  104. x0 = S[i-1][0]
  105. y0 = S[i-1][1]
  106. x1 = S[i][0]
  107. y1 = S[i][1]
  108. if x1<x0:
  109. x0,y0,x1,y1 = x1,y1,x0,y0
  110. # (y-y0)*(x1-x0) = (y1-y0)*(x-x0)
  111. # (x1-x0)*y + (y0-y1)*x = y0*(x1-x0)+x0*(y0-y1)
  112. # a*y+b*x = c
  113. self.a = float(x1-x0)
  114. self.b = float(y1-y0)
  115. self.x0 = x0
  116. self.x1 = x1
  117. self.y0 = y0
  118. self.y1 = y1
  119. self.series = s
  120. self.i = i
  121. self.s = s
  122. def __str__(self):
  123. return '[(%s,%s),(%s,%s)]' % (self.x0,self.y0,self.x1,self.y1)
  124. __repr__ = __str__
  125. def intersect(self,o,I):
  126. '''try to find an intersection with _Segment o
  127. '''
  128. x0 = self.x0
  129. ox0 = o.x0
  130. assert x0<=ox0
  131. if ox0>self.x1: return 1
  132. if o.s==self.s and o.i in (self.i-1,self.i+1): return
  133. a = self.a
  134. b = self.b
  135. oa = o.a
  136. ob = o.b
  137. det = ob*a - oa*b
  138. if -1e-8<det<1e-8: return
  139. dx = x0 - ox0
  140. dy = self.y0 - o.y0
  141. u = (oa*dy - ob*dx)/det
  142. ou = (a*dy - b*dx)/det
  143. if u<0 or u>1 or ou<0 or ou>1: return
  144. x = x0 + u*a
  145. y = self.y0 + u*b
  146. if _ZERO<u<_ONE:
  147. t = self.s,self.i,x,y
  148. if t not in I: I.append(t)
  149. if _ZERO<ou<_ONE:
  150. t = o.s,o.i,x,y
  151. if t not in I: I.append(t)
  152. def _segKey(a):
  153. return (a.x0,a.x1,a.y0,a.y1,a.s,a.i)
  154. def find_intersections(data,small=0):
  155. '''
  156. data is a sequence of series
  157. each series is a list of (x,y) coordinates
  158. where x & y are ints or floats
  159. find_intersections returns a sequence of 4-tuples
  160. i, j, x, y
  161. where i is a data index j is an insertion position for data[i]
  162. and x, y are coordinates of an intersection of series data[i]
  163. with some other series. If correctly implemented we get all such
  164. intersections. We don't count endpoint intersections and consider
  165. parallel lines as non intersecting (even when coincident).
  166. We ignore segments that have an estimated size less than small.
  167. '''
  168. #find all line segments
  169. S = []
  170. a = S.append
  171. for s in range(len(data)):
  172. ds = data[s]
  173. if not ds: continue
  174. n = len(ds)
  175. if n==1: continue
  176. for i in range(1,n):
  177. seg = _Segment(s,i,data)
  178. if seg.a+abs(seg.b)>=small: a(seg)
  179. S.sort(key=_segKey)
  180. I = []
  181. n = len(S)
  182. for i in range(0,n-1):
  183. s = S[i]
  184. for j in range(i+1,n):
  185. if s.intersect(S[j],I)==1: break
  186. I.sort()
  187. return I
  188. if __name__=='__main__':
  189. from reportlab.graphics.shapes import Drawing
  190. from reportlab.lib.colors import lightgrey, pink
  191. D = Drawing(300,200)
  192. _draw_3d_bar(D, 10, 20, 10, 50, 5, 5, fillColor=lightgrey, strokeColor=pink)
  193. _draw_3d_bar(D, 30, 40, 10, 45, 5, 5, fillColor=lightgrey, strokeColor=pink)
  194. D.save(formats=['pdf'],outDir='.',fnRoot='_draw_3d_bar')
  195. print(find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(.2666666667,0.4),(0.1,0.4),(0.1,0.2),(0,0),(1,1)],[(0,1),(0.4,0.1),(1,0.1)]]))
  196. print(find_intersections([[(0.1, 0.2), (0.1, 0.4)], [(0, 1), (0.4, 0.1)]]))
  197. print(find_intersections([[(0.2, 0.4), (0.1, 0.4)], [(0.1, 0.8), (0.4, 0.1)]]))
  198. print(find_intersections([[(0,0),(1,1)],[(0.4,0.1),(1,0.1)]]))
  199. print(find_intersections([[(0,0.5),(1,0.5),(0.5,0),(0.5,1)],[(0,0),(1,1)],[(0.1,0.8),(0.4,0.1),(1,0.1)]]))