Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,9 @@ dmypy.json

# Pyre type checker
.pyre/

# STL files
*.stl

# MacOS file viewer metadata
**/.DS_Store
151 changes: 151 additions & 0 deletions docs/lab_stl_output.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"# lab stl output\n",
"\n",
"the FullControl lab exists for things that aren't suitable for the main FullControl package yet, potentially due to complexity in terms of their concept, code, hardware requirements, computational requirements, etc.\n",
" \n",
"FullControl features/functions/classes in the lab may be more experimental in nature and should be used with caution, with an understanding that they may change in future updates\n",
"\n",
"at present, both the lab and the regular FullControl packages are under active development and the code and package structures may change considerably. some aspects currently in FullControl may move to lab and vice versa\n",
"\n",
"lab currently has three main aspects:\n",
"- geometry functions that supplement existing geometry functions in FullControl\n",
"- multi-axis demos\n",
"- stl output of the designed geometry with extudate heights and widths based on the designed `ExtrusionGeometry` \n",
"\n",
"this notebook briefly demonstrates stl-output functionality"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"#### FullControl lab import"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import fullcontrol as fc\n",
"import lab.fullcontrol as fclab"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### create a ***design***"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"EW, EH = 0.8, 0.3 # extrusion width and height\n",
"radius, layers = 10, 5\n",
"design_name = 'test_design'\n",
"steps = fc.helixZ(fc.Point(x=0, y=0, z=EH), radius, radius, 0, layers, EH, layers*32)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### transform the design to a 'plot' ***result*** to preview it"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fc.transform(steps, 'plot', fc.PlotControls(style='tube', zoom=0.7,\n",
" initialization_data={'extrusion_width': EW, 'extrusion_height': EH}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### ModelControls adjust how a ***design*** is transformed into a '3d_model' ***result***\n",
"\n",
"***designs*** are transformed into a 'plot' according to some default settings which can be overwritten with a PlotControls object with the following attributes (all demonstrated in this notebook):\n",
"\n",
"- `stl_filename` - string for filename (do not include '.stl')\n",
"- `include_date` - options: True/False (include dates/time-stamp in the stl filename)\n",
"- `tube_shape` - options: 'rectangle' / 'diamond' / 'hexagon' / 'octagon' - adjusts cross sectional shape of extrudates in the stl file\n",
" - note this is different format for controlling the design as opposed to `tube-sides` in a `PlotControls` object\n",
"- `tube_type` - options: 'flow'/'cylinders' - adjust how the plot transitions from line to line\n",
" - see the `PlotControls` tutorial for more info about this parameter\n",
"- `stl_type` - options: 'ascii'/'binary' - stl file format\n",
"- `stls_combined` - options: True/False - state whether designs containing multiple bodies are saved with all bodies in a single stl file\n",
"- `initialization_data` - define initial width/height of 3D lines with dictionary: {'extrusion_width': value, 'extrusion_height': value}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"fclab.transform(steps, '3d_model', fclab.ModelControls(\n",
" stl_filename=design_name, \n",
" include_date=False, \n",
" tube_shape='rectangle',\n",
" tube_type= 'flow', \n",
" stl_type = 'ascii', \n",
" stls_combined = True, \n",
" initialization_data={'extrusion_width': EW, 'extrusion_height': EH}))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### colab\n",
"\n",
"if using google colab, the stl file can be downloaded from the file browser on the left-hand side or with:\n",
"\n",
"```\n",
"from google.colab import files\n",
"files.download(f'{design_name}.stl')\n",
"```\n",
"(assuming `include_date` is False)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "fc",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.3"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
4 changes: 2 additions & 2 deletions fullcontrol/visualize/controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class PlotControls(BaseModel):
color_type: Optional[str] = 'z_gradient'
line_width: Optional[float] = 2
style: Optional[str] = None # 'tube'/'line'
tube_type: Optional[str] = None # 'flow'/'cylinders'
tube_sides: Optional[int] = None
tube_type: Optional[str] = 'flow' # 'flow'/'cylinders'
tube_sides: Optional[int] = 4
zoom: Optional[float] = 1
hide_annotations: Optional[bool] = False
hide_travel: Optional[bool] = False
Expand Down
84 changes: 43 additions & 41 deletions fullcontrol/visualize/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,38 @@
import plotly.graph_objects as go
from fullcontrol.visualize.plot_data import PlotData
from fullcontrol.visualize.controls import PlotControls
from fullcontrol.visualize.tube_mesh import CylindersMesh, FlowTubeMesh
from fullcontrol.visualize.tube_mesh import CylindersMesh, FlowTubeMesh, MeshExporter


def generate_mesh(path, linewidth_now: float, Mesh: FlowTubeMesh, sides, rounding_strength, flat_sides, colors_now: list = None):
global local_max # allow external tracking for nice plot boundaries
path_points = np.array([path.xvals, path.yvals, path.zvals]).T
good_points = np.ones(len(path_points), dtype=bool)
dups = np.all(np.diff(path_points, axis=0)==0, axis=1)
if np.any(dups):
# remove successive duplicate points so TubeMesh can be generated
good_points[1:] = ~dups
# modify colors list so the new ones are accessible outside the function
colors_now[:] = np.array(colors_now, dtype=object)[good_points]
path_points = path_points[good_points]
capped = False
widths = path.widths
if not widths: # TODO: check whether it's ever reasonable for a user to not define the widths for their extrusion path
local_max = widths = linewidth_now/10
else:
widths = np.array(widths)[good_points]
if Mesh == CylindersMesh:
widths = widths[1:]
local_max = max(widths)
heights = path.heights or None
if heights:
heights = np.array(heights)[good_points]
if Mesh == CylindersMesh:
heights = heights[1:]
return Mesh(path_points, widths=widths, heights=heights, sides=sides, capped=capped, inplace_path=True,
rounding_strength=rounding_strength, flat_sides=flat_sides)


def plot(data: PlotData, controls: PlotControls):
'plot data for x y z lines with RGB colors and annotations. style of plot governed by controls'
fig = go.Figure()
Expand All @@ -17,48 +46,21 @@ def plot(data: PlotData, controls: PlotControls):

if controls.tube_type is not None:
Mesh = {'flow': FlowTubeMesh, 'cylinders': CylindersMesh}[controls.tube_type]
else:
else: # Fall back to FlowTubeMesh if no tube_type is explicitly specified
Mesh = FlowTubeMesh

# generate line plots
any_mesh_plots = False
max_width = 0
for path in data.paths:
colors_now = [f'rgb({color[0]*255:.2f}, {color[1]*255:.2f}, {color[2]*255:.2f})' for color in path.colors]
linewidth_now = controls.line_width * \
2 if path.extruder.on == True else controls.line_width*0.5
if path.extruder.on and controls.style == 'tube':
path_points = np.array([path.xvals, path.yvals, path.zvals]).T
good_points = np.ones(len(path_points), dtype=bool)
dups = np.all(np.diff(path_points, axis=0)==0, axis=1)
if np.any(dups):
# remove successive duplicate points so TubeMesh can be generated
good_points[1:] = ~dups
colors_now = np.array(colors_now, dtype=object)[good_points]
path_points = path_points[good_points]
num_path_points = len(path_points)
neat = bool(controls.neat_for_publishing)
# if automatic, dynamically reduce the number of sides when plotting large numbers of path points
sides = controls.tube_sides or (6 if num_path_points < 10 else 4 if num_path_points < 1_000_000 else 2)
capped = neat or num_path_points < 100
widths = path.widths
if not widths:
local_max = widths = linewidth_now/10
else:
widths = np.array(widths)[good_points]
if Mesh == CylindersMesh:
widths = widths[1:]
local_max = max(widths)
heights = path.heights or None
if heights:
heights = np.array(heights)[good_points]
if Mesh == CylindersMesh:
heights = heights[1:]
fig.add_trace(Mesh(path_points, widths=widths, heights=heights, sides=sides, capped=capped, inplace_path=True)
.to_Mesh3d(colors=colors_now))
any_mesh_plots = True
sides, rounding_strength, flat_sides = controls.tube_sides, 0.4, False
mesh = generate_mesh(path, linewidth_now, Mesh, sides, rounding_strength, flat_sides, colors_now)
fig.add_trace(mesh.to_Mesh3d(colors=colors_now))
max_width = max(max_width, local_max)
elif not controls.hide_travel or path.extruder.on:
elif not controls.hide_travel or path.extruder.on: # plot travel lines for tube and line
fig.add_trace(go.Scatter3d(mode='lines', x=path.xvals, y=path.yvals, z=path.zvals,
showlegend=False, line=dict(width=linewidth_now, color=colors_now)))

Expand Down Expand Up @@ -104,14 +106,14 @@ def plot(data: PlotData, controls: PlotControls):

camera = dict(eye=dict(x=-0.5/controls.zoom, y=-1/controls.zoom, z=-0.5+0.5/controls.zoom),
center=dict(x=0, y=0, z=-0.5))
fig.update_layout(template='plotly_dark', paper_bgcolor="black", scene_aspectmode='cube', scene=dict(annotations=annotations,
xaxis=dict(backgroundcolor="black", nticks=10, range=[
data.bounding_box.midx-bounding_box_size/2, data.bounding_box.midx+bounding_box_size/2],),
yaxis=dict(backgroundcolor="black", nticks=10, range=[
data.bounding_box.midy-bounding_box_size/2, data.bounding_box.midy+bounding_box_size/2],),
zaxis=dict(backgroundcolor="black", nticks=10, range=[min(0, data.bounding_box.minz), bounding_box_size],),),
scene_camera=camera,
width=800, height=500, margin=dict(l=10, r=10, b=10, t=10, pad=4))
fig.update_layout(template='plotly_dark', paper_bgcolor="black", scene_aspectmode='cube',
scene=dict(annotations=annotations,
xaxis=dict(backgroundcolor="black", nticks=10,
range=[data.bounding_box.midx-bounding_box_size/2, data.bounding_box.midx+bounding_box_size/2],),
yaxis=dict(backgroundcolor="black", nticks=10,
range=[data.bounding_box.midy-bounding_box_size/2, data.bounding_box.midy+bounding_box_size/2],),
zaxis=dict(backgroundcolor="black", nticks=10, range=[min(0, data.bounding_box.minz), bounding_box_size],),
), scene_camera=camera, width=800, height=500, margin=dict(l=10, r=10, b=10, t=10, pad=4))
if controls.hide_axes or controls.neat_for_publishing:
for axis in ['xaxis', 'yaxis', 'zaxis']:
fig.update_layout(
Expand Down
Loading