diff --git a/flaskapp/static/img/model_thumbnails/BoatBase/graph-model.png b/flaskapp/static/img/model_thumbnails/BoatBase/graph-model.png index e4d76ea5bb471ff3b1494bba3c6d563878725e4a..1b15b4fb7f07b63fb31416088f9e5fd42e8e935e 100644 Binary files a/flaskapp/static/img/model_thumbnails/BoatBase/graph-model.png and b/flaskapp/static/img/model_thumbnails/BoatBase/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Canoe/graph-model.png b/flaskapp/static/img/model_thumbnails/Canoe/graph-model.png index 0fd207c398715f6b787d92e9d7af15ca2425e9ad..191faed786948313c9afcdc2accc743780d5b2e3 100644 Binary files a/flaskapp/static/img/model_thumbnails/Canoe/graph-model.png and b/flaskapp/static/img/model_thumbnails/Canoe/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/CatFoil/graph-model.png b/flaskapp/static/img/model_thumbnails/CatFoil/graph-model.png index 535e3a6a71b69be5fe7e7b61ae86fbf03dc72288..cac59e5a51c38761686a1fb3a67e72e28d8c29fc 100644 Binary files a/flaskapp/static/img/model_thumbnails/CatFoil/graph-model.png and b/flaskapp/static/img/model_thumbnails/CatFoil/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Catamaran/graph-model.png b/flaskapp/static/img/model_thumbnails/Catamaran/graph-model.png index 95c1477ddf564bfbcf55ffb2961fac5c94126723..1c9bdf624c91f1bbf7abf1dc33dc8d58651a55c2 100644 Binary files a/flaskapp/static/img/model_thumbnails/Catamaran/graph-model.png and b/flaskapp/static/img/model_thumbnails/Catamaran/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Paperbot/graph-model.png b/flaskapp/static/img/model_thumbnails/Paperbot/graph-model.png index 931d9b2fcca20aa119f69ae4ca9ddbaf1e85281a..5f7d9a961928b62b08819d730e4d6a80cad9a594 100644 Binary files a/flaskapp/static/img/model_thumbnails/Paperbot/graph-model.png and b/flaskapp/static/img/model_thumbnails/Paperbot/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/RockerChair/graph-model.png b/flaskapp/static/img/model_thumbnails/RockerChair/graph-model.png index caa9635e8c6732dbd80a18917d65e832e5d37911..84c183c4ecd6b29ff3189db11412825e55c2869e 100644 Binary files a/flaskapp/static/img/model_thumbnails/RockerChair/graph-model.png and b/flaskapp/static/img/model_thumbnails/RockerChair/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/SimpleChair/graph-model.png b/flaskapp/static/img/model_thumbnails/SimpleChair/graph-model.png index 0a78d5cd184b3e5f42f16e570303d2d825e2db11..ee1e8a2a5ab37de239d3f853e89da29247edf0aa 100644 Binary files a/flaskapp/static/img/model_thumbnails/SimpleChair/graph-model.png and b/flaskapp/static/img/model_thumbnails/SimpleChair/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/SimpleTable/graph-model.png b/flaskapp/static/img/model_thumbnails/SimpleTable/graph-model.png index 82916c2eca9324ddbd6fadf617ea56b5d9eab9cc..0efc9d26dc72b6b679b5e6f973a5d0837224fc68 100644 Binary files a/flaskapp/static/img/model_thumbnails/SimpleTable/graph-model.png and b/flaskapp/static/img/model_thumbnails/SimpleTable/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Stool/graph-model.png b/flaskapp/static/img/model_thumbnails/Stool/graph-model.png index 9f349ed719d8281d3702b732d83cee4bee5bae5b..f9b3f6f5e6f9ea07062011ff7ec8889313120a47 100644 Binary files a/flaskapp/static/img/model_thumbnails/Stool/graph-model.png and b/flaskapp/static/img/model_thumbnails/Stool/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Trimaran/graph-model.png b/flaskapp/static/img/model_thumbnails/Trimaran/graph-model.png index 83e008d2bfeafaf6c8c294191b851f54daa33e08..af5afcf6c454b3d0589d6d54e7ec8449709e68cb 100644 Binary files a/flaskapp/static/img/model_thumbnails/Trimaran/graph-model.png and b/flaskapp/static/img/model_thumbnails/Trimaran/graph-model.png differ diff --git a/flaskapp/static/img/model_thumbnails/Tug/graph-model.png b/flaskapp/static/img/model_thumbnails/Tug/graph-model.png index 2e4452331a93ecb29426563a2c9fb813dbc673f3..dbeb038a571243aaf2b7100ab8633ce9fa39f775 100644 Binary files a/flaskapp/static/img/model_thumbnails/Tug/graph-model.png and b/flaskapp/static/img/model_thumbnails/Tug/graph-model.png differ diff --git a/requirements.txt b/requirements.txt index 834bce3c0347430e312e7a5079a6a4e0302cdcb7..c46c73f30fb16772d08a0ddb460a5510de3d329d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ Flask-Markdown jinja2-highlight networkx numpy-stl -matplotlib +plotly +kaleido diff --git a/rocolib/utils/display.py b/rocolib/utils/display.py index 826dd821a527b7afd37198c431702b2699c0a277..5cc74c8a4e84ddafc5b5a70463708e6ea6f6bafc 100644 --- a/rocolib/utils/display.py +++ b/rocolib/utils/display.py @@ -3,10 +3,7 @@ import math import numpy import logging from stl import mesh -from mpl_toolkits import mplot3d -from matplotlib import pyplot -from matplotlib.colors import LightSource -from matplotlib import cm +import plotly.graph_objects as go from rocolib.api.composables.graph.Drawing import * from rocolib.api.composables.graph.DrawingEdge import * @@ -14,63 +11,76 @@ from rocolib.api.composables.graph.DrawingEdge import * log = logging.getLogger(__name__) -def display3D(fh, ph=None, show=False): +### copied from https://chart-studio.plotly.com/~empet/15276/converting-a-stl-mesh-to-plotly-gomes/#/ +def stl2mesh3d(stl_mesh): + # this function extracts the unique vertices and the lists I, J, K to define a Plotly mesh3d + p, q, r = stl_mesh.vectors.shape #(p, 3, 3) + # the array stl_mesh.vectors.reshape(p*q, r) can contain multiple copies of the same vertex; + # extract unique vertices from all mesh triangles + vertices, ixr = numpy.unique(stl_mesh.vectors.reshape(p*q, r), return_inverse=True, axis=0) + I = numpy.take(ixr, [3*k for k in range(p)]) + J = numpy.take(ixr, [3*k+1 for k in range(p)]) + K = numpy.take(ixr, [3*k+2 for k in range(p)]) + return vertices, I, J, K + +def plotlyFigure(stlmesh, color): + vertices, I, J, K = stl2mesh3d(stlmesh) + x, y, z = vertices.T + colorscale= [[0, color], [1, color]] + mesh3D = go.Mesh3d( + x=x, + y=y, + z=z, + i=I, + j=J, + k=K, + flatshading=True, + colorscale=colorscale, + intensity=z, + name='model', + showscale=False) + layout = go.Layout( + paper_bgcolor='rgba(0,0,0,0)', + plot_bgcolor='rgba(0,0,0,0)', + width=1024, + height=1024, + scene_xaxis_visible=False, + scene_yaxis_visible=False, + scene_zaxis_visible=False) + + fig = go.Figure(data=[mesh3D], layout=layout) + fig.update_layout(margin=dict(r=0, l=0, b=0, t=0), + scene_aspectmode='data', + width=1024) + fig.data[0].update(lighting=dict(ambient= 0.18, + diffuse= 1, + fresnel= .1, + specular= 0, + roughness= .1, + facenormalsepsilon=0)) + fig.data[0].update(lightposition=dict(x=3000, + y=3000, + z=10000)); + + return fig + +def display3D(fh, ph=None, show=False, color="#ccccff"): ### Modified from https://pypi.org/project/numpy-stl/ docs - ### and https://stackoverflow.com/questions/56864378/how-to-light-and-shade-a-poly3dcollection - - # Create a new plot - figure = pyplot.figure() - axes = mplot3d.Axes3D(figure) # Load the STL mesh stlmesh = mesh.Mesh.from_file(None, fh=fh) # Back to units of mm stlmesh.vectors *= 1000 - stlmesh.update_normals() - polymesh = mplot3d.art3d.Poly3DCollection(stlmesh.vectors) - if not len(stlmesh.points): - log.error("No points in STL mesh, skipping display3D entirely") - return - - # Apply light source - try: - ls = LightSource(azdeg=225, altdeg=45) - # Darkest shadowed surface, in rgba - dk = numpy.array([0.3, 0.1, 0.2, 1]) - # Brightest lit surface, in rgba - lt = numpy.array([0.7, 0.8, 1.0, 1]) - # Interpolate between the two, based on face normal - shade = lambda s: (lt-dk) * s + dk - - sns = ls.shade_normals(stlmesh.get_unit_normals(), fraction=1.0) - rgba = numpy.array([shade(s) for s in sns]) - polymesh.set_facecolor(rgba) - except Exception as e: - log.error("Couldn't shade normals, skipping") - log.error(repr(e)) - - axes.add_collection3d(polymesh) - - # Adjust limits of axes to fill the mesh, but keep 1:1:1 aspect ratio - try: - pts = stlmesh.points.reshape(-1,3) - ptp = max(numpy.ptp(pts, 0))/2.5 ### Should be 2; this is a bit bigger because margins - ctrs = [(min(pts[:,i]) + max(pts[:,i]))/2 for i in range(3)] - lims = [[ctrs[i] - ptp, ctrs[i] + ptp] for i in range(3)] - axes.auto_scale_xyz(*lims) - except Exception as e: - log.error("Couldn't rescale axes, skipping") - log.error(repr(e)) + + fig = plotlyFigure(stlmesh, color) if ph is not None: - axes.axis('off') - pyplot.savefig(ph, format='png', dpi=300, transparent=True, bbox_inches='tight', pad_inches=0) + fig.write_image(ph) if show: - # Show the plot to the screen - axes.axis('on') - pyplot.show() - -#def displayTkinter(dwg, height = 500, width = 700, showFlats = True): + fig.update_layout(scene_xaxis_visible=True, + scene_yaxis_visible=True, + scene_zaxis_visible=True) + fig.show() class DisplayApp: def __init__(self, dwg, height = 500, width = 700, showFlats = True):