title: How to export interactive 3D plot (.obj/html) that can be rotated 360 degree by users (Python only)
body: <p>I am trying to visulize the result from a 3D bin packing model (showing how different size of items are packed in a larger bin).</p>
  <p>Below is the full code and a visulization method using matplotlib3D. However, it cannot be saved in a 3D file like format which allow user to rotate it 360 degree after saving it to the desktop as png format. According to the model calculated position data for each item, how to generate .obj file with the same looking as matplotlib3D plot, or in html which allows it open in browser to rotate the picture.</p>
  <pre><code>from py3dbp import Packer, Bin, Item
  from mpl_toolkits.mplot3d import Axes3D
  from mpl_toolkits.mplot3d.art3d import Poly3DCollection
  import numpy as np
  import matplotlib.pyplot as plt
  import random
  
  trucks = [
      [250, 250, 500],
      [500, 500, 400],
      [300, 300, 300],
      [300, 300, 200],
      [300, 300, 100],
      [500, 500, 500]
  ]
  
  for t in range(len(trucks)):
      packer = Packer()
  
      # packer.add_bin(Bin('small-envelope', 11.5, 6.125, 0.25, 10))
      # packer.add_bin(Bin('large-envelope', 15.0, 12.0, 0.75, 15))
      # packer.add_bin(Bin('small-box', 8.625, 5.375, 1.625, 70.0))
      # packer.add_bin(Bin('medium-box', 11.0, 8.5, 5.5, 70.0))
      # packer.add_bin(Bin('medium-2-box', 13.625, 11.875, 3.375, 70.0))
      # packer.add_bin(Bin('large-box', 240, 244, 1360, 70.0))
      # packer.add_bin(Bin('large-2-box', 23.6875, 11.75, 3.0, 70.0))
  
      truckX = trucks[t][0]
      truckY = trucks[t][1]
      truckZ = trucks[t][2]
  
      packer.add_bin(Bin('LB', truckX, truckY, truckZ, 3000.0))
  
      for i in range(300):
          packer.add_item(Item('boxL' + str(i), 20, 40, 20, 1))
  
      for i in range(10):
          packer.add_item(Item('boxU' + str(i), 100, 100, 100, 1))
  
      for i in range(5):
          packer.add_item(Item('boxU' + str(i), 200, 100, 50, 1))
  
      for i in range(10):
          packer.add_item(Item('boxU' + str(i), 40, 40, 20, 1))
  
      # packer.pack()
      packer.pack(bigger_first=False)
  
      positions = []
      sizes = []
      colors = []
  
      for b in packer.bins:
          print(&quot;:::::::::::&quot;, b.string())
  
          print(&quot;FITTED ITEMS:&quot;)
          for item in b.items:
              print(&quot;====&gt; &quot;, item.string())
              x = float(item.position[0])
              y = float(item.position[1])
              z = float(item.position[2])
              positions.append((x, y, z))
              sizes.append(
                  (float(item.get_dimension()[0]), float(item.get_dimension()[1]), float(item.get_dimension()[2])))
  
          print(&quot;UNFITTED ITEMS:&quot;)
          for item in b.unfitted_items:
              print(&quot;====&gt; &quot;, item.string())
  
          print(&quot;***************************************************&quot;)
          print(&quot;***************************************************&quot;)
  
  
      def cuboid_data2(o, size=(1, 1, 1)):
          X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
               [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
               [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
               [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
               [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
               [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
          X = np.array(X).astype(float)
          for i in range(3):
              X[:, :, i] *= size[i]
          X += np.array(o)
          return X
  
  
      def plotCubeAt2(positions, sizes=None, colors=None, **kwargs):
          if not isinstance(colors, (list, np.ndarray)): colors = [&quot;C0&quot;] * len(positions)
          if not isinstance(sizes, (list, np.ndarray)): sizes = [(1, 1, 1)] * len(positions)
          g = []
          for p, s, c in zip(positions, sizes, colors):
              g.append(cuboid_data2(p, size=s))
          return Poly3DCollection(np.concatenate(g),
                                  facecolors=np.repeat(colors, 6), **kwargs)
  
  
      colorList = [&quot;crimson&quot;, &quot;limegreen&quot;, &quot;g&quot;, &quot;r&quot;, &quot;c&quot;, &quot;m&quot;, &quot;y&quot;, &quot;k&quot;]
  
      for i in range(len(b.items)):
          f = random.randint(0, 7)
          colors.append(colorList[f])
  
      print(colors)
  
      fig = plt.figure()
      ax = fig.gca(projection='3d')
      ax.set_aspect('auto')
  
      pc = plotCubeAt2(positions, sizes, colors=colors, edgecolor=&quot;k&quot;)
      ax.add_collection3d(pc)
  
      ax.set_xlim([0, truckX])
      ax.set_ylim([0, truckY])
      ax.set_zlim([0, truckZ])
  
      plt.show()
  </code></pre>
  <p><a href="https://i.stack.imgur.com/lHh7r.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/lHh7r.png" alt="enter image description here" /></a></p>