├── .gitignore
├── LICENSE
├── README.md
├── Step 1 Picture to DXF.py
├── Step 2 DXF to STL.scad
├── assets
└── images
│ ├── Image to DXF.png
│ ├── dxf generated example.png
│ ├── lightbox example.png
│ ├── openscad example.png
│ └── orcastudio example.png
├── default_settings.txt
├── examples
├── Dymo label maker.jpg
├── Klein Catapult Stripper.jpg
├── Random Wrenches.jpg
├── Rubber Mallet.jpg
├── Seekone Heat Gun Attachments.jpg
├── Seekone Heat Gun.jpg
├── Step 3 3D print File.3mf
└── example.dxf
├── raw photos
├── README.md
├── calibration_files
│ ├── camera_calibration.py
│ └── pattern.png
└── undistort_image.py
├── src
├── capture_image.py
├── capture_image.ui
├── capture_image_ui.py
├── default_camera.txt
├── fix_ui.py
├── modules
│ ├── functions_environment.scad
│ ├── functions_general.scad
│ ├── functions_gridfinity.scad
│ ├── functions_string.scad
│ ├── gridfinity_constants.scad
│ ├── module_attachment_clip.scad
│ ├── module_calipers.scad
│ ├── module_divider_walls.scad
│ ├── module_gridfinity.scad
│ ├── module_gridfinity_Extendable.scad
│ ├── module_gridfinity_baseplate.scad
│ ├── module_gridfinity_baseplate_cnclaser.scad
│ ├── module_gridfinity_baseplate_cncmagnet.scad
│ ├── module_gridfinity_baseplate_common.scad
│ ├── module_gridfinity_baseplate_common_post.scad
│ ├── module_gridfinity_baseplate_lid.scad
│ ├── module_gridfinity_baseplate_regular.scad
│ ├── module_gridfinity_block.scad
│ ├── module_gridfinity_cup.scad
│ ├── module_gridfinity_cup_base.scad
│ ├── module_gridfinity_cup_base_text.scad
│ ├── module_gridfinity_dividers_removable.scad
│ ├── module_gridfinity_efficient_floor.scad
│ ├── module_gridfinity_frame_connectors.scad
│ ├── module_gridfinity_label.scad
│ ├── module_gridfinity_lid.scad
│ ├── module_gridfinity_sliding_lid.scad
│ ├── module_item_holder.scad
│ ├── module_item_holder_data.scad
│ ├── module_lip.scad
│ ├── module_pattern_brick.scad
│ ├── module_pattern_voronoi.scad
│ ├── module_patterns.scad
│ ├── module_rounded_negative_champher.scad
│ ├── module_utility.scad
│ ├── module_utility_wallcutout.scad
│ ├── module_voronoi.scad
│ ├── polyround.scad
│ └── thridparty
│ │ ├── dotSCAD
│ │ ├── __comm__
│ │ │ ├── __frags.scad
│ │ │ ├── __ra_to_xy.scad
│ │ │ └── __to_ang_vect.scad
│ │ ├── cross_sections.scad
│ │ ├── matrix
│ │ │ ├── _impl
│ │ │ │ ├── _m_rotation_impl.scad
│ │ │ │ ├── _m_scaling_impl.scad
│ │ │ │ └── _m_translation_impl.scad
│ │ │ ├── m_rotation.scad
│ │ │ ├── m_scaling.scad
│ │ │ └── m_translation.scad
│ │ ├── ring_extrude.scad
│ │ ├── sweep.scad
│ │ └── util
│ │ │ └── reverse.scad
│ │ ├── ub_caliper.scad
│ │ ├── ub_common.scad
│ │ ├── ub_helptxt.scad
│ │ ├── ub_hexgrid.scad
│ │ └── ub_sbogen.scad
├── processing.py
├── ui.py
└── ui.ui
├── token 2.0 v4.f3d
├── token 2.0 v4.step
└── wiki
├── Light Panel Setup 2.JPEG
├── Light Panel Setup.JPEG
├── Light Panel.JPEG
├── Ratchet 2.JPEG
├── Ratchet.JPEG
├── bad photo 2.png
├── bad photo 3.png
├── bad photo.JPEG
├── example adjustable wrench 1.JPEG
├── example adjustable wrench 2.JPEG
└── good photo wrenches.JPEG
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.pyc
3 | src/__pycache__/ui.cpython-313.pyc
4 | *.pyc
5 | src/__pycache__/processing.cpython-313.pyc
6 | *.pyc
7 | raw photos/calibration_files/undistorted_WIN_20250524_12_29_52_Pro.jpg
8 | raw photos/calibration_files/undistorted_WIN_20250524_12_29_58_Pro.jpg
9 | raw photos/calibration_files/undistorted_WIN_20250524_12_30_07_Pro.jpg
10 | raw photos/calibration_files/undistorted_WIN_20250524_12_30_14_Pro.jpg
11 | raw photos/calibration_files/undistorted_WIN_20250524_12_30_19_Pro.jpg
12 | raw photos/calibration_files/undistorted_WIN_20250524_12_30_50_Pro.jpg
13 | raw photos/calibration_files/undistorted_WIN_20250524_12_30_57_Pro.jpg
14 | raw photos/calibration_files/undistorted_WIN_20250524_12_31_05_Pro.jpg
15 | raw photos/calibration_files/undistorted_WIN_20250524_12_31_10_Pro.jpg
16 | raw photos/calibration_files/undistorted_WIN_20250524_12_31_20_Pro.jpg
17 | raw photos/calibration_files/WIN_20250524_12_29_52_Pro.jpg
18 | raw photos/calibration_files/WIN_20250524_12_29_58_Pro.jpg
19 | raw photos/calibration_files/WIN_20250524_12_30_07_Pro.jpg
20 | raw photos/calibration_files/WIN_20250524_12_30_14_Pro.jpg
21 | raw photos/calibration_files/WIN_20250524_12_30_19_Pro.jpg
22 | raw photos/calibration_files/WIN_20250524_12_30_50_Pro.jpg
23 | raw photos/calibration_files/WIN_20250524_12_30_57_Pro.jpg
24 | raw photos/calibration_files/WIN_20250524_12_31_05_Pro.jpg
25 | raw photos/calibration_files/WIN_20250524_12_31_10_Pro.jpg
26 | raw photos/calibration_files/WIN_20250524_12_31_20_Pro.jpg
27 | raw photos/calibration_files/calibration_data.pkl
28 | raw photos/calibration_files/camera_matrix.txt
29 | raw photos/calibration_files/distortion_coefficients.txt
30 | camera_settings_cache.pkl
31 | Step 2 DXF to STL.json
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 tkubic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gridfinity Shadow Maker
2 |
3 | This template is used to create Gridfinity shadow boards using Python scripts and OpenSCAD.
4 |
5 | ## Highlights
6 |
7 |
8 |
9 |  |
10 |
11 |
12 |  |
13 |  |
14 |
15 |
16 |
17 |
18 |
19 | ## Step-by-Step Guide
20 |
21 | ### Step 1: Take and Edit Pictures
22 | 1. **Take Photos**: Use a lightboard to take photos of your tools or components. Best images can be taken in an enclosure or dark room. Ensure to include a 3" token in the photo for scale reference (you can 3d print the token found in the main folder folder). Based on your ambient lighting conditions, you will need to fine-tune your Threshold Input value.
23 | 2. **Example Images**: An example image taken on a lightbox is located in the `examples` folder. You can use these to learn the workflow or debug problems.
24 | 3. **Crop Photos**: Ensure the borders of the photos are all white.
25 | 4. **Touch-Up Photos**: Edit the photos as needed to create the shape you want to outline. The basic Paint application is most popular. Black filled shapes do well to ensure crisp, high contrasting edges are found
26 |
27 | ### Step 2: Trace the Objects
28 | 1. Run the provided Python script to create your OpenSCAD files.
29 | 2. Enter a project name. This will save all design files to a folder of that name to aide in documenting your work.
30 | 3. General description of settings:
31 | - **Threshold Input**: This helps with edge detection. Images should have high contrast of edges to background
32 | - **Offset**: offset in inches from traced image
33 | - **Token Size**: used for a scale reference
34 |
35 | ### Step 3: Create the 3D Model
36 | 1. The OpenSCAD file can be opened directly from the Step 1 Python user interface.
37 | 2. In the customizer, you can modify various settings:
38 | - **General Settings**: Adjust `gridx`, `gridy`, and `gridz` to match the size of your desired shadow board.
39 | - **Finger Slot Options**: Customize the size, angle, and position of the finger slots.
40 | - **Cut Depth**: Set the depth for the cuts.
41 | 3. **Render and Export**:
42 | - Click the "Render" button (F6) to render the model.
43 | - Once rendered, click "Export" to save the STL file.
44 |
45 | ### Step 4: Color, add Text, and Print
46 | 1. Open **OrcaSlicer** or **Bambu Studio** to generate the gcode for the printer. A .3mf template is available in the repository with preferred printer settings.
47 | 2. Add text (hotkey "t"). Preferred Font = "Arial Rounded MT Bold", Height = 12mm. Smaller text height must use smaller nozzles on the printer.
48 | 3. Use the color painting feature (hotkey "n") to fill the outline with a color that pops, like red. Standard colors used are:
49 | - **Black** for the main print
50 | - **Red** for the tool outline
51 | - **White** for the text/trim
52 | 4. If large shadow boards are being created you may need to cut (hotkey "c") the board into smaller pieces. Preferred method is to use dovetails
53 |
54 | ## Video Tutorial 🎥
55 | Want a walkthrough of the process? Check out the YouTube tutorial for a step-by-step guide!
56 | [](https://www.youtube.com/watch?v=K45Y8rKlYDY)
57 |
58 | ## Setting Up for the First Time
59 |
60 | ### Prerequisites
61 | #### **This repository**:
62 | Clone or download a copy of this repository. You can click the green "Code" button above and select "Download ZIP"
63 | #### **OpenSCAD**:
64 | Download and install the latest nightly version of OpenSCAD from [OpenSCAD Nightly Builds](https://openscad.org/downloads.html#snapshots).
65 | #### **OrcaSlicer or Bambu Studio**:
66 | Download and install either OrcaSlicer from [OrcaSlicer releases](https://github.com/SoftFever/OrcaSlicer/releases) or Bambu Studio from [Bambu Studio](https://bambulab.com/en/download/studio).
67 |
68 | #### **Python**:
69 | 1. **Setup Python**:
70 | - Download and install the latest version of Python from [python.org](https://www.python.org/). Ensure to add Python to your system PATH.
71 | 2. **Install Dependencies**:
72 | - Use the following command from a terminal or command prompt to install the required dependencies:
73 | ```sh
74 | pip install PyQt5 opencv_python pillow colorama ezdxf fonttools iniconfig numpy opencv-python packaging pillow pip pluggy pyparsing pyperclip pytest typing_extensions
75 | ```
76 |
77 |
78 | ## Credits
79 | This project uses code from [ostat's gridfinity-extended-openscad](https://github.com/ostat/gridfinity_extended_openscad) project for creating the Gridfinity base bins.
80 |
--------------------------------------------------------------------------------
/Step 1 Picture to DXF.py:
--------------------------------------------------------------------------------
1 | from PyQt5 import QtWidgets, QtGui
2 | from src.ui import Ui_MainWindow # type: ignore
3 | from src.processing import find_diameter, find_contours, save_contours_as_dxf, select_image, import_to_openscad, exit_application, clear_canvas, create_main_window, display_image_on_canvas # type: ignore
4 | import cv2
5 | import traceback
6 | from PIL import Image
7 | import threading
8 | import os
9 | from datetime import datetime
10 | import sys
11 | import shutil
12 |
13 | def create_main_window():
14 | MainWindow = QtWidgets.QMainWindow()
15 | ui = Ui_MainWindow()
16 | ui.setupUi(MainWindow)
17 |
18 | canvas = ui.canvas
19 | canvas.setScene(QtWidgets.QGraphicsScene())
20 |
21 | ui.console_text.setText("Input Project Name then Load Image") # Set default console text
22 |
23 | # Load defaults if available
24 | defaults_path = os.path.join(os.path.dirname(__file__), "default_settings.txt")
25 | if os.path.exists(defaults_path):
26 | try:
27 | with open(defaults_path, "r") as f:
28 | lines = f.read().splitlines()
29 | defaults = {}
30 | for line in lines:
31 | if '=' in line:
32 | k, v = line.split('=', 1)
33 | defaults[k.strip()] = v.strip()
34 | if 'threshold' in defaults:
35 | ui.threshold_entry.setText(defaults['threshold'])
36 | if 'offset' in defaults:
37 | ui.offset_entry.setText(defaults['offset'])
38 | if 'token' in defaults:
39 | ui.token_entry.setText(defaults['token'])
40 | if 'resolution' in defaults:
41 | ui.resolution_entry.setText(defaults['resolution'])
42 | except Exception:
43 | pass
44 |
45 | return (MainWindow, ui, canvas, ui.load_button, ui.process_button, ui.import_button,
46 | ui.exit_button, ui.threshold_entry, ui.offset_entry, ui.token_entry,
47 | ui.resolution_entry, ui.console_text)
48 |
49 | def main():
50 | CALIBRATION_FILE = '/src/calibration_data.pkl'
51 | global threshold_entry, offset_entry, token_entry, resolution_entry, input_image_path, file_name, console_text, image
52 | # Enable high DPI scaling for better text/UI scaling on Windows
53 | from PyQt5 import QtCore
54 | if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
55 | QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
56 | if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
57 | QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
58 | app = QtWidgets.QApplication([])
59 | window, ui, canvas, load_button, process_button, import_button, exit_button, threshold_entry, offset_entry, token_entry, resolution_entry, console_text = create_main_window()
60 |
61 | def toggle_load_button():
62 | load_button.setEnabled(bool(ui.lineEdit.text())) # Enable if lineEdit has text
63 | ui.captureImage.setEnabled(bool(ui.lineEdit.text())) # Enable Capture Image button as well
64 |
65 | ui.lineEdit.textChanged.connect(toggle_load_button) # Connect textChanged signal
66 | toggle_load_button() # Initial check to set the correct state of load_button
67 |
68 | def load_image():
69 | global input_image_path, file_name, image
70 | try:
71 | clear_canvas(canvas)
72 | folder_name = ui.lineEdit.text().strip() # Get folder name from lineEdit
73 | if not folder_name:
74 | console_text.setText("Project name is empty. Please enter a valid name.")
75 | return
76 | design_files_folder = os.path.join(os.path.dirname(__file__), folder_name)
77 | os.makedirs(design_files_folder, exist_ok=True)
78 | # Copy src folder into the project folder if not already present
79 | src_src = os.path.join(os.path.dirname(__file__), "src")
80 | dst_src = os.path.join(design_files_folder, "src")
81 | if not os.path.exists(dst_src):
82 | shutil.copytree(src_src, dst_src)
83 | # Pass default directory to select_image
84 | input_image_path, file_name = select_image(console_text, default_dir=design_files_folder)
85 | if not input_image_path:
86 | print("No image selected. Exiting.")
87 | return
88 | print(f"Loaded image: {input_image_path}")
89 | image = cv2.imread(input_image_path)
90 | if image is None:
91 | print("Failed to load image.")
92 | return
93 |
94 | display_image_on_canvas(image, canvas, 1, "Original")
95 |
96 | design_file_path = os.path.join(design_files_folder, os.path.basename(input_image_path))
97 | if not os.path.exists(design_file_path):
98 | cv2.imwrite(design_file_path, image)
99 | console_text.setText(f"Copied image to: {design_file_path}")
100 |
101 | process_image() # Automatically run process_image after loading the image
102 | process_button.setEnabled(True)
103 | except Exception as e:
104 | console_text.setText(f"Error loading image: {str(e)}")
105 | print(traceback.format_exc())
106 |
107 | def process_image():
108 | global image
109 | if image is None:
110 | console_text.setText("No image loaded. Please load or capture an image first.")
111 | return
112 | try:
113 | clear_canvas(canvas, keep_original=True)
114 | console_text.setText(f"Processing image.")
115 | folder_name = ui.lineEdit.text().strip() # Get folder name from lineEdit
116 | if not folder_name:
117 | console_text.setText("Project name is empty. Please enter a valid name.")
118 | return
119 | diameter, threshold_input = find_diameter(image, canvas, threshold_entry, offset_entry, token_entry, resolution_entry, console_text)
120 | if diameter is None or threshold_input is None:
121 | return # Return to main loop if the user selects "no"
122 | contours, offset_image = find_contours(image, diameter, threshold_input, canvas, console_text)
123 | dxf_path, gridx_size, gridy_size = save_contours_as_dxf(contours, file_name, float(token_entry.text()) / diameter, console_text, folder_name)
124 | console_text.setText(f"Processing image\nGrid X Size: {gridx_size}, Grid Y Size: {gridy_size}")
125 | import_button.setEnabled(True)
126 | import_button.dxf_path = dxf_path
127 | import_button.gridx_size = gridx_size
128 | import_button.gridy_size = gridy_size
129 | import_button.folder_name = folder_name # Store folder name for import_to_openscad
130 | except Exception as e:
131 | console_text.setText(f"Error processing image: {str(e)}")
132 | print(traceback.format_exc())
133 |
134 | def save_defaults():
135 | try:
136 | defaults_path = os.path.join(os.path.dirname(__file__), "default_settings.txt")
137 | with open(defaults_path, "w") as f:
138 | f.write(f"threshold={threshold_entry.text()}\n")
139 | f.write(f"offset={offset_entry.text()}\n")
140 | f.write(f"token={token_entry.text()}\n")
141 | f.write(f"resolution={resolution_entry.text()}\n")
142 | console_text.setText("Defaults saved.")
143 | except Exception as e:
144 | console_text.setText(f"Error saving defaults: {str(e)}")
145 |
146 | def launch_capture_image():
147 | folder_name = ui.lineEdit.text().strip()
148 | if not folder_name:
149 | console_text.setText("Project name is empty. Please enter a valid name.")
150 | return
151 | project_folder = os.path.join(os.path.dirname(__file__), folder_name)
152 | # Launch capture_image.py with project_folder as argument
153 | import subprocess
154 | subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), 'src', 'capture_image.py'), project_folder])
155 |
156 | load_button.clicked.connect(load_image)
157 | process_button.clicked.connect(process_image)
158 | import_button.clicked.connect(lambda: import_to_openscad(import_button.dxf_path, import_button.gridx_size, import_button.gridy_size, console_text, file_name, import_button.folder_name))
159 | exit_button.clicked.connect(lambda: exit_application(console_text))
160 | ui.SaveDefault.clicked.connect(save_defaults)
161 | ui.captureImage.clicked.connect(launch_capture_image)
162 |
163 | window.showMaximized() # Show the main window in maximized view
164 | app.exec_()
165 |
166 | if __name__ == "__main__":
167 | main()
168 |
--------------------------------------------------------------------------------
/assets/images/Image to DXF.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/assets/images/Image to DXF.png
--------------------------------------------------------------------------------
/assets/images/dxf generated example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/assets/images/dxf generated example.png
--------------------------------------------------------------------------------
/assets/images/lightbox example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/assets/images/lightbox example.png
--------------------------------------------------------------------------------
/assets/images/openscad example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/assets/images/openscad example.png
--------------------------------------------------------------------------------
/assets/images/orcastudio example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/assets/images/orcastudio example.png
--------------------------------------------------------------------------------
/default_settings.txt:
--------------------------------------------------------------------------------
1 | threshold=110
2 | offset=0.1
3 | token=3.000
4 | resolution=20
5 |
--------------------------------------------------------------------------------
/examples/Dymo label maker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Dymo label maker.jpg
--------------------------------------------------------------------------------
/examples/Klein Catapult Stripper.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Klein Catapult Stripper.jpg
--------------------------------------------------------------------------------
/examples/Random Wrenches.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Random Wrenches.jpg
--------------------------------------------------------------------------------
/examples/Rubber Mallet.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Rubber Mallet.jpg
--------------------------------------------------------------------------------
/examples/Seekone Heat Gun Attachments.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Seekone Heat Gun Attachments.jpg
--------------------------------------------------------------------------------
/examples/Seekone Heat Gun.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Seekone Heat Gun.jpg
--------------------------------------------------------------------------------
/examples/Step 3 3D print File.3mf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/examples/Step 3 3D print File.3mf
--------------------------------------------------------------------------------
/raw photos/README.md:
--------------------------------------------------------------------------------
1 | # Camera Calibration and Image Undistortion Guide
2 |
3 | This guide will help you calibrate your camera and undistort your images for accurate results.
4 |
5 | ## Camera Calibration
6 |
7 | 1. **Print the Calibration Pattern**
8 | - Locate the `pattern.png` file in the `calibration_files` directory.
9 | - Print the pattern on a standard sheet of paper.
10 |
11 | 2. **Capture Calibration Photos**
12 | - Take at least 8 photos of the printed checkerboard pattern in various orientations.
13 | - Ensure 2-3 of these photos are taken with the checkerboard propped up at an angle.
14 | - You can use a clipboard and a 2-4 inch block to prop up different ends of the checkerboard.
15 |
16 | 3. **Prepare Calibration Images**
17 | - Save all captured images as `.jpg` files.
18 | - Place these images in the `calibration_files` directory.
19 |
20 | 4. **Run the Calibration Script**
21 | - Execute the `camera_calibration.py` script.
22 | - Compare the undistorted images with the raw versions to ensure successful calibration.
23 |
24 | ## Image Undistortion
25 |
26 | 1. **Prepare Raw Images**
27 | - Place all raw images to be undistorted in the `raw photos` folder as `.jpg` files.
28 | - **Important:** Do not crop or alter the raw images in any way. The calibration only works on unaltered images directly from the camera.
29 |
30 | 2. **Run the Undistortion Script**
31 | - Execute the `undistort_image.py` script.
32 | - The undistorted images will be saved in the `Design Files` folder.
33 |
34 | Follow these steps to ensure accurate calibration and undistortion of your images.
35 |
--------------------------------------------------------------------------------
/raw photos/calibration_files/camera_calibration.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import cv2
3 | import glob
4 | import os
5 | import pickle
6 |
7 | # Camera calibration parameters
8 | # You can modify these variables as needed
9 | CHESSBOARD_SIZE = (9, 6) # Number of inner corners per chessboard row and column
10 | SQUARE_SIZE = 2.5 # Size of a square in centimeters
11 | SAVE_UNDISTORTED = True # Whether to save undistorted images
12 |
13 | def calibrate_camera():
14 | """
15 | Calibrate the camera using chessboard images.
16 |
17 | Returns:
18 | ret: The RMS re-projection error
19 | mtx: Camera matrix
20 | dist: Distortion coefficients
21 | rvecs: Rotation vectors
22 | tvecs: Translation vectors
23 | """
24 | # Prepare object points (0,0,0), (1,0,0), (2,0,0) ... (8,5,0)
25 | objp = np.zeros((CHESSBOARD_SIZE[0] * CHESSBOARD_SIZE[1], 3), np.float32)
26 | objp[:, :2] = np.mgrid[0:CHESSBOARD_SIZE[0], 0:CHESSBOARD_SIZE[1]].T.reshape(-1, 2)
27 |
28 | # Scale object points by square size (for real-world measurements)
29 | objp = objp * SQUARE_SIZE
30 |
31 | # Arrays to store object points and image points from all images
32 | objpoints = [] # 3D points in real world space
33 | imgpoints = [] # 2D points in image plane
34 |
35 | # Get list of calibration images
36 | images = [img for img in glob.glob('*.jpg') if not os.path.basename(img).startswith('undistorted')]
37 |
38 | if not images:
39 | print("No calibration images found in the current directory")
40 | return None, None, None, None, None
41 |
42 | print(f"Found {len(images)} calibration images")
43 |
44 | # Process each calibration image
45 | for idx, fname in enumerate(images):
46 | img = cv2.imread(fname)
47 | gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
48 |
49 | # Find the chessboard corners
50 | ret, corners = cv2.findChessboardCorners(gray, CHESSBOARD_SIZE, None)
51 |
52 | # If found, add object points and image points
53 | if ret:
54 | objpoints.append(objp)
55 |
56 | # Refine corner positions
57 | criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
58 | corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
59 | imgpoints.append(corners2)
60 |
61 | print(f"Processed image {idx+1}/{len(images)}: {fname} - Chessboard found")
62 | else:
63 | print(f"Processed image {idx+1}/{len(images)}: {fname} - Chessboard NOT found")
64 |
65 | if not objpoints:
66 | print("No chessboard patterns were detected in any images.")
67 | return None, None, None, None, None
68 |
69 | print("Calibrating camera...")
70 |
71 | # Calibrate camera
72 | ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
73 | objpoints, imgpoints, gray.shape[::-1], None, None
74 | )
75 |
76 | # Save calibration results
77 | calibration_data = {
78 | 'camera_matrix': mtx,
79 | 'distortion_coefficients': dist,
80 | 'rotation_vectors': rvecs,
81 | 'translation_vectors': tvecs,
82 | 'reprojection_error': ret
83 | }
84 |
85 | with open('calibration_data.pkl', 'wb') as f:
86 | pickle.dump(calibration_data, f)
87 |
88 | # Save camera matrix and distortion coefficients as text files
89 | np.savetxt('camera_matrix.txt', mtx)
90 | np.savetxt('distortion_coefficients.txt', dist)
91 |
92 | print(f"Calibration complete! RMS re-projection error: {ret}")
93 | print("Results saved to the current directory")
94 |
95 | return ret, mtx, dist, rvecs, tvecs
96 |
97 | def undistort_images(mtx, dist):
98 | """
99 | Undistort all calibration images using the calibration results.
100 |
101 | Args:
102 | mtx: Camera matrix
103 | dist: Distortion coefficients
104 | """
105 | if not SAVE_UNDISTORTED:
106 | return
107 |
108 | images = [img for img in glob.glob('*.jpg') if not os.path.basename(img).startswith('undistorted')]
109 |
110 | if not images:
111 | print("No images found in the current directory")
112 | return
113 |
114 | print(f"Undistorting {len(images)} images...")
115 |
116 | for idx, fname in enumerate(images):
117 | img = cv2.imread(fname)
118 | h, w = img.shape[:2]
119 |
120 | # Refine camera matrix based on free scaling parameter
121 | newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
122 |
123 | # Undistort image
124 | dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
125 |
126 | # Crop the image (optional)
127 | x, y, w, h = roi
128 | dst = dst[y:y+h, x:x+w]
129 |
130 | # Save undistorted image directly in the current directory
131 | output_img_path = f'undistorted_{os.path.basename(fname)}'
132 | cv2.imwrite(output_img_path, dst)
133 |
134 | print(f"Undistorted image {idx+1}/{len(images)}: {fname}")
135 |
136 | print("Undistorted images saved to the current directory")
137 |
138 | def calculate_reprojection_error(objpoints, imgpoints, mtx, dist, rvecs, tvecs):
139 | """
140 | Calculate the reprojection error for each calibration image.
141 |
142 | Args:
143 | objpoints: 3D points in real world space
144 | imgpoints: 2D points in image plane
145 | mtx: Camera matrix
146 | dist: Distortion coefficients
147 | rvecs: Rotation vectors
148 | tvecs: Translation vectors
149 |
150 | Returns:
151 | mean_error: Mean reprojection error
152 | """
153 | total_error = 0
154 | for i in range(len(objpoints)):
155 | imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
156 | error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
157 | total_error += error
158 | print(f"Reprojection error for image {i+1}: {error}")
159 |
160 | mean_error = total_error / len(objpoints)
161 | print(f"Mean reprojection error: {mean_error}")
162 |
163 | return mean_error
164 |
165 | def main():
166 | """
167 | Main function to run the camera calibration process.
168 | """
169 | print("Starting camera calibration...")
170 |
171 | # Calibrate camera
172 | ret, mtx, dist, rvecs, tvecs = calibrate_camera()
173 |
174 | if mtx is None:
175 | print("Calibration failed. Exiting.")
176 | return
177 |
178 | # Undistort images
179 | undistort_images(mtx, dist)
180 |
181 | print("Camera calibration completed successfully!")
182 | print("Close this window to exit.")
183 | input()
184 |
185 | if __name__ == "__main__":
186 | main()
--------------------------------------------------------------------------------
/raw photos/calibration_files/pattern.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tkubic/GridfinityShadowMaker/38bc080f626f7796f48f841e19aa71a0eba2b7ad/raw photos/calibration_files/pattern.png
--------------------------------------------------------------------------------
/raw photos/undistort_image.py:
--------------------------------------------------------------------------------
1 | import cv2
2 | import pickle
3 | import os
4 | from tkinter import Tk
5 | from tkinter.filedialog import askopenfilename
6 |
7 | # Path to calibration data
8 | CALIBRATION_FILE = 'calibration_files/calibration_data.pkl'
9 | UNDISTORTED_IMAGES_DIR = '../Design Files'
10 |
11 | def load_calibration_data(calibration_file):
12 | """
13 | Load calibration data from a file.
14 |
15 | Args:
16 | calibration_file: Path to the calibration data file.
17 |
18 | Returns:
19 | mtx: Camera matrix
20 | dist: Distortion coefficients
21 | """
22 | if not os.path.exists(calibration_file):
23 | print(f"Calibration file not found: {calibration_file}")
24 | return None, None
25 |
26 | with open(calibration_file, 'rb') as f:
27 | data = pickle.load(f)
28 |
29 | return data['camera_matrix'], data['distortion_coefficients']
30 |
31 | def undistort_image(img, mtx, dist):
32 | """
33 | Undistort a single image using the calibration data.
34 |
35 | Args:
36 | img: Input image as a numpy array.
37 | mtx: Camera matrix.
38 | dist: Distortion coefficients.
39 |
40 | Returns:
41 | dst: Undistorted image as a numpy array.
42 | """
43 | h, w = img.shape[:2]
44 |
45 | # Refine camera matrix based on free scaling parameter
46 | newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
47 |
48 | # Undistort image
49 | dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
50 |
51 | # Crop the image (optional)
52 | x, y, w, h = roi
53 | dst = dst[y:y+h, x:x+w]
54 |
55 | return dst
56 |
57 | def save_image(dst, output_dir, image_name):
58 | """
59 | Save the undistorted image to the specified directory.
60 |
61 | Args:
62 | dst: Undistorted image as a numpy array.
63 | output_dir: Directory to save the undistorted image.
64 | image_name: Name of the input image file.
65 | """
66 | if not os.path.exists(output_dir):
67 | os.makedirs(output_dir)
68 |
69 | output_path = os.path.join(output_dir, f'undistorted_{image_name}')
70 | cv2.imwrite(output_path, dst)
71 | print(f"Undistorted image saved to: {output_path}")
72 |
73 | def main():
74 | """
75 | Main function to undistort all .jpg images in the current directory.
76 | """
77 | print("Loading calibration data...")
78 | mtx, dist = load_calibration_data(CALIBRATION_FILE)
79 |
80 | if mtx is None or dist is None:
81 | print("Failed to load calibration data. Exiting.")
82 | return
83 |
84 | # Directory to save undistorted images
85 | output_dir = UNDISTORTED_IMAGES_DIR
86 | if not os.path.exists(output_dir):
87 | os.makedirs(output_dir)
88 |
89 | # Find all .jpg files in the current directory
90 | print("Finding .jpg files in the current directory...")
91 | image_files = [f for f in os.listdir('.') if f.lower().endswith('.jpg')]
92 |
93 | if not image_files:
94 | print("No .jpg files found in the current directory. Exiting.")
95 | return
96 |
97 | for image_file in image_files:
98 | print(f"Processing {image_file}...")
99 | img = cv2.imread(image_file)
100 | if img is None:
101 | print(f"Failed to read image: {image_file}. Skipping.")
102 | continue
103 |
104 | # Undistort the image
105 | dst = undistort_image(img, mtx, dist)
106 |
107 | # Save the undistorted image
108 | save_image(dst, output_dir, image_file)
109 |
110 | print("Processing complete. Undistorted images saved to 'undistorted_images' directory.")
111 |
112 | if __name__ == "__main__":
113 | main()
114 |
--------------------------------------------------------------------------------
/src/capture_image.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 1024
10 | 768
11 |
12 |
13 |
14 | Dialog
15 |
16 |
17 | -
18 |
19 |
20 | false
21 |
22 |
23 |
24 | 12
25 |
26 |
27 |
28 | Capture Image
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
36 | 100
37 | 16777215
38 |
39 |
40 |
41 | Qt::LayoutDirection::RightToLeft
42 |
43 |
44 | Reload List
45 |
46 |
47 |
48 | -
49 |
50 |
51 | -
52 |
53 |
54 | Image Name
55 |
56 |
57 |
58 | -
59 |
60 |
61 |
62 | 200
63 | 16777215
64 |
65 |
66 |
67 |
68 | 14
69 |
70 |
71 |
72 | Quit
73 |
74 |
75 |
76 | -
77 |
78 |
79 | -
80 |
81 |
82 |
83 | 200
84 | 16777215
85 |
86 |
87 |
88 |
89 | 9
90 |
91 |
92 |
93 | Qt::LayoutDirection::RightToLeft
94 |
95 |
96 | Switch Camera
97 |
98 |
99 |
100 | -
101 |
102 |
103 |
104 | 12
105 |
106 |
107 |
108 | Edit Image
109 |
110 |
111 |
112 | -
113 |
114 |
115 |
116 | 0
117 | 75
118 |
119 |
120 |
121 | QFrame::Shape::Box
122 |
123 |
124 | QFrame::Shadow::Plain
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/capture_image_ui.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file 'capture_image.ui'
4 | #
5 | # Created by: PyQt5 UI code generator 5.15.11
6 | #
7 | # WARNING: Any manual changes made to this file will be lost when pyuic5 is
8 | # run again. Do not edit this file unless you know what you are doing.
9 |
10 |
11 | from PyQt5 import QtCore, QtGui, QtWidgets
12 |
13 |
14 | class Ui_Dialog(object):
15 | def setupUi(self, Dialog):
16 | Dialog.setObjectName("Dialog")
17 | Dialog.resize(1024, 768)
18 | self.gridLayout = QtWidgets.QGridLayout(Dialog)
19 | self.gridLayout.setObjectName("gridLayout")
20 | self.buttonCaptureImage = QtWidgets.QPushButton(Dialog)
21 | self.buttonCaptureImage.setEnabled(False)
22 | font = QtGui.QFont()
23 | font.setPointSize(12)
24 | self.buttonCaptureImage.setFont(font)
25 | self.buttonCaptureImage.setObjectName("buttonCaptureImage")
26 | self.gridLayout.addWidget(self.buttonCaptureImage, 2, 0, 1, 1)
27 | self.buttonReloadList = QtWidgets.QPushButton(Dialog)
28 | self.buttonReloadList.setMaximumSize(QtCore.QSize(100, 16777215))
29 | self.buttonReloadList.setLayoutDirection(QtCore.Qt.RightToLeft)
30 | self.buttonReloadList.setObjectName("buttonReloadList")
31 | self.gridLayout.addWidget(self.buttonReloadList, 3, 1, 1, 1)
32 | self.listImages = QtWidgets.QListView(Dialog)
33 | self.listImages.setObjectName("listImages")
34 | self.gridLayout.addWidget(self.listImages, 0, 1, 1, 1)
35 | self.lineeditImageName = QtWidgets.QLineEdit(Dialog)
36 | self.lineeditImageName.setObjectName("lineeditImageName")
37 | self.gridLayout.addWidget(self.lineeditImageName, 1, 0, 1, 1)
38 | self.buttonQuit = QtWidgets.QPushButton(Dialog)
39 | self.buttonQuit.setMaximumSize(QtCore.QSize(200, 16777215))
40 | font = QtGui.QFont()
41 | font.setPointSize(14)
42 | self.buttonQuit.setFont(font)
43 | self.buttonQuit.setObjectName("buttonQuit")
44 | self.gridLayout.addWidget(self.buttonQuit, 5, 0, 1, 1)
45 | self.canvasCamera = QtWidgets.QGraphicsView(Dialog)
46 | self.canvasCamera.setObjectName("canvasCamera")
47 | self.gridLayout.addWidget(self.canvasCamera, 0, 0, 1, 1)
48 | self.buttonSwitchCamera = QtWidgets.QPushButton(Dialog)
49 | self.buttonSwitchCamera.setMaximumSize(QtCore.QSize(200, 16777215))
50 | font = QtGui.QFont()
51 | font.setPointSize(9)
52 | self.buttonSwitchCamera.setFont(font)
53 | self.buttonSwitchCamera.setLayoutDirection(QtCore.Qt.RightToLeft)
54 | self.buttonSwitchCamera.setObjectName("buttonSwitchCamera")
55 | self.gridLayout.addWidget(self.buttonSwitchCamera, 3, 0, 1, 1)
56 | self.buttonEditImage = QtWidgets.QPushButton(Dialog)
57 | font = QtGui.QFont()
58 | font.setPointSize(12)
59 | self.buttonEditImage.setFont(font)
60 | self.buttonEditImage.setObjectName("buttonEditImage")
61 | self.gridLayout.addWidget(self.buttonEditImage, 1, 1, 1, 1)
62 | self.labelConsole = QtWidgets.QLabel(Dialog)
63 | self.labelConsole.setMinimumSize(QtCore.QSize(0, 75))
64 | self.labelConsole.setFrameShape(QtWidgets.QFrame.Box)
65 | self.labelConsole.setFrameShadow(QtWidgets.QFrame.Plain)
66 | self.labelConsole.setText("")
67 | self.labelConsole.setObjectName("labelConsole")
68 | self.gridLayout.addWidget(self.labelConsole, 4, 0, 1, 1)
69 | self.gridLayout.setColumnStretch(0, 3)
70 |
71 | self.retranslateUi(Dialog)
72 | QtCore.QMetaObject.connectSlotsByName(Dialog)
73 |
74 | def retranslateUi(self, Dialog):
75 | _translate = QtCore.QCoreApplication.translate
76 | Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
77 | self.buttonCaptureImage.setText(_translate("Dialog", "Capture Image"))
78 | self.buttonReloadList.setText(_translate("Dialog", "Reload List"))
79 | self.lineeditImageName.setPlaceholderText(_translate("Dialog", "Image Name"))
80 | self.buttonQuit.setText(_translate("Dialog", "Quit"))
81 | self.buttonSwitchCamera.setText(_translate("Dialog", "Switch Camera"))
82 | self.buttonEditImage.setText(_translate("Dialog", "Edit Image"))
83 |
--------------------------------------------------------------------------------
/src/default_camera.txt:
--------------------------------------------------------------------------------
1 | 1
--------------------------------------------------------------------------------
/src/fix_ui.py:
--------------------------------------------------------------------------------
1 | # This files purpose is to fix the ui.py file generated by pyuic5 to be compatible with PyQt6
2 | # The pyuic5 command to convert the ui file is: pyuic5 -o ui.py ui.ui
3 |
4 | import re
5 | import subprocess
6 |
7 | def generate_ui_file():
8 | subprocess.run(['pyuic5', '-o', 'ui.py', 'ui.ui'], check=True)
9 | subprocess.run(['pyuic5', '-o', 'capture_image_ui.py', 'capture_image.ui'], check=True)
10 |
11 | def fix_ui_file(filepath):
12 | with open(filepath, 'r', encoding='utf-8') as file:
13 | content = file.read()
14 |
15 | # Fix frame shape and shadow syntax
16 | content = re.sub(r'QtCore.Qt.QFrame::Shape::', 'QtWidgets.QFrame.', content)
17 | content = re.sub(r'QtCore.Qt.QFrame::Shadow::', 'QtWidgets.QFrame.', content)
18 | content = re.sub(r'QtCore.Qt.Qt::AlignmentFlag::', 'QtCore.Qt.', content)
19 | content = re.sub(r'QtCore.Qt.Qt::LayoutDirection::', 'QtCore.Qt.', content)
20 |
21 | # Fix alignment issues
22 | content = re.sub(r'QtCore.Qt.AlignRight\|QtCore.Qt.AlignTrailing\|QtCore.Qt.AlignVCenter', 'QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter', content)
23 | content = re.sub(r'QtCore.Qt.AlignRight\|QtCore.Qt.AlignTrailing\|QtCore.Qt.AlignVCenter', 'QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter', content)
24 |
25 | with open(filepath, 'w', encoding='utf-8') as file:
26 | file.write(content)
27 |
28 | if __name__ == "__main__":
29 | generate_ui_file()
30 | fix_ui_file('ui.py')
31 | fix_ui_file('capture_image_ui.py')
32 | print("ui.py and capture_image_ui.py have been generated and fixed.")
--------------------------------------------------------------------------------
/src/modules/functions_environment.scad:
--------------------------------------------------------------------------------
1 |
2 | include
3 |
4 | //Set up the Environment, if not run object should still render using defaults
5 | module set_environment(
6 | width,
7 | depth,
8 | height = 0,
9 | setColour = "preview",
10 | help = false,
11 | render_position = "center", //[default,center,zero]
12 | cut = [0,0,0],
13 | pitch = [gf_pitch, gf_pitch, gf_zpitch],
14 | randomSeed = 0,
15 | force_render = true){
16 |
17 | echo("🟩set_environment", fs=$fs, fa=$fa, fn=$fn, pitch=pitch);
18 |
19 | //Set special variables, that child modules can use
20 | $pitch = pitch;
21 |
22 | $setColour = setColour;
23 | $showHelp = help;
24 | $randomSeed = randomSeed;
25 | $forceRender = force_render;
26 |
27 | $user_width = width;
28 | $user_depth = depth;
29 | $user_height = height;
30 |
31 | num_x = calcDimensionWidth(width, true);
32 | num_y = calcDimensionDepth(depth, true);
33 | num_z = calcDimensionHeight(height, true);
34 | $num_x = num_x;
35 | $num_y = num_y;
36 | $num_z = num_z;
37 |
38 | $cutx = calcDimensionWidth(cut.x);
39 | $cuty = calcDimensionWidth(cut.y);
40 | $cutz = calcDimensionWidth(cut.z);
41 |
42 | //Position the object
43 | translate(gridfinityRenderPosition(render_position,num_x,num_y))
44 | union(){
45 | difference(){
46 | //Render the object
47 | children(0);
48 |
49 | //Render the cut, used for debugging
50 | /*
51 | if(cutx > 0 && cutz > 0 && $preview){
52 | color(color_cut)
53 | translate([-fudgeFactor,-fudgeFactor,-fudgeFactor])
54 | cube([env_pitch().x*cutx,num_y*env_pitch().y+fudgeFactor*2,(cutz+1)*env_pitch().z]);
55 | }
56 | if(cuty > 0 && cutz > 0 && $preview){
57 | color(color_cut)
58 | translate([-fudgeFactor,-fudgeFactor,-fudgeFactor])
59 | cube([num_x*env_pitch().x+fudgeFactor*2,env_pitch().y*cuty,(cutz+1)*env_pitch().z]);
60 | }*/
61 | }
62 |
63 | //children(1);
64 | }
65 | }
66 |
67 | function env_numx() = is_undef($num_x) || !is_num($num_x) ? 0 : $num_x;
68 | function env_numy() = is_undef($num_y) || !is_num($num_y) ? 0 : $num_y;
69 | function env_numz() = is_undef($num_z) || !is_num($num_z) ? 0 : $num_z;
70 |
71 | function env_pitch() = is_undef($pitch) || !is_list($pitch) ? [gf_pitch, gf_pitch, gf_zpitch] : $pitch;
72 |
73 | function env_cutx() = is_undef($cutx) || !is_num($cutx) ? 0 : $cutx;
74 | function env_cuty() = is_undef($cuty) || !is_num($cuty) ? 0 : $cuty;
75 | function env_cutz() = is_undef($cutz) || !is_num($cutz) ? 0 : $cutz;
76 | function env_random_seed() = is_undef($randomSeed) || !is_num($randomSeed) || $randomSeed == 0 ? undef : $randomSeed;
77 | function env_force_render() = is_undef($forceRender) ? true : $forceRender;
78 |
79 | //set_colour = "preview"; //[disabled, preview, lip]
80 | function env_colour(colour, isLip = false, fallBack = color_cup) =
81 | is_undef($setColour)
82 | ? $preview ? colour : fallBack
83 | : is_string($setColour)
84 | ? $setColour == "enable" ? colour
85 | : $setColour == "preview" && $preview ? colour
86 | : $setColour == "lip" && isLip ? colour
87 | : fallBack
88 | : fallBack;
89 |
90 | function env_help_enabled(level) =
91 | is_string(level) && level == "force" ? true
92 | : is_undef($showHelp) ? false
93 | : is_bool($showHelp) ? $showHelp
94 | : is_string($showHelp)
95 | ? $showHelp == "info" && level == "info" ? true
96 | : $showHelp == "debug" && (level == "info" || level == "debug") ? true
97 | : $showHelp == "trace" && (level == "info" || level == "debug" || level == "trace") ? true
98 | : false
99 | : false;
100 |
--------------------------------------------------------------------------------
/src/modules/functions_general.scad:
--------------------------------------------------------------------------------
1 | include
2 |
3 | function sum(list, c = 0, end) =
4 | let(end = is_undef(end) ? len(list) : end)
5 | c < 0 || end < 0 ? 0 :
6 | c < len(list) - 1 && c < end
7 | ? list[c] + sum(list, c + 1, end=end)
8 | : list[c];
9 |
10 | function vector_sum(v, start=0, end, itemIndex) =
11 | let(v=is_list(v)?v:[v], end = is_undef(end)?len(v)-1:min(len(v)-1,end))
12 | is_num(itemIndex)
13 | ? start= 0, "sigFigs must be a number")
20 | let(
21 | sigFigs = round(sigFigs),
22 | factor = 10^round(sigFigs))
23 | sigFigs == 0
24 | ? round(value)
25 | : round(value*factor)/factor;
26 |
27 | function DictGet(list, key, alert=false) =
28 | let(matchResults = search([key],list,1),
29 | matchIndex = is_list(matchResults) && len(matchResults)==1 && is_num(matchResults[0]) ? matchResults[0]: undef,
30 | alertMessage = str("count not find key in list key:'", key, "' matchResults:'", matchResults, "' matchIndex:'", matchIndex),
31 | matchValue = is_num(matchIndex) ? list[matchIndex] : undef,
32 | x = !alert && is_undef(matchValue) ? echo(alertMessage) : 1)
33 | assert(!alert || !is_undef(matchValue), alertMessage)
34 | matchValue[1];
35 |
36 | function DictSetRange(list, keyValueArray) = !(len(keyValueArray)>0) ? list :
37 | assert(is_list(list), str("DictSetRange(keyValueArray, arr) - arr is not a list. list:",list))
38 | assert(is_list(keyValueArray), str("DictSetRange(keyValueArray, arr) - keyValueArray is not a list. keyValueArray:", keyValueArray))
39 | let(currentKeyValue = keyValueArray[0])
40 | assert(is_list(currentKeyValue), str("DictSetRange(keyValueArray, arr) - currentKeyValue is not a list. currentKeyValue:",currentKeyValue))
41 | assert(len(currentKeyValue)==2, str("DictSetRange(keyValueArray, arr) - currentKeyValue is not length of 2. currentKeyValue:",currentKeyValue))
42 | assert(is_string(currentKeyValue[0]), str("DictSetRange(keyValueArray, arr) - currentKeyValue[0] is not a string, currentKeyValue:",currentKeyValue))
43 | let(keyValueArrayNext = remove_item(keyValueArray,0),
44 | updatedList = DictSet(list, currentKeyValue)
45 | ) concat(DictSetRange(updatedList, keyValueArrayNext));
46 |
47 | function DictSet(list, keyValue) =
48 | assert(is_list(list), str("DictSet(keyValueArray, arr) - arr is not a list list:", list))
49 | assert(is_list(keyValue), str("DictSet(keyValueArray, arr) - keyValueArray is not a list. keyValue:",keyValue))
50 | assert(len(keyValue)==2, str("DictSet(keyValueArray, arr) - keyValueArray is not a list. keyValue:",keyValue))
51 | let(matchResults = search([keyValue[0]],list,1),
52 | matchIndex = is_list(matchResults) && len(matchResults)==1 && is_num(matchResults[0]) ? matchResults[0] : undef)
53 | assert(!is_undef(matchIndex), str("count not find key in list, key:'", keyValue[0], "'", DictToString(list)))
54 | replace(list, matchIndex, keyValue);
55 |
56 | module DictDisplay(list, name = ""){
57 | echo(DictToString(list=list,name=name));
58 | }
59 | function DictToString(list, name = "") =
60 | let(infoText=[for(i=[0:len(list)-1])str(list[i][0],"=",list[i][1])])
61 | str("🟧", name, concatstringarray(infoText));
62 |
63 | function concatstringarray(in, out="",pos=0, sep="\r\n ") = pos>=len(in)?out:
64 | concatstringarray(in=in,out=str(out,sep,in[pos]),pos=pos +1);
65 |
66 | //Replace multiple values in an array
67 | function replace_Items(keyValueArray, arr) = !(len(keyValueArray)>0) ? arr :
68 | assert(is_list(arr), "replace_Items(keyValueArray, arr) - arr is not a list")
69 | assert(is_list(keyValueArray), "replace_Items(keyValueArray, arr) - keyValueArray is not a list")
70 | let(currentKeyValue = keyValueArray[0])
71 | assert(is_list(currentKeyValue), "replace_Items(keyValueArray, arr) - currentKeyValue is not a list")
72 | assert(is_num(currentKeyValue[0]), "replace_Items(keyValueArray, arr) - currentKeyValue[0] is not a number")
73 | let(keyValueArrayNext = remove_item(keyValueArray,0),
74 | updatedList = replace(arr, currentKeyValue[0],currentKeyValue[1])
75 | ) concat(replace_Items(keyValueArrayNext, updatedList));
76 |
77 | //Replace a value in an array
78 | function replace(list,position,value) =
79 | assert(is_list(list), "replace(list,position,value) - list is not a list")
80 | assert(is_num(position), "replace(list,position,value) - position is not a number")
81 | let (
82 | l1 = position > 0 ? partial(list,start=0,end=position-1) : [],
83 | l2 = position < len(list)-1 ? partial(list,start=position+1,end=len(list)-1) :[]
84 | ) concat(l1,[value],l2);
85 |
86 | // takes part of an array
87 | function partial(list,start,end) = [for (i = [start:end]) list[i]];
88 |
89 | //Removes item from an array
90 | function remove_item(list,position) = [for (i = [0:len(list)-1]) if (i != position) list[i]];
91 |
92 | //Takes a string and converts it in to an array of arrays.
93 | //I.E. "0, 0, 0.5, 3, 2, 6|0.5, 0, 0.5, 3,2, 6|1, 0, 3, 1.5|1, 1.5, 3, 1.5";
94 | //becomes [[0, 0, 0.5, 3, 2, 6], [0.5, 0, 0.5, 3, 2, 6], [1, 0, 3, 1.5], [1, 1.5, 3, 1.5]]
95 | function splitCustomConfig(customConfig) = let(
96 | compartments = split(customConfig, "|")
97 | ) [for (x =[0:1:len(compartments)-1]) csv_parse(compartments[x])];
98 |
99 | /*
100 | U+1F7E5 🟥 LARGE RED SQUARE
101 | U+1F7E6 🟦 LARGE BLUE SQUARE
102 | U+1F7E7 🟧 LARGE ORANGE SQUARE
103 | U+1F7E8 🟨 LARGE YELLOW SQUARE
104 | U+1F7E9 🟩 LARGE GREEN SQUARE
105 | U+1F7EA 🟪 LARGE PURPLE SQUARE
106 | U+1F7EB 🟫 LARGE BROWN SQUARE
107 | U+2B1B ⬛ BLACK LARGE SQUARE
108 | U+2B1C ⬜ WHITE LARGE SQUARE
109 | */
110 |
111 | function outputCustomConfig(typecode, arr) = let(
112 | config = createCustomConfig(arr),
113 | dynamicConfig = str("\"", typecode,"\"", ",", config)
114 | ) str("🟦 Generating 'tray' config, to be used in custom config.\r\nLocal Config\r\n\t", config, "\r\nDynamic Config\r\n\t", dynamicConfig,"\r\n");
115 |
116 | function createCustomConfig(arr, pos=0, sep = ",") = pos >= len(arr) ? "" :
117 | let(
118 | current = is_list(arr[pos]) ? createCustomConfig(arr[pos], sep=";")
119 | : is_string(arr[pos]) ? str("\"",arr[pos],"\"")
120 | : arr[pos],
121 | strNext = createCustomConfig(arr, pos+1, sep)
122 | ) str(current, strNext!=""?str(sep, strNext):"");
123 |
124 | module assert_openscad_version(){
125 | assert(version()[0]>2022,"Gridfinity Extended requires an OpenSCAD version greater than 2022 https://openscad.org/downloads. Use Development Snapshots if the release version is still 2021.01 https://openscad.org/downloads.html#snapshots.");
126 | }
127 |
128 | module color_conditional(enable=true, c){
129 | if(enable)
130 | color(c)
131 | children();
132 | else
133 | children();
134 | }
135 |
136 | module render_conditional(enable=true){
137 | if(enable)
138 | render()
139 | children();
140 | else
141 | union()
142 | children();
143 | }
144 |
145 | module hull_conditional(enabled = true)
146 | {
147 | if(enabled){
148 | hull(){
149 | children();
150 | }
151 | }
152 | else{
153 | union(){
154 | children();
155 | }
156 | }
157 | }
--------------------------------------------------------------------------------
/src/modules/functions_string.scad:
--------------------------------------------------------------------------------
1 | // String functions found here https://github.com/thehans/funcutils/blob/master/string.scad
2 | join = function (l,delimiter="")
3 | let(s = len(l), d = delimiter,
4 | jb = function (b,e) let(s = e-b, m = floor(b+s/2)) // join binary
5 | s > 2 ? str(jb(b,m), jb(m,e)) : s == 2 ? str(l[b],l[b+1]) : l[b],
6 | jd = function (b,e) let(s = e-b, m = floor(b+s/2)) // join delimiter
7 | s > 2 ? str(jd(b,m), d, jd(m,e)) : s == 2 ? str(l[b],d,l[b+1]) : l[b])
8 | s > 0 ? (d=="" ? jb(0,s) : jd(0,s)) : "";
9 |
10 | substr = function(s,b,e) let(e=is_undef(e) || e > len(s) ? len(s) : e) (b==e) ? "" : join([for(i=[b:1:e-1]) s[i] ]);
11 |
12 | split = function(s,separator=" ") separator=="" ? [for(i=[0:1:len(s)-1]) s[i]] :
13 | let(t=separator, e=len(s), f=len(t),
14 | _s=function(b,c,d,r) b1 && abs(x) < pow(10,l-1) ? str(sp, lz(x,l-1)) : "",
27 | tz = function (x, t) let(mult=pow(10,t-1)) t>0 && abs(floor(x*mult)-x*mult) < 1e-9 ? str(sp, tz(x,t-1)) : ""
28 | )
29 | str(x2<0?"-":" ", lz(x2,w-p-2), abs(x2), abs(floor(x)-x2)<1e-9 ? "." : "" ,tz(x2,p));
30 |
31 | float = function(s) let(
32 | _f = function(s, i, x, vM, dM, ddM, m)
33 | i >= len(s) ? round(x*dM)/dM :
34 | let(
35 | d = ord(s[i])
36 | )
37 | (d == 32 && m == 0) || (d == 43 && (m == 0 || m == 2)) ?
38 | _f(s, i+1, x, vM, dM, ddM, m) :
39 | d == 45 && (m == 0 || m == 2) ?
40 | _f(s, i+1, x, vM, -dM, ddM, floor(m/2)+1) :
41 | d >= 48 && d <= 57 ?
42 | _f(s, i+1, x*vM + (d-48)/dM, vM, dM*ddM, ddM, floor(m/2)+1) :
43 | d == 46 && m<=1 ? _f(s, i+1, x, 1, 10*dM, 10, max(m, 1)) :
44 | (d == 69 || d == 101) && m==1 ? let(
45 | expon = _f(s, i+1, 0, 10, 1, 1, 2)
46 | )
47 | (is_undef(expon) ? undef : expon >= 0 ?
48 | (round(x*dM)*(10^expon/dM)) :
49 | (round(x*dM)/dM)/10^(-expon)) :
50 | undef
51 | )
52 | _f(s, 0, 0, 10, 1, 1, 0);
53 |
54 | csv_parse = function(s) [for (e=split(s, ",")) float(e)];
--------------------------------------------------------------------------------
/src/modules/gridfinity_constants.scad:
--------------------------------------------------------------------------------
1 | // dimensions as declared on https://gridfinity.xyz/specification/
2 |
3 | //Gridfinity grid size
4 | gf_pitch = 42;
5 | //Gridfinity height size
6 | gf_zpitch = 7;
7 |
8 | // each bin is undersize by this much
9 | gf_tolerance = 0.5;
10 |
11 | gf_taper_angle = 45;
12 |
13 | // cup
14 | gf_cup_corner_radius = 3.75;
15 | gf_cup_floor_thickness = 0.7;
16 |
17 | // CupBase
18 | gf_cupbase_lower_taper_height = 0.8;
19 | gf_cupbase_riser_height = 1.8;
20 | gf_cupbase_upper_taper_height = 2.15;
21 | gf_cupbase_magnet_position = 4.8;
22 | gf_cupbase_screw_diameter = 3;
23 | gf_cupbase_screw_depth = 6;
24 | gf_magnet_diameter = 6.5;
25 | gf_magnet_thickness = 2.4;
26 | gf_base_grid_clearance_height = 3.5;
27 |
28 | //stacking lips
29 | // Standard lip
30 | // \ gf_lip_upper_taper_height
31 | // | gf_lip_riser_height
32 | // \ gf_lip_lower_taper_height
33 | // | gf_lip_height
34 | // / gf_lip_support_taper_height
35 | // /
36 | // /
37 | ///
38 | // Reduced lip
39 | // \ gf_lip_upper_taper_height
40 | // | gf_lip_riser_height
41 | // / gf_lip_reduced_support_taper_height
42 | ///
43 | gf_lip_lower_taper_height = 0.7;
44 | gf_lip_riser_height = 1.8;
45 | gf_lip_upper_taper_height = 1.9;
46 | gf_lip_height = 1.2;
47 | //gf_lip_support_taper_height = 2.5;
48 | //gf_lip_reduced_support_taper_height = 1.9;
49 |
50 | // base plate
51 | gf_baseplate_lower_taper_height = 0.7;
52 | gf_baseplate_riser_height = 1.8;
53 | gf_baseplate_upper_taper_height = 2.15;
54 | gf_baseplate_magnet_od = 6.5;
55 | gf_baseplate_magnet_thickness = 2.4;
56 |
57 | // top lip height 4.4mm
58 | gf_Lip_Height = 4.4;//gf_lip_lower_taper_height + gf_lip_riser_height + gf_lip_upper_taper_height;
59 |
60 | // cupbase height 4.75mm + 0.25.
61 | function gfBaseHeight() = gf_cupbase_lower_taper_height + gf_cupbase_riser_height + gf_cupbase_upper_taper_height+0.25; //results in 5
62 | gf_min_base_height = gfBaseHeight();
63 |
64 | // base heighttop lip height 4.4mm
65 | function gfBasePlateHeight() = gf_baseplate_lower_taper_height + gf_baseplate_riser_height + gf_baseplate_upper_taper_height;
66 |
67 |
68 |
69 | // old names, that will get replaced
70 | /*
71 | gridfinity_lip_height = gf_Lip_Height;
72 | gridfinity_corner_radius = gf_cup_corner_radius ;
73 | gridfinity_zpitch = env_pitch().z;
74 | gridfinity_clearance = gf_tolerance;
75 | minFloorThickness = gf_cup_floor_thickness;
76 | const_magnet_height = gf_magnet_thickness;
77 | */
78 |
79 | //Small amount to add to prevent clipping in openSCAD
80 | fudgeFactor = 0.01;
81 |
82 | color_cup = "LightSlateGray";
83 | color_divider = "Gainsboro"; //LemonChiffon
84 | color_topcavity = "Green";//"SteelBlue";
85 | color_label = "DarkCyan";
86 | color_cupcavity = "IndianRed";
87 | color_wallcutout = "SandyBrown";
88 | color_basehole = "DarkSlateGray";
89 | color_base = "DimGray";
90 | color_extension = "lightpink";
91 | color_text = "Yellow"; //Gold
92 | color_cut = "Black";
93 | color_lid = "MediumAquamarine";
--------------------------------------------------------------------------------
/src/modules/module_attachment_clip.scad:
--------------------------------------------------------------------------------
1 | /*
2 | Module: attachment_clip
3 | Description: Creates a wall clip that is used when the box is split.
4 | */
5 | include
6 | include
7 | include
8 | /*
9 | attachment_clip(height = 13,
10 | width = 0,
11 | thickness = 2,
12 | footingThickness= 2,
13 | tabStyle = 0);
14 | */
15 |
16 | // Creates a wall clip that is used when the box is split
17 | // Parameters:
18 | // - height: The height of the clip (default: 8)
19 | // - width: The width of the clip (default: 0, which means it will be set to half of the height)
20 | // - thickness: The thickness of the clip (default: gf_lip_support_taper_height)
21 | // - footingThickness: The thickness of the footing (default: 1)
22 | // - tabStyle: The style of the tab (default: 0)s
23 | module attachment_clip(
24 | height = 8,
25 | width = 0,
26 | thickness = gf_lip_support_taper_height,
27 | footingThickness = 1,
28 | tabStyle = 0)
29 | {
30 | // Assertions to check the input parameters
31 | assert(is_num(height) && height > 0, "height must be a positive number");
32 | assert(is_num(width) && width >= 0, "width must be a non-negative number");
33 | assert(is_num(thickness) && thickness > 0, "thickness must be a positive number");
34 | assert(is_num(footingThickness) && footingThickness > 0, "footingThickness must be a positive number");
35 | assert(is_num(tabStyle) && tabStyle >= 0, "tabStyle must be a non-negative number");
36 |
37 | if(env_help_enabled("debug")) echo("attachment_clip", height=height, width=width, thickness=thickness, tabStyle=tabStyle);
38 | tabVersion = 0;
39 | width = width > 0 ? width : height / 2;
40 | tabHeight = height - thickness * 2;
41 | tabDepth = min(tabHeight / 2 * 0.8, width * 0.8, 3.5);
42 | tabStyle = floor(tabStyle);
43 | translate([0, 0, -height / 2])
44 |
45 | union()
46 | {
47 | if(tabStyle == 2){
48 | // using hull prevents shape not being closed
49 | hull()
50 | polyhedron
51 | (points = [
52 | [0, 0, 0], //0
53 | [0, -width, 0], //1
54 | [0, -width, height], //2
55 | [0, 0, height], //3
56 | [0, thickness, height-thickness], //4
57 | [0, thickness, thickness], //5
58 | [thickness, thickness, thickness], //6
59 | [thickness, -width+thickness, thickness], //7
60 | [thickness, -width+thickness, height-thickness], //8
61 | [thickness, thickness, height-thickness] //9
62 | ],
63 | faces = [[0,1,2,3,4,5],[6,7,8,9]]
64 | );
65 | hull()
66 | polyhedron
67 | (points = [
68 | [0, thickness, thickness], //0
69 | [0, -width+thickness, thickness], //1
70 | [0, -width+thickness, height-thickness], //2
71 | [0, thickness, height-thickness], //3
72 | [0, tabDepth, height-tabDepth], //4
73 | [0, tabDepth, tabDepth], //5
74 | [thickness, thickness, thickness], //6
75 | [thickness, -width+thickness, thickness], //7
76 | [thickness, -width+thickness, height-thickness], //8
77 | [thickness, thickness, height-thickness], //9
78 | [thickness, tabDepth, height-tabDepth], //10
79 | [thickness, tabDepth, tabDepth] //11
80 | ],
81 | faces = [[0,1,2,3,4,5],[6,7,8,9,10,11]]
82 | );
83 | } else if(tabStyle == 1){
84 | // using hull prevents shape not being closed
85 | hull()
86 | polyhedron
87 | (points = [
88 | [0, -thickness, 0], //0
89 | [0, -width, 0], //1
90 | [0, -width, height], //2
91 | [0, -thickness, height], //3
92 | [0, 0, height-thickness], //4
93 | [0, 0, thickness], //5
94 | [thickness, 0, thickness], //6
95 | [thickness, -width+thickness, thickness], //7
96 | [thickness, -width+thickness, height-thickness], //8
97 | [thickness, 0, height-thickness] //9
98 | ],
99 | faces = [[0,1,2,3,4,5],[6,7,8,9]]
100 | );
101 | hull()
102 | polyhedron
103 | (points = [
104 | [0, 0, thickness], //0
105 | [0, -width+thickness, thickness], //1
106 | [0, -width+thickness, height-thickness], //2
107 | [0, 0, height-thickness], //3
108 | [0, tabDepth, height-thickness-tabDepth], //4
109 | [0, tabDepth, thickness+tabDepth], //5
110 | [thickness, 0, thickness], //6
111 | [thickness, -width+thickness, thickness], //7
112 | [thickness, -width+thickness, height-thickness], //8
113 | [thickness, 0, height-thickness], //9
114 | [thickness, tabDepth, height-thickness-tabDepth],//10
115 | [thickness, tabDepth, thickness+tabDepth] //11
116 | ],
117 | faces = [[0,1,2,3,4,5],[6,7,8,9,10,11]]
118 | );
119 | } else {
120 | // using hull prevents shape not being closed
121 | hull()
122 | polyhedron
123 | (points = [
124 | [0, 0, 0], //0
125 | [0, -width, 0], //1
126 | [0, -width, height], //2
127 | [0, 0, height], //3
128 | [thickness, 0, thickness], //4
129 | [thickness, -width+thickness, thickness], //5
130 | [thickness, -width+thickness, height-thickness], //6
131 | [thickness, 0, height-thickness], //7
132 | [-footingThickness, 0, 0], //8
133 | [-footingThickness, -width, 0], //9
134 | [-footingThickness, -width, height], //10
135 | [-footingThickness, 0, height] //11
136 | ],
137 | faces = [[0,1,2,3],[4,5,6,7],[8,9,10,11]]
138 | );
139 | hull()
140 | polyhedron
141 | (points = [
142 | [0, 0, thickness], //0
143 | [0, -width+thickness, thickness], //1
144 | [0, -width+thickness, height-thickness], //2
145 | [0, 0, height-thickness], //3
146 | [0, tabDepth, height-thickness-tabDepth], //4
147 | [0, tabDepth, thickness+tabDepth], //5
148 | [thickness, 0, thickness], //6
149 | [thickness, -width+thickness, thickness], //7
150 | [thickness, -width+thickness, height-thickness], //8
151 | [thickness, 0, height-thickness], //9
152 | [thickness, tabDepth, height-thickness-tabDepth],//10
153 | [thickness, tabDepth, thickness+tabDepth] //11
154 | ],
155 | faces = [[0,1,2,3,4,5],[6,7,8,9,10,11]]
156 | );
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/modules/module_calipers.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 | include
4 | include
5 | include
6 |
7 | module ShowCalipers(
8 | cutx, cuty,
9 | size,
10 | lip_style,
11 | magnet_depth,
12 | screw_depth,
13 | center_magnet_depth,
14 | floor_thickness,
15 | filled_in,
16 | wall_thickness,
17 | efficient_floor,
18 | flat_base){
19 | assert(is_num(cutx));
20 | assert(is_num(cuty));
21 | assert(is_list(size));
22 | assert(is_string(lip_style));
23 | assert(is_num(magnet_depth));
24 | assert(is_num(screw_depth));
25 | assert(is_num(center_magnet_depth));
26 | assert(is_num(floor_thickness));
27 | assert(is_string(filled_in));
28 | assert(is_num(wall_thickness));
29 | assert(is_string(efficient_floor));
30 | assert(is_string(flat_base));
31 |
32 | if(cuty > 0 && $preview)
33 | {
34 | color(color_text)
35 | translate([0,env_pitch().y*cuty,0])
36 | rotate([90,0,0])
37 | showCalipersForSide("width", size.x, size.z, lip_style, magnet_depth, screw_depth, center_magnet_depth, floor_thickness, filled_in,wall_thickness,efficient_floor,flat_base,env_pitch().x);
38 | }
39 |
40 | if(cutx > 0 && $preview)
41 | {
42 | color(color_text)
43 | translate([env_pitch().x*cutx,env_pitch().y*size.y,0])
44 | rotate([90,0,270])
45 | showCalipersForSide("depth", size.y, size.z, lip_style, magnet_depth, screw_depth, center_magnet_depth, floor_thickness, filled_in,wall_thickness,efficient_floor,flat_base,env_pitch().y);
46 | }
47 | }
48 |
49 | module showCalipersForSide(description, gf_num, num_z, lip_style, magnet_depth, screw_depth, center_magnet_depth, floor_thickness, filled_in, wall_thickness, efficient_floor, flat_base, pitch){
50 | assert(is_string(description));
51 | assert(is_num(gf_num));
52 | assert(is_num(num_z));
53 | assert(is_string(lip_style));
54 | assert(is_num(magnet_depth));
55 | assert(is_num(screw_depth));
56 | assert(is_num(center_magnet_depth));
57 | assert(is_num(floor_thickness));
58 | assert(is_string(filled_in));
59 | assert(is_num(wall_thickness));
60 | assert(is_string(efficient_floor));
61 | assert(is_string(flat_base));
62 | assert(is_num(pitch));
63 |
64 | fontSize = 5;
65 | gridHeight= gfBaseHeight();
66 | baseClearanceHeight = cupBaseClearanceHeight(magnet_depth, screw_depth, center_magnet_depth, flat_base);
67 | floorHeight = calculateFloorHeight(
68 | magnet_depth=magnet_depth,
69 | screw_depth=screw_depth,
70 | floor_thickness=floor_thickness,
71 | num_z=num_z,
72 | filled_in=filled_in,
73 | efficient_floor=efficient_floor,
74 | flat_base=flat_base);
75 | floorDepth = efficient_floor != "off"
76 | ? floor_thickness :
77 | floorHeight - baseClearanceHeight;
78 |
79 | if(env_help_enabled("info")) echo("showClippersForSide", description=description, gf_num=gf_num,num_z=num_z,lip_style=lip_style,magnet_depth=magnet_depth,screw_depth=screw_depth,floor_thickness=floor_thickness,filled_in=filled_in,wall_thickness=wall_thickness,efficient_floor=efficient_floor,flat_base=flat_base);
80 | if(env_help_enabled("info")) echo("showClippersForSide", floorHeight=floorHeight, floorDepth=floorDepth, baseClearanceHeight=baseClearanceHeight);
81 | wallTop = calculateWallTop(num_z, lip_style);
82 |
83 | isCutX = description == "depth";
84 | translate([gf_tolerance/2,wallTop,0])
85 | Caliper(messpunkt = false, center=false,
86 | h = 0.1, s = fontSize,
87 | end=0, in=1,
88 | translate=[0,5,0],
89 | l=gf_num*pitch-gf_tolerance,
90 | txt2 = str("total ", description, " ", gf_num));
91 |
92 | translate([gf_tolerance/2+wall_thickness,(1+(num_z-1)/2)*env_pitch().z,0])
93 | Caliper(messpunkt = false, center=false,
94 | h = 0.1, s = fontSize,
95 | end=0, in=1,
96 | l=gf_num*pitch-gf_tolerance-wall_thickness*2,
97 | txt2 = str("inner ", description));
98 |
99 | translate(isCutX
100 | ?[(gf_num)*pitch,0,0]
101 | :[0,0,0])
102 | Caliper(messpunkt = false, center=false,
103 | h = 0.1, size = fontSize,
104 | cx=isCutX ? 0: -1,
105 | end=0, in=2,
106 | l=num_z*env_pitch().z,
107 | translate=isCutX ? [1,0,0] : [-1,0,0],
108 | txt2 = str("height ", num_z));
109 |
110 | if(lip_style != "none")
111 | translate(isCutX
112 | ?[(gf_num)*pitch,num_z*env_pitch().z,0]
113 | :[0,num_z*env_pitch().z,0])
114 | Caliper(messpunkt = false, center=false,
115 | h = 0.1, size = fontSize,
116 | cx=isCutX ? 0: -1,
117 | end=0, in=2,
118 | l=wallTop - (num_z*env_pitch().z),//gf_Lip_Height,
119 | translate=isCutX ? [1,0,0] : [-1,0,0],
120 | txt2 = str("lip height"));
121 |
122 | if(lip_style != "none")
123 | translate(isCutX
124 | ?[(gf_num)*pitch,0,0]
125 | :[0,0,0])
126 | Caliper(messpunkt = false, center=false,
127 | h = 0.1, size = fontSize,
128 | cx=isCutX ? 0: -1,
129 | end=0, in=2,
130 | translate=isCutX ? [fontSize*3,0,0] : [fontSize*-3,0,0],
131 | l=wallTop,
132 | txt2 = str("total height"));
133 |
134 | if(!flat_base)
135 | translate(isCutX
136 | ? gf_num < 1 ? [gf_num*pitch-1,0,0] : [(floor(gf_num)-1)*pitch-1,0,0]
137 | : gf_num < 1 ? [1,0,0] : [pitch,0,0])
138 | Caliper(messpunkt = false, center=false,
139 | h = 0.1, s = fontSize*.75,
140 | cx=isCutX ? 0 : -1,
141 | end=0, in=2,
142 | translate=isCutX ?[3,0,0]:[-3,0,0],
143 | l=gridHeight,
144 | txt2 = "grid height");
145 |
146 | if(baseClearanceHeight > 0)
147 | translate(isCutX
148 | ? gf_num < 1 ? [1,0,0] : [+pitch*(gf_num-1),0,0]
149 | : gf_num < 1 ? [gf_num*pitch-1,0,0] : [pitch-1,0,0])
150 | Caliper(messpunkt = false, center=false,
151 | h = 0.1, s = fontSize*.7,
152 | cx=isCutX ? -1 : 0,
153 | end=0, in=2,
154 | translate=isCutX ?[-2,0,0]:[2,0,0],
155 | l=baseClearanceHeight,
156 | txt2 = "clearance height");
157 |
158 | if(efficient_floor == "off")
159 | translate(isCutX
160 | ? gf_num < 1 ? [1,baseClearanceHeight,0] : [pitch*(gf_num-1),baseClearanceHeight,0]
161 | : gf_num < 1 ? [gf_num*pitch-1,baseClearanceHeight,0] : [pitch-1,baseClearanceHeight,0])
162 | Caliper(messpunkt = false, center=false,
163 | h = 0.1, s = fontSize*.75,
164 | cx=isCutX ? -1 : 0,
165 | end=0, in=2,
166 | translate=isCutX ?[-2,0,0]:[2,0,0],
167 | l=floorDepth,
168 | txt2 = "floor thickness");
169 |
170 | translate(isCutX
171 | ? gf_num < 1 ? [pitch*gf_num/2,0,0] : [pitch*(gf_num-1/2),0,0]
172 | : gf_num < 1 ? [pitch*gf_num/2,0,0] : [pitch/2,0,0])
173 | Caliper(messpunkt = false, center=false,
174 | h = 0.1, s = fontSize*0.8,
175 | cx=1, end=0, in=2,
176 | translate=[0,-floorHeight/2+2,0],
177 | l=floorHeight,
178 | txt2 = "floor height");
179 |
180 | if(screw_depth > 0)
181 | translate(isCutX
182 | ? [pitch*(gf_num)-6,0,0]
183 | : [10,0,0])
184 | Caliper(messpunkt = false, center=false,
185 | h = 0.1, s = fontSize*.75,
186 | cx=1, end=0, in=2,
187 | l=screw_depth,
188 | txt2 = "screw");
189 |
190 | if(magnet_depth > 0)
191 | translate(isCutX
192 | ? [pitch*(gf_num)-10,0,0]
193 | : [6,0,0])
194 | Caliper(messpunkt = false, center=false,
195 | h = 0.1, s = fontSize*.75,
196 | //translate=[-2,0,0],
197 | cx=1, end=0, in=2,
198 | l=magnet_depth,
199 | txt2 = "magnet");
200 | }
201 |
--------------------------------------------------------------------------------
/src/modules/module_divider_walls.scad:
--------------------------------------------------------------------------------
1 | iSeparatorPosition = 0;
2 | iSeparatorLength = 1;
3 | iSeparatorHeight = 2;
4 | iSeparatorWallThickness = 3;
5 | iSeparatorBendPosition = 4;
6 | iSeparatorBendSeparation = 5;
7 | iSeparatorBendAngle = 6;
8 | iSeparatorWallCutDepth = 7;
9 | iSeparatorWallCutoutWidth = 8;
10 |
11 |
12 | //Takes the user config and calculates the separators positions
13 | function calculateSeparators(
14 | separator_config,
15 | length,
16 | height,
17 | wall_thickness = 0,
18 | bend_position = 0,
19 | bend_angle = 0,
20 | bend_separation = 0,
21 | cut_depth = 0) =
22 | is_string(separator_config)
23 | ? let(seps = [for (s = split(separator_config, "|")) csv_parse(s)]) // takes part of an array
24 | [for (i = [0:len(seps)-1]) [
25 | is_list(seps[i]) && len(seps[i]) >= 1 ? seps[i][0] : 0, //0 iSeparatorPosition
26 | length, //1 iSeparatorLength
27 | height, //2 iSeparatorHeight
28 | is_list(seps[i]) && len(seps[i]) >= 6 ? seps[i][5] : wall_thickness, //3 iSeparatorWallThickness
29 | bend_position, //4 iSeparatorBendPosition
30 | is_list(seps[i]) && len(seps[i]) >= 2 ? seps[i][1] : bend_separation, //5 iSeparatorBendSeparation
31 | is_list(seps[i]) && len(seps[i]) >= 3 ? seps[i][2] : bend_angle*(i%2==1?1:-1), //6 iSeparatorBendAngle
32 | is_list(seps[i]) && len(seps[i]) >= 4 ? seps[i][3] : cut_depth, //7 iSeparatorWallCutDepth
33 | is_list(seps[i]) && len(seps[i]) >= 5 ? seps[i][4] : 0 //8 iSeparatorWallCutoutWidth
34 | ]]
35 | : (is_list(separator_config) && len(separator_config) > 0)
36 | ? [for (i = [0:len(separator_config)-1])[
37 | separator_config[i], //0 iSeparatorPosition
38 | length, //1 iSeparatorLength
39 | height, //2 iSeparatorHeight
40 | wall_thickness, //3 iSeparatorWallThickness
41 | bend_position, //4 iSeparatorBendPosition
42 | bend_separation, //5 iSeparatorBendSeparation
43 | bend_angle*(i%2==1?1:-1), //6 iSeparatorBendAngle
44 | cut_depth, //7 iSeparatorWallCutDepth
45 | 0 //8 iSeparatorWallCutoutWidth
46 | ]]
47 | : [];
48 |
49 | //Renders the physical separators
50 | //calculatedSeparators - the calculated separator positions
51 | //separator_orientation - the orientation of the separators (vertical or horizontal)
52 | //override_wall_thickness - overrides the wallthickness
53 | module separators(
54 | calculatedSeparators,
55 | separator_orientation = "vertical",
56 | override_wall_thickness)
57 | {
58 |
59 | position_separators(
60 | calculatedSeparators = calculatedSeparators,
61 | separator_orientation = separator_orientation){
62 | thickness = is_num(override_wall_thickness) ? override_wall_thickness : $sepCfg[iSeparatorWallThickness];
63 | translate([-thickness/2,0])
64 | bentWall(
65 | length=$sepCfg[iSeparatorLength],
66 | bendPosition=$sepCfg[iSeparatorBendPosition],
67 | bendAngle=$sepCfg[iSeparatorBendAngle],
68 | separation=$sepCfg[iSeparatorBendSeparation],
69 | lowerBendRadius=$sepCfg[iSeparatorBendSeparation]/2,
70 | upperBendRadius=$sepCfg[iSeparatorBendSeparation]/2,
71 | height = $sepCfg[iSeparatorHeight],
72 | wall_cutout_depth = $sepCfg[iSeparatorWallCutDepth],
73 | wall_cutout_width = $sepCfg[iSeparatorWallCutoutWidth],
74 | thickness = thickness);
75 | }
76 | }
77 |
78 | //positions the child in the correct location for the settings.
79 | //This is a generic function that can be used for any separator, for example, the actual wall or the wall cutout
80 | module position_separators(
81 | calculatedSeparators,
82 | separator_orientation)
83 | {
84 | assert(separator_orientation == "horizontal" || separator_orientation == "vertical", "separator_orientation must be 'horizontal' or 'vertical'");
85 |
86 | sepConfigs = calculatedSeparators;
87 | if(env_help_enabled("trace")) echo("separators",sepConfigs=sepConfigs);
88 |
89 | if(is_list(sepConfigs) && len(sepConfigs) > 0){
90 | for (i=[0:len(sepConfigs)-1]) {
91 | //set the current separator config for the child to access
92 | $sepCfg = sepConfigs[i];
93 | if(separator_orientation == "vertical"){
94 | translate([$sepCfg[iSeparatorPosition],0,0])
95 | children();
96 | }
97 | if(separator_orientation == "horizontal"){
98 | translate([0,$sepCfg[iSeparatorPosition],0])
99 | rotate([0,0,90])
100 | children();
101 | }
102 | }
103 | }
104 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_Extendable.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 |
4 | /* [Extendable]
5 | extension_x_enabled = "disabled"; //[disabled, front, back]
6 | extension_x_position = 0.5;
7 | extension_y_enabled = "disabled"; //[disabled, front, back]
8 | extension_y_position = 0.5;
9 | extension_tabs_enabled = true;
10 | //Tab size, height, width, thickness, style. width default is height, thickness default is 1.4, style {0,1,2}.
11 | extension_tab_size= [10,0,0,0];
12 | */
13 | iExtendablex=0;
14 | iExtendabley=1;
15 | iExtendableTabsEnabled=2;
16 | iExtendableTabSize=3;
17 |
18 | iExtendableEnabled=0;
19 | iExtendablePosition=1;
20 | iExtendablePositionmm=2;
21 |
22 | iExtendableTabSizeHeight=0;
23 | iExtendableTabSizeWidth=1;
24 | iExtendableTabSizeThickness=2;
25 | iExtendableTabSizeStyle=3;
26 |
27 | BinExtensionEnabled_disabled = "disabled";
28 | BinExtensionEnabled_front = "front";
29 | BinExtensionEnabled_back = "back";
30 | BinExtensionEnabled_values = [BinExtensionEnabled_disabled, BinExtensionEnabled_front, BinExtensionEnabled_back];
31 | function validateBinExtensionEnabled(value, name = "BinExtensionEnabled") =
32 | assert(list_contains(BinExtensionEnabled_values, value), typeerror(name, value))
33 | value;
34 |
35 | function ExtendableSettings(
36 | extendablexEnabled,
37 | extendablexPosition,
38 | extendableyEnabled,
39 | extendableyPosition,
40 | extendableTabsEnabled,
41 | extendableTabSize) =
42 | let(
43 | xEnabled = validateBinExtensionEnabled(
44 | is_bool(extendablexEnabled)
45 | ? extendablexEnabled ? BinExtensionEnabled_front : BinExtensionEnabled_disabled
46 | : extendablexEnabled),
47 | yEnabled = validateBinExtensionEnabled(
48 | is_bool(extendableyEnabled)
49 | ? extendableyEnabled ? BinExtensionEnabled_front : BinExtensionEnabled_disabled
50 | : extendableyEnabled),
51 | xPosition = is_bool(extendablexEnabled) ? 0.5 : extendablexPosition,
52 | yPosition = is_bool(extendableyEnabled) ? 0.5 : extendableyPosition,
53 | result = [
54 | [xEnabled, xPosition],
55 | [yEnabled, yPosition],
56 | extendableTabsEnabled,
57 | extendableTabSize],
58 | validatedResult = ValidateExtendableSettings(result)
59 | ) validatedResult;
60 |
61 | function ValidateExtendableSettings(settings, num_x, num_y) =
62 | assert(is_list(settings), "Extendable Settings must be a list")
63 | assert(len(settings)==4, "Extendable Settings must length 4")
64 | assert(is_list(settings[iExtendablex]) && len(settings[iExtendablex])>=2 && len(settings[iExtendablex])<=3, "Extendable x Settings must length 2 or 3")
65 | assert(is_list(settings[iExtendabley]) && len(settings[iExtendabley])>=2 && len(settings[iExtendabley])<=3, "Extendable y Settings must length 2 or 3")
66 | let(
67 | xetendableEnabled = validateBinExtensionEnabled(settings[iExtendablex][iExtendableEnabled]),
68 | yetendableEnabled = validateBinExtensionEnabled(settings[iExtendabley][iExtendableEnabled]),
69 | cutx = !is_undef(num_x) ? unitPositionTo_mm(settings.x[iExtendablePosition],num_x,env_pitch().x) : num_x,
70 | cuty = !is_undef(num_y) ? unitPositionTo_mm(settings.y[iExtendablePosition],num_y,env_pitch().y) : num_y
71 | ) [
72 | [xetendableEnabled, settings[iExtendablex][iExtendablePosition], cutx],
73 | [yetendableEnabled, settings[iExtendabley][iExtendablePosition], cuty],
74 | settings[iExtendableTabsEnabled],
75 | settings[iExtendableTabSize]];
76 |
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 | use
6 | use
7 | use
8 |
9 | /* [BasePlate] */
10 | // Plate Style
11 | Default_Base_Plate_Options = "default";//[default:Default, cnc:CNC or Laser]
12 | Default_Oversize_method = "fill"; //[crop, fill]
13 |
14 | // Magnet
15 | // Enable magnets
16 | Default_Enable_Magnets = true;
17 | //size of magnet, diameter and height. Zacks original used 6.5 and 2.4
18 | Default_Magnet_Size = [6.5, 2.4]; // .1
19 |
20 | /* [Base Plate Clips - POC dont use yet]*/
21 | Default_Connector_Position = "center_wall";
22 | Default_Connector_Clip_Enabled = false;
23 | Default_Connector_Clip_Size = 10;
24 | Default_Connector_Clip_Tolerance = 0.1;
25 |
26 | //This feature is not yet finalised, or working properly.
27 | Default_Connector_Butterfly_Enabled = false;
28 | Default_Connector_Butterfly_Size = [6,6,1.5];
29 | Default_Connector_Butterfly_Radius = 0.1;
30 | Default_Connector_Butterfly_Tolerance = 0.1;
31 |
32 | //This feature is not yet finalised, or working properly.
33 | Default_Connector_Filament_Enabled = false;
34 | Default_Connector_Filament_Diameter = 2;
35 | Default_Connector_Filament_Length = 8;
36 |
37 | module gridfinity_baseplate(
38 | num_x = 2,
39 | num_y = 3,
40 | outer_num_x = 0,
41 | outer_num_y = 0,
42 | outer_height = 0,
43 | position_fill_grid_x = "near",
44 | position_fill_grid_y = "near",
45 | position_grid_in_outer_x = "center",
46 | position_grid_in_outer_y = "center",
47 | plate_corner_radius=gf_cup_corner_radius,
48 | magnetSize = Default_Magnet_Size,
49 | magnetZOffset=0,
50 | magnetTopCover=0,
51 | reducedWallHeight = -1,
52 | reduceWallTaper = false,
53 | cornerScrewEnabled = false,
54 | centerScrewEnabled = false,
55 | weightedEnable = false,
56 | oversizeMethod = Default_Oversize_method,
57 | plateOptions = Default_Base_Plate_Options,
58 | customGridEnabled = false,
59 | gridPositions = [[1]],
60 | connectorPosition = Default_Connector_Position,
61 | connectorClipEnabled = Default_Connector_Clip_Enabled,
62 | connectorClipSize = Default_Connector_Clip_Size,
63 | connectorClipTolerance = Default_Connector_Clip_Tolerance,
64 | connectorButterflyEnabled = Default_Connector_Butterfly_Enabled,
65 | connectorButterflySize = Default_Connector_Butterfly_Size,
66 | connectorButterflyRadius = Default_Connector_Butterfly_Radius,
67 | connectorButterflyTolerance = Default_Connector_Butterfly_Tolerance,
68 | connectorFilamentEnabled = Default_Connector_Filament_Enabled,
69 | connectorFilamentDiameter = Default_Connector_Filament_Diameter,
70 | connectorFilamentLength = Default_Connector_Filament_Length)
71 | {
72 | _gridPositions = customGridEnabled ? gridPositions : [[1]];
73 |
74 | outer_width = oversizeMethod == "outer" ? max(num_x, outer_num_x) : outer_num_x;
75 | outer_depth = oversizeMethod == "outer" ? max(num_y, outer_num_y) : outer_num_y;
76 |
77 | width =
78 | oversizeMethod == "crop" ? ceil(num_x)
79 | : oversizeMethod == "outer" ? floor(num_x)
80 | : num_x ;
81 | depth =
82 | oversizeMethod == "crop" ? ceil(num_y)
83 | : oversizeMethod == "outer" ? floor(num_y)
84 | : num_y;
85 |
86 | debug_cut()
87 | intersection(){
88 | union() {
89 | for(xi = [0:len(_gridPositions)-1])
90 | for(yi = [0:len(_gridPositions[xi])-1])
91 | {
92 | if(is_list(_gridPositions[xi][yi])){
93 | assert(is_num(_gridPositions[xi][yi][0]));
94 | assert(is_list(_gridPositions[xi][yi][1]));
95 | }
96 | gridPosCorners = is_list(_gridPositions[xi][yi]) ? _gridPositions[xi][yi][0] : _gridPositions[xi][yi];
97 | gridPosx = is_list(_gridPositions[xi][yi]) ? _gridPositions[xi][yi][1].x : 1;
98 | gridPosy = is_list(_gridPositions[xi][yi]) ? _gridPositions[xi][yi][1].y : 1;
99 |
100 | if(_gridPositions[xi][yi])
101 | {
102 | let($pitch = [env_pitch().x*gridPosx, env_pitch().y*gridPosy, env_pitch().y])
103 | translate([env_pitch().x*xi/gridPosx,env_pitch().y*yi/gridPosy,0])
104 | baseplate(
105 | width = customGridEnabled ? 1 : width,
106 | depth = customGridEnabled ? 1 : depth,
107 | outer_width = outer_width,
108 | outer_depth = outer_depth,
109 | outer_height = outer_height,
110 | position_fill_grid_x = position_fill_grid_x,
111 | position_fill_grid_y = position_fill_grid_y,
112 | position_grid_in_outer_x = position_grid_in_outer_x,
113 | position_grid_in_outer_y = position_grid_in_outer_y,
114 | magnetSize = magnetSize,
115 | magnetZOffset=magnetZOffset,
116 | magnetTopCover=magnetTopCover,
117 | reducedWallHeight = reducedWallHeight,
118 | reduceWallTaper = reduceWallTaper,
119 | cornerScrewEnabled = cornerScrewEnabled,
120 | centerScrewEnabled = centerScrewEnabled,
121 | weightedEnable = weightedEnable,
122 | plateOptions= plateOptions,
123 | connectorPosition = connectorPosition,
124 | connectorClipEnabled = connectorClipEnabled,
125 | connectorClipSize = connectorClipSize,
126 | connectorClipTolerance = connectorClipTolerance,
127 | connectorButterflyEnabled = connectorButterflyEnabled,
128 | connectorButterflySize = connectorButterflySize,
129 | connectorButterflyRadius = connectorButterflyRadius,
130 | connectorButterflyTolerance = connectorButterflyTolerance,
131 | connectorFilamentEnabled = connectorFilamentEnabled,
132 | connectorFilamentDiameter = connectorFilamentDiameter,
133 | connectorFilamentLength = connectorFilamentLength,
134 | plate_corner_radius = plate_corner_radius,
135 | roundedCorners = gridPosCorners == 1 ? 15 : gridPosCorners - 2);
136 | }
137 | }
138 | }
139 | if(oversizeMethod == "crop")
140 | cube([num_x*env_pitch().x, num_y*env_pitch().y,20]);
141 | }
142 | }
143 |
144 | module baseplate(
145 | width = 2,
146 | depth = 1,
147 | outer_width = 0,
148 | outer_depth = 0,
149 | outer_height = 0,
150 | position_fill_grid_x = "near",
151 | position_fill_grid_y = "near",
152 | position_grid_in_outer_x = "center",
153 | position_grid_in_outer_y = "center",
154 | magnetSize = [gf_baseplate_magnet_od,gf_baseplate_magnet_thickness],
155 | magnetZOffset=0,
156 | magnetTopCover=0,
157 | reducedWallHeight = -1,
158 | reduceWallTaper = false,
159 | cornerScrewEnabled = false,
160 | centerScrewEnabled = false,
161 | weightedEnable = false,
162 | plateOptions = "default",
163 | plate_corner_radius = gf_cup_corner_radius,
164 | roundedCorners = 15,
165 | connectorPosition = Default_Connector_Position,
166 | connectorClipEnabled = Default_Connector_Clip_Enabled,
167 | connectorClipSize = Default_Connector_Clip_Size,
168 | connectorClipTolerance = Default_Connector_Clip_Tolerance,
169 | connectorButterflyEnabled = Default_Connector_Butterfly_Enabled,
170 | connectorButterflySize = Default_Connector_Butterfly_Size,
171 | connectorButterflyRadius = Default_Connector_Butterfly_Radius,
172 | connectorButterflyTolerance = Default_Connector_Butterfly_Tolerance,
173 | connectorFilamentEnabled = Default_Connector_Filament_Enabled,
174 | connectorFilamentDiameter = Default_Connector_Filament_Diameter,
175 | connectorFilamentLength = Default_Connector_Filament_Length)
176 | {
177 | assert_openscad_version();
178 |
179 | difference(){
180 | union(){
181 | if (plateOptions == "cnclaser"){
182 | baseplate_cnclaser(
183 | num_x=width,
184 | num_y=depth,
185 | magnetSize=magnetSize,
186 | magnetZOffset=magnetZOffset,
187 | roundedCorners=roundedCorners);
188 | }
189 | else {
190 | baseplate_regular(
191 | grid_num_x = width,
192 | grid_num_y = depth,
193 | outer_num_x = outer_width,
194 | outer_num_y = outer_depth,
195 | outer_height = outer_height,
196 | position_fill_grid_x = position_fill_grid_x,
197 | position_fill_grid_y = position_fill_grid_y,
198 | position_grid_in_outer_x = position_grid_in_outer_x,
199 | position_grid_in_outer_y = position_grid_in_outer_y,
200 | magnetSize = magnetSize,
201 | magnetZOffset=magnetZOffset,
202 | magnetTopCover=magnetTopCover,
203 | reducedWallHeight=reducedWallHeight,
204 | reduceWallTaper=reduceWallTaper,
205 | centerScrewEnabled = centerScrewEnabled,
206 | cornerScrewEnabled = cornerScrewEnabled,
207 | weightHolder = weightedEnable,
208 | cornerRadius = plate_corner_radius,
209 | roundedCorners=roundedCorners)
210 | frame_connectors(
211 | width = width,
212 | depth = depth,
213 | connectorPosition = connectorPosition,
214 | connectorClipEnabled = connectorClipEnabled,
215 | connectorClipSize = connectorClipSize,
216 | connectorClipTolerance = connectorClipTolerance,
217 | connectorButterflyEnabled = connectorButterflyEnabled,
218 | connectorButterflySize = connectorButterflySize,
219 | connectorButterflyRadius = connectorButterflyRadius,
220 | connectorButterflyTolerance = connectorButterflyTolerance,
221 | connectorFilamentEnabled = connectorFilamentEnabled,
222 | connectorFilamentLength = connectorFilamentLength,
223 | connectorFilamentDiameter = connectorFilamentDiameter);
224 | }
225 | }
226 | }
227 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate_cnclaser.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 |
6 | /*
7 | BasePlateSettings_cnclaser = ["cnclaser", [
8 | [iBaseplateTypeSettings_SupportsMagnets, true],
9 | ]];
10 | */
11 |
12 | //cncmagnet_baseplate(1,2, magnetSize = [0,0]);
13 | //baseplate_cnclaser(num_x = 1.5, num_y = 2.8, magnetSize=[6,7]);
14 |
15 | module baseplate_cnclaser(
16 | num_x,
17 | num_y,
18 | cornerRadius = gf_cup_corner_radius,
19 | magnetSize= [gf_baseplate_magnet_od,gf_baseplate_magnet_thickness],
20 | roundedCorners = 15)
21 | {
22 | magnetEnable = magnetSize[0] >0 && magnetSize[1] > 0;
23 | magnetSize = magnetEnable ? magnetSize : [0,0];
24 |
25 | magnet_position = calculateAttachmentPositions(magnetSize[0]);
26 | magnetHeight = magnetSize[1];
27 | magnetborder = 5;
28 |
29 | translate([0, 0, magnetHeight])
30 | cnclaser_baseplate_internal(num_x, num_y,
31 | extra_down=magnetHeight,
32 | cornerRadius = cornerRadius,
33 | roundedCorners = roundedCorners)
34 | if(magnetEnable) {
35 | translate([env_pitch().x/2,env_pitch().y/2])
36 | gridcopycorners(
37 | r=magnet_position,
38 | num_x=($gci.x == ceil(num_x)-1 ? num_x-$gci.x : 1),
39 | num_y=($gci.y == ceil(num_y)-1 ? num_y-$gci.y : 1),
40 | center= true) {
41 | //magnet cutout
42 | translate([0, 0, -fudgeFactor*3])
43 | cylinder(d=magnetSize[0], h=magnetSize[1]+fudgeFactor*4);
44 | }
45 | }
46 |
47 | }
48 |
49 | module cnclaser_baseplate_internal(
50 | num_x,
51 | num_y,
52 | extra_down=0,
53 | height = 4,
54 | cornerRadius = gf_cup_corner_radius,
55 | roundedCorners = 15) {
56 |
57 | corner_position = [env_pitch().x/2-cornerRadius, env_pitch().y/2-cornerRadius];
58 |
59 | difference() {
60 | color(color_cup)
61 | outer_baseplate(
62 | num_x=num_x,
63 | num_y=num_y,
64 | extendedDepth=extra_down,
65 | height=height,
66 | cornerRadius = cornerRadius,
67 | roundedCorners = roundedCorners);
68 |
69 | color(color_topcavity)
70 | translate([0, 0, -fudgeFactor])
71 | gridcopy(ceil(num_x), ceil(num_y))
72 | union(){
73 | hull()
74 | cornercopy(
75 | r=corner_position,
76 | ($gci.x == ceil(num_x)-1 ? num_x-$gci.x : 1),
77 | ($gci.y == ceil(num_y)-1 ? num_y-$gci.y : 1))
78 | cylinder(r=2,h=height+fudgeFactor*2);
79 | translate([0,0,-extra_down])
80 | children();
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate_cncmagnet.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 |
6 | cncmagnet_baseplate(1,2, magnetSize = [0,0]);
7 |
8 | BasePlateSettings_cncmagnet = ["cncmagnet", [
9 | [iBaseplateTypeSettings_SupportsMagnets, true],
10 | ]];
11 |
12 | module cncmagnet_baseplate(
13 | num_x,
14 | num_y,
15 | cornerRadius = gf_cup_corner_radius,
16 | magnetSize= [gf_baseplate_magnet_od,gf_baseplate_magnet_thickness],
17 | roundedCorners = 15)
18 | {
19 | magnetEnable = magnetSize[0] >0 && magnetSize[1] > 0;
20 | magnetSize = magnetSize ? magnetSize : [0,0];
21 |
22 | magnet_position = calculateAttachmentPositions(gf_baseplate_magnet_od);
23 |
24 | frameHeight = magnetSize[1];
25 | magnetborder = 5;
26 |
27 | difference() {
28 | translate([0, 0, frameHeight])
29 | cnc_baseplate(num_x, num_y,
30 | extra_down=frameHeight,
31 | cornerRadius = cornerRadius,
32 | roundedCorners = roundedCorners);
33 |
34 | if(magnetEnable){
35 | gridcopy(num_x, num_y) {
36 | cornercopy(magnet_position) {
37 | translate([0, 0, -fudgeFactor])
38 | cylinder(d=magnetSize[0], h=magnetSize[1]+fudgeFactor*2);
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | module cnc_baseplate(
46 | num_x,
47 | num_y,
48 | extra_down=0,
49 | height = 4,
50 | cornerRadius = gf_cup_corner_radius,
51 | roundedCorners = 15) {
52 |
53 | corner_position = [env_pitch().x/2-cornerRadius, env_pitch().y/2-cornerRadius];
54 |
55 | difference() {
56 | color(color_cup)
57 | hull()
58 | //render()
59 | cornercopy(r=corner_position, num_x, num_y) {
60 | radius = bitwise_and(roundedCorners, decimaltobitwise($idx[0],$idx[1])) > 0 ? cornerRadius : 0.01;// 0.01 is almost zero....
61 | ctrn = [
62 | ($idx[0] == 0 ? -1 : 1)*(cornerRadius-radius),
63 | ($idx[1] == 0 ? -1 : 1)*(cornerRadius-radius), -extra_down];
64 | translate(ctrn)
65 | cylinder(r=radius, h=height+extra_down);
66 | }
67 |
68 | color(color_topcavity)
69 | translate([0, 0, -fudgeFactor])
70 | gridcopy(num_x, num_y)
71 | hull()
72 | cornercopy(corner_position, 1, 1)
73 | cylinder(r=2,h=height+fudgeFactor*2);
74 | }
75 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate_common_post.scad:
--------------------------------------------------------------------------------
1 | BasePlateSettings =[
2 | BasePlateSettings_cnclaser];
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate_lid.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 | include
6 |
7 | module baseplate_lid(
8 | num_x,
9 | num_y,
10 | lidOptions = "default",
11 | lidIncludeMagnets = true,
12 | lidEfficientFloorThickness = 0.7,
13 | lidEfficientBaseHeight = 0.4,
14 | position_fill_grid_x = "far",
15 | position_fill_grid_y = "far",
16 | magnetSize = [gf_baseplate_magnet_od,gf_baseplate_magnet_thickness],
17 | reducedWallHeight=-1,
18 | cornerScrewEnabled = true,
19 | cornerRadius = gf_cup_corner_radius) {
20 | flat_base = lidOptions == "flat" ? FlatBase_gridfinity : FlatBase_off;
21 | half_pitch = lidOptions == "halfpitch";
22 | efficient_base = lidOptions == "efficient";
23 |
24 | tray = false;
25 |
26 | height =
27 | flat_base ? 0.7 :
28 | half_pitch ? 0.9 :
29 | efficient_base ? lidEfficientBaseHeight : 1;
30 |
31 | //These should be base constants
32 | minFloorThickness = 1;
33 | counterSinkDepth = 2.5;
34 | screwDepth = counterSinkDepth+3.9;
35 | weightDepth = 4;
36 |
37 | frameBaseHeight = magnetSize[1];
38 | frameLipHeight = 4;
39 | frameTop = 7*height;
40 | difference() {
41 | union(){
42 | grid_block(
43 | num_x,
44 | num_y,
45 | efficient_base ? lidEfficientBaseHeight+0.6 : height,
46 | filledin = tray ? "enabled" : "enabledfilllip" , //[disabled, enabled, enabledfilllip]
47 | cupBase_settings = CupBaseSettings(
48 | flatBase=flat_base,
49 | halfPitch=half_pitch),
50 | lip_settings = LipSettings());
51 | }
52 |
53 | if(!tray)
54 | translate([0,0,frameTop])
55 | union(){
56 | translate([0,0,4.4])
57 | outer_baseplate(
58 | num_x=num_x,
59 | num_y=num_y,
60 | height=frameLipHeight,
61 | cornerRadius = cornerRadius);
62 |
63 | frame_cavity(
64 | num_x=num_x,
65 | num_y=num_y,
66 | position_fill_grid_x = position_fill_grid_x,
67 | position_fill_grid_y = position_fill_grid_y,
68 | extra_down = frameBaseHeight,
69 | frameLipHeight = frameLipHeight,
70 | cornerRadius = cornerRadius,
71 | reducedWallHeight = reducedWallHeight)
72 | difference(){
73 | translate([fudgeFactor,fudgeFactor,-fudgeFactor])
74 | cube([env_pitch().x-fudgeFactor*2,env_pitch().y-fudgeFactor*2,frameBaseHeight-fudgeFactor*2]);
75 |
76 | baseplate_cavities(
77 | num_x = $gc_size.x,
78 | num_y = $gc_size.y,
79 | baseCavityHeight=frameBaseHeight,
80 | magnetSize = magnetSize,
81 | magnetSouround = false,
82 | cornerRadius = cornerRadius);
83 | }
84 | }
85 | }
86 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_baseplate_regular.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 |
6 | module baseplate_regular(
7 | grid_num_x,
8 | grid_num_y,
9 | outer_num_x = 0,
10 | outer_num_y = 0,
11 | outer_height = 0,
12 | position_fill_grid_x = "near",
13 | position_fill_grid_y = "near",
14 | position_grid_in_outer_x = "center",
15 | position_grid_in_outer_y = "center",
16 | magnetSize = [0,0],
17 | magnetZOffset=0,
18 | magnetTopCover=0,
19 | reducedWallHeight=-1,
20 | reduceWallTaper = false,
21 | centerScrewEnabled = false,
22 | cornerScrewEnabled = false,
23 | weightHolder = false,
24 | cornerRadius = gf_cup_corner_radius,
25 | roundedCorners = 15) {
26 |
27 | //These should be base constants
28 | minFloorThickness = 1;
29 | counterSinkDepth = 2.5;
30 | screwDepth = counterSinkDepth+3.9;
31 | weightDepth = 4;
32 |
33 | frameBaseHeight = max(
34 | centerScrewEnabled ? screwDepth : 0,
35 | centerScrewEnabled ? counterSinkDepth + weightDepth + minFloorThickness : 0,
36 | cornerScrewEnabled ? screwDepth : 0,
37 | cornerScrewEnabled ? magnetSize[1] + counterSinkDepth + minFloorThickness : 0,
38 | weightHolder ? weightDepth+minFloorThickness : 0,
39 | magnetSize.y+magnetZOffset+magnetTopCover);
40 | $frameBaseHeight = frameBaseHeight;
41 |
42 | translate([0,0,frameBaseHeight])
43 | frame_plain(
44 | grid_num_x = grid_num_x,
45 | grid_num_y = grid_num_y,
46 | outer_num_x = outer_num_x,
47 | outer_num_y = outer_num_y,
48 | outer_height = outer_height,
49 | position_fill_grid_x = position_fill_grid_x,
50 | position_fill_grid_y = position_fill_grid_y,
51 | position_grid_in_outer_x = position_grid_in_outer_x,
52 | position_grid_in_outer_y = position_grid_in_outer_y,
53 | extra_down=frameBaseHeight,
54 | cornerRadius = cornerRadius,
55 | reducedWallHeight=reducedWallHeight,
56 | reduceWallTaper=reduceWallTaper,
57 | roundedCorners = roundedCorners){
58 | //translate([0,0,-fudgeFactor])
59 | difference(){
60 | translate([fudgeFactor,fudgeFactor,fudgeFactor])
61 | cube([env_pitch().x-fudgeFactor*2,env_pitch().y-fudgeFactor*2,frameBaseHeight+fudgeFactor*2]);
62 |
63 | baseplate_cavities(
64 | num_x = $gc_size.x,
65 | num_y = $gc_size.y,
66 | baseCavityHeight=frameBaseHeight+fudgeFactor,
67 | magnetSize = magnetSize,
68 | magnetZOffset=magnetZOffset,
69 | magnetTopCover=magnetTopCover,
70 | centerScrewEnabled = centerScrewEnabled && $gc_is_corner.x && $gc_is_corner.y,
71 | cornerScrewEnabled = cornerScrewEnabled,
72 | weightHolder = weightHolder,
73 | cornerRadius = cornerRadius,
74 | roundedCorners = roundedCorners,
75 | reverseAlignment = [$gci.x == 0, $gci.y==0]);
76 | }
77 |
78 | children();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_block.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 |
4 | show_gridfinity_demo = false;
5 | if(show_gridfinity_demo){
6 |
7 | grid_block($fn=64);
8 |
9 | translate([50,0,0])
10 | union(){
11 | frame_cavity($fn=64);
12 |
13 | translate([0,50*3,0])
14 | frame_cavity(remove_bottom_taper=true,$fn=64);
15 |
16 | translate([0,50*4,0])
17 | frame_cavity(reducedWallHeight=1,$fn=64);
18 | }
19 |
20 | translate([150,0,0])
21 | union(){
22 | pad_oversize($fn=64);
23 |
24 | translate([0,50,0])
25 | pad_oversize(extend_down=5,$fn=64);
26 |
27 | translate([0,50*2,0])
28 | pad_oversize(margins=1,$fn=64);
29 |
30 | translate([0,50*3,0])
31 | pad_oversize(remove_bottom_taper=true,$fn=64);
32 |
33 | translate([0,50*4,0])
34 | pad_oversize(render_top=false,$fn=64);
35 |
36 | translate([0,50*5,0])
37 | pad_oversize(render_bottom=false,$fn=64);
38 | }
39 | }
40 |
41 | // basic block with cutout in top to be stackable, optional holes in bottom
42 | // start with this and begin 'carving'
43 | //grid_block();
44 | module grid_block(
45 | num_x=1,
46 | num_y=2,
47 | num_z=2,
48 | position = "zero",
49 | filledin = "disabled", //[disabled, enabled, enabledfilllip]
50 | wall_thickness = 1.2,
51 | cupBase_settings = CupBaseSettings(),
52 | lip_settings = LipSettings(),
53 | help)
54 | {
55 | lipHeight = 3.75;
56 |
57 | assert_openscad_version();
58 | cupBase_settings = ValidateCupBaseSettings(cupBase_settings);
59 |
60 | //Legacy variables.
61 | magnet_size=cupBase_settings[iCupBase_MagnetSize];
62 | screw_size=cupBase_settings[iCupBase_ScrewSize];
63 | hole_overhang_remedy=cupBase_settings[iCupBase_HoleOverhangRemedy];
64 | box_corner_attachments_only = cupBase_settings[iCupBase_CornerAttachmentsOnly];
65 | half_pitch=cupBase_settings[iCupBase_HalfPitch];
66 | flat_base=cupBase_settings[iCupBase_FlatBase];
67 | center_magnet_size = cupBase_settings[iCupBase_CenterMagnetSize];
68 | magnet_easy_release = cupBase_settings[iCupBase_MagnetEasyRelease];
69 |
70 | outer_size = [env_pitch().x - gf_tolerance, env_pitch().y - gf_tolerance]; // typically 41.5
71 | block_corner_position = [outer_size.x/2 - gf_cup_corner_radius, outer_size.y/2 - gf_cup_corner_radius]; // need not match center of pad corners
72 |
73 | magnet_position = calculateAttachmentPositions(magnet_size[iCylinderDimension_Diameter], screw_size[iCylinderDimension_Diameter]);
74 |
75 | overhang_fix = hole_overhang_remedy > 0 && magnet_size[iCylinderDimension_Diameter] > 0 && screw_size[iCylinderDimension_Diameter] > 0 ? hole_overhang_remedy : 0;
76 | overhang_fix_depth = 0.3; // assume this is enough
77 |
78 | tz(env_pitch().z*num_z-fudgeFactor*2)
79 | if(filledin == "enabledfilllip"){
80 | color(env_colour(color_topcavity))
81 | tz(-fudgeFactor)
82 | hull()
83 | cornercopy(block_corner_position, num_x, num_y)
84 | cylinder(r=gf_cup_corner_radius, h=lipHeight);
85 | } else {
86 |
87 | cupLip(
88 | num_x = num_x,
89 | num_y = num_y,
90 | lipStyle = lip_settings[iLipStyle],
91 | wall_thickness = wall_thickness,
92 | lip_notches = lip_settings[iLipNotch],
93 | lip_top_relief_height = lip_settings[iLipTopReliefHeight],
94 | lip_top_relief_width = lip_settings[iLipTopReliefWidth],
95 | lip_clip_position = lip_settings[iLipClipPosition],
96 | lip_non_blocking = lip_settings[iLipNonBlocking]);
97 | }
98 |
99 | translate(gridfinityRenderPosition(position,num_x,num_y))
100 | difference() {
101 | baseHeight = 5;
102 | intersection() {
103 | //Main cup outer shape
104 | color(env_colour(color_cup))
105 | tz(-fudgeFactor)
106 | hull()
107 | cornercopy(block_corner_position, num_x, num_y)
108 | cylinder(r=gf_cup_corner_radius, h=env_pitch().z*num_z);
109 |
110 | union(){
111 | if(flat_base == FlatBase_rounded){
112 | basebottomRadius = let(fbr = cupBase_settings[iCupBase_FlatBaseRoundedRadius])
113 | fbr == -1 ? gf_cup_corner_radius/2 : fbr;
114 |
115 | color(env_colour(color_cup))
116 | translate([0.25,0.25,0])
117 | roundedCube(
118 | x = num_x*env_pitch().x-0.5,
119 | y = num_y*env_pitch().y-0.5,
120 | z = env_pitch().z,
121 | size=[],
122 | topRadius = 0,
123 | bottomRadius = basebottomRadius,
124 | sideRadius = gf_cup_corner_radius,
125 | centerxy = false,
126 | supportReduction_z = [cupBase_settings[iCupBase_FlatBaseRoundedEasyPrint],0]
127 | );
128 | } else {
129 | // logic for constructing odd-size grids of possibly half-pitch pads
130 | color(env_colour(color_base))
131 | pad_grid(num_x, num_y, half_pitch, flat_base = flat_base, cupBase_settings[iCupBase_MinimumPrintablePadSize]);
132 | }
133 |
134 | color(env_colour(color_cup))
135 | tz(baseHeight)
136 | cube([env_pitch().x*num_x, env_pitch().y*num_y, env_pitch().z*num_z]);
137 | }
138 | }
139 |
140 | if(center_magnet_size[iCylinderDimension_Diameter]){
141 | //Center Magnet
142 | for(x =[0:1:num_x-1])
143 | {
144 | for(y =[0:1:num_y-1])
145 | {
146 | color(env_colour(color_basehole))
147 | translate([
148 | x * env_pitch().x + env_pitch().x / 2, // Add half pitch in X
149 | y * env_pitch().y + env_pitch().y / 2, // Add half pitch in Y
150 | -fudgeFactor
151 | ])
152 | cylinder(h=center_magnet_size[iCylinderDimension_Height]-fudgeFactor, d=center_magnet_size[iCylinderDimension_Diameter]);
153 | }
154 | }
155 | }
156 |
157 | color(env_colour(color_basehole))
158 | tz(-fudgeFactor)
159 | gridcopycorners(num_x, num_y, magnet_position, box_corner_attachments_only){
160 | rdeg =
161 | $gcci[2] == [ 1, 1] ? 90 :
162 | $gcci[2] == [-1, 1] ? 180 :
163 | $gcci[2] == [-1,-1] ? -90 :
164 | $gcci[2] == [ 1,-1] ? 0 : 0;
165 | rotate([0,0,rdeg-45+(magnet_easy_release==MagnetEasyRelease_outer ? 0 : 180)])
166 | MagnetAndScrewRecess(
167 | magnetDiameter = magnet_size[iCylinderDimension_Diameter],
168 | magnetThickness = magnet_size[iCylinderDimension_Height]+0.1,
169 | screwDiameter = screw_size[iCylinderDimension_Diameter],
170 | screwDepth = screw_size[iCylinderDimension_Height],
171 | overhangFixLayers = overhang_fix,
172 | overhangFixDepth = overhang_fix_depth,
173 | easyMagnetRelease = magnet_easy_release != MagnetEasyRelease_off);
174 | }
175 | }
176 |
177 | HelpTxt("grid_block",[
178 | "num_x",num_x
179 | ,"num_y",num_y
180 | ,"num_z",num_z
181 | ,"magnet_size",magnet_size
182 | ,"screw_size",screw_size
183 | ,"position",position
184 | ,"hole_overhang_remedy",hole_overhang_remedy
185 | ,"half_pitch",half_pitch
186 | ,"box_corner_attachments_only",box_corner_attachments_only
187 | ,"flat_base",flat_base
188 | ,"lipSettings",lip_settings
189 | ,"filledin",filledin]
190 | ,help);
191 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_cup_base.scad:
--------------------------------------------------------------------------------
1 | include
2 |
3 | /* [Base]
4 | // (Zack's design uses magnet diameter of 6.5)
5 | magnet_diameter = 0; // .1
6 | //create relief for magnet removal
7 | magnet_easy_release = "auto";//["off","auto","inner","outer"]
8 | // (Zack's design uses depth of 6)
9 | screw_depth = 0;
10 | center_magnet_diameter =0;
11 | center_magnet_thickness = 0;
12 | // Sequential Bridging hole overhang remedy is active only when both screws and magnets are nonzero (and this option is selected)
13 | hole_overhang_remedy = 2;
14 | //Only add attachments (magnets and screw) to box corners (prints faster).
15 | box_corner_attachments_only = true;
16 | // Minimum thickness above cutouts in base (Zack's design is effectively 1.2)
17 | floor_thickness = 0.7;
18 | cavity_floor_radius = -1;// .1
19 | // Efficient floor option saves material and time, but the internal floor is not flat
20 | efficient_floor = "off";//[off,on,rounded,smooth]
21 | // Enable to subdivide bottom pads to allow half-cell offsets
22 | half_pitch = false;
23 | // Removes the internal grid from base the shape
24 | flat_base = false;
25 | // Remove floor to create a vertical spacer
26 | spacer = false;
27 | */
28 |
29 | iCupBase_MagnetSize=0;
30 | iCupBase_MagnetEasyRelease=1;
31 | iCupBase_CenterMagnetSize=2;
32 | iCupBase_ScrewSize=3;
33 | iCupBase_HoleOverhangRemedy=4;
34 | iCupBase_CornerAttachmentsOnly=5;
35 | iCupBase_FloorThickness=6;
36 | iCupBase_CavityFloorRadius=7;
37 | iCupBase_EfficientFloor=8;
38 | iCupBase_HalfPitch=9;
39 | iCupBase_FlatBase=10;
40 | iCupBase_Spacer=11;
41 | iCupBase_MinimumPrintablePadSize=12;
42 | iCupBase_FlatBaseRoundedRadius=13;
43 | iCupBase_FlatBaseRoundedEasyPrint=14;
44 |
45 | iCylinderDimension_Diameter=0;
46 | iCylinderDimension_Height=1;
47 |
48 | MagnetEasyRelease_off = "off";
49 | MagnetEasyRelease_auto = "auto";
50 | MagnetEasyRelease_inner = "inner";
51 | MagnetEasyRelease_outer = "outer";
52 | MagnetEasyRelease_values = [MagnetEasyRelease_off, MagnetEasyRelease_auto, MagnetEasyRelease_inner, MagnetEasyRelease_outer];
53 | function validateMagnetEasyRelease(value, efficientFloorValue) =
54 | //Convert boolean to list value
55 | let(value = is_bool(value) ? value ? MagnetEasyRelease_auto : MagnetEasyRelease_off : value,
56 | autoValue = value == MagnetEasyRelease_auto
57 | ? efficientFloorValue == EfficientFloor_off ? MagnetEasyRelease_inner : MagnetEasyRelease_outer
58 | : value)
59 | assert(list_contains(MagnetEasyRelease_values, autoValue), typeerror("MagnetEasyRelease", autoValue))
60 | autoValue;
61 |
62 | EfficientFloor_off = "off";
63 | EfficientFloor_on = "on";
64 | EfficientFloor_rounded = "rounded";
65 | EfficientFloor_smooth = "smooth";
66 |
67 | EfficientFloor_values = [EfficientFloor_off, EfficientFloor_on, EfficientFloor_rounded, EfficientFloor_smooth];
68 | function validateEfficientFloor(value) =
69 | //Convert boolean to list value
70 | let(value = is_bool(value) ? value ? EfficientFloor_on : EfficientFloor_off : value)
71 | assert(list_contains(EfficientFloor_values, value), typeerror("EfficientFloor", value))
72 | value;
73 |
74 | FlatBase_off = "off";
75 | FlatBase_gridfinity = "gridfinity";
76 | FlatBase_rounded = "rounded";
77 |
78 | FlatBase_values = [FlatBase_off, FlatBase_gridfinity, FlatBase_rounded];
79 | function validateFlatBase(value) =
80 | //Convert boolean to list value
81 | let(value = is_bool(value) ? value ? FlatBase_gridfinity : FlatBase_off : value)
82 | assert(list_contains(FlatBase_values, value), typeerror("FlatBase", value))
83 | value;
84 |
85 | function CupBaseSettings(
86 | magnetSize = [0,0],
87 | magnetEasyRelease = MagnetEasyRelease_auto,
88 | centerMagnetSize = [0,0],
89 | screwSize = [0,0],
90 | holeOverhangRemedy = 2,
91 | cornerAttachmentsOnly = true,
92 | floorThickness = gf_cup_floor_thickness,
93 | cavityFloorRadius = -1,
94 | efficientFloor = EfficientFloor_off,
95 | halfPitch = false,
96 | flatBase = FlatBase_off,
97 | spacer = false,
98 | minimumPrintablePadSize = 0,
99 | flatBaseRoundedRadius=-1,
100 | flatBaseRoundedEasyPrint=-1
101 | ) =
102 | let(
103 | magnetSize =
104 | is_num(magnetSize)
105 | ? [magnetSize, gf_magnet_thickness]
106 | : magnetSize,
107 | screwSize =
108 | is_num(screwSize)
109 | ? [gf_cupbase_screw_diameter, screwSize]
110 | : screwSize,
111 |
112 | efficientFloor = validateEfficientFloor(efficientFloor),
113 | centerMagnetSize = efficientFloor != EfficientFloor_off ? [0, 0] : centerMagnetSize,
114 | cavityFloorRadius = efficientFloor != EfficientFloor_off ? 0 : cavityFloorRadius,
115 | magnetEasyRelease = validateMagnetEasyRelease(magnetEasyRelease, efficientFloor),
116 | result = [
117 | magnetSize[0] == 0 || magnetSize[1] == 0 ? [0,0] : magnetSize,
118 | validateMagnetEasyRelease(magnetEasyRelease),
119 | centerMagnetSize[0] == 0 || centerMagnetSize[1] == 0 ? [0,0] : centerMagnetSize,
120 | screwSize[0] == 0 || screwSize[1] == 0 ? [0,0] : screwSize,
121 | holeOverhangRemedy,
122 | cornerAttachmentsOnly,
123 | floorThickness,
124 | cavityFloorRadius,
125 | validateEfficientFloor(efficientFloor),
126 | halfPitch,
127 | validateFlatBase(flatBase),
128 | spacer,
129 | minimumPrintablePadSize,
130 | flatBaseRoundedRadius,
131 | flatBaseRoundedEasyPrint
132 | ],
133 | validatedResult = ValidateCupBaseSettings(result)
134 | ) validatedResult;
135 |
136 | function ValidateCupBaseSettings(settings, num_x, num_y) =
137 | assert(is_list(settings) && len(settings) == 15, typeerror_list("CupBase Settings", settings, 15))
138 | assert(is_list(settings[iCupBase_MagnetSize]) && len(settings[iCupBase_MagnetSize])==2, "CupBase Magnet Setting must be a list of length 2")
139 | assert(is_list(settings[iCupBase_CenterMagnetSize]) && len(settings[iCupBase_CenterMagnetSize])==2, "CenterMagnet Magnet Setting must be a list of length 2")
140 | assert(is_list(settings[iCupBase_ScrewSize]) && len(settings[iCupBase_ScrewSize])==2, "ScrewSize Magnet Setting must be a list of length 2")
141 | assert(is_num(settings[iCupBase_HoleOverhangRemedy]), "CupBase HoleOverhangRemedy Settings must be a number")
142 | assert(is_bool(settings[iCupBase_CornerAttachmentsOnly]), "CupBase CornerAttachmentsOnly Settings must be a boolean")
143 | assert(is_num(settings[iCupBase_FloorThickness]), "CupBase FloorThickness Settings must be a number")
144 | assert(is_num(settings[iCupBase_CavityFloorRadius]), "CupBase CavityFloorRadius Settings must be a number")
145 | assert(is_bool(settings[iCupBase_HalfPitch]), "CupBase HalfPitch Settings must be a boolean")
146 | assert(is_string(settings[iCupBase_FlatBase]), "CupBase FlatBase Settings must be a string")
147 | assert(is_bool(settings[iCupBase_Spacer]), "CupBase Spacer Settings must be a boolean")
148 | assert(is_num(settings[iCupBase_MinimumPrintablePadSize]), "CupBase minimumPrintablePadSize Settings must be a number")
149 |
150 | let(
151 | efficientFloor = validateEfficientFloor(settings[iCupBase_EfficientFloor]),
152 | magnetEasyRelease = validateMagnetEasyRelease(settings[iCupBase_MagnetEasyRelease], efficientFloor),
153 | flatBase = validateFlatBase(settings[iCupBase_FlatBase])
154 | ) [
155 | settings[iCupBase_MagnetSize],
156 | magnetEasyRelease,
157 | settings[iCupBase_CenterMagnetSize],
158 | settings[iCupBase_ScrewSize],
159 | settings[iCupBase_HoleOverhangRemedy],
160 | settings[iCupBase_CornerAttachmentsOnly],
161 | settings[iCupBase_FloorThickness],
162 | settings[iCupBase_CavityFloorRadius],
163 | efficientFloor,
164 | settings[iCupBase_HalfPitch],
165 | flatBase,
166 | settings[iCupBase_Spacer],
167 | settings[iCupBase_MinimumPrintablePadSize],
168 | settings[iCupBase_FlatBaseRoundedRadius],
169 | settings[iCupBase_FlatBaseRoundedEasyPrint]
170 | ];
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_cup_base_text.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 |
4 | iCupBaseTextLine1Enabled = 0;
5 | iCupBaseTextLine2Enabled = 1;
6 | iCupBaseTextLine1Value = 2;
7 | iCupBaseTextLine2Value = 3;
8 | iCupBaseTextFontSize = 4;
9 | iCupBaseTextFont = 5;
10 | iCupBaseTextDepth = 6;
11 |
12 | function CupBaseTextSettings(
13 | baseTextLine1Enabled,
14 | baseTextLine2Enabled,
15 | baseTextLine1Value,
16 | baseTextLine2Value,
17 | baseTextFontSize,
18 | baseTextFont,
19 | baseTextDepth) =
20 | [baseTextLine1Enabled,
21 | baseTextLine2Enabled,
22 | baseTextLine1Value,
23 | baseTextLine2Value,
24 | baseTextFontSize,
25 | baseTextFont,
26 | baseTextDepth];
27 |
28 | module AssertCupBaseTextSettings(settings){
29 | assert(is_list(settings), "BaseText Settings must be a list")
30 | assert(len(settings)==7, "BaseText Settings must length 7");
31 | }
32 |
33 | // add text to the bottom
34 | module cup_base_text(
35 | cupBaseTextSettings,
36 | wall_thickness = 1.2,
37 | base_clearance = 4,
38 | magnet_position = 0){
39 |
40 | maxTextWidth = 30;
41 | maxTextSize= 10;
42 | if(env_help_enabled("trace")) echo("cup_base_text", magnet_position=magnet_position);
43 | AssertCupBaseTextSettings(cupBaseTextSettings);
44 | text_line1_enabled = cupBaseTextSettings[iCupBaseTextLine1Enabled];
45 | text_line2_enabled = cupBaseTextSettings[iCupBaseTextLine2Enabled];
46 | text_line1_value = cupBaseTextSettings[iCupBaseTextLine1Value];
47 | text_line2_value = cupBaseTextSettings[iCupBaseTextLine2Value];
48 | text_size = cupBaseTextSettings[iCupBaseTextFontSize];
49 | text_font = cupBaseTextSettings[iCupBaseTextFont];
50 | text_depth = cupBaseTextSettings[iCupBaseTextDepth];
51 |
52 | _text_x = wall_thickness + max(base_clearance, magnet_position * 1/3);
53 | _text_1_y = max(base_clearance, magnet_position * 1/3);
54 |
55 | _text_1_text =
56 | is_string(text_line1_value) && text_line1_value!=""
57 | ? text_line1_value
58 | : str(
59 | str($num_x),
60 | " x ",
61 | str($num_y),
62 | " x ",
63 | str($num_z));
64 |
65 | _text_1_size = text_size > 0 ? text_size :
66 | let(sample_text_1_width = textmetrics(text=_text_1_text, size=5, font=text_font).size.x)
67 | 5*maxTextWidth/sample_text_1_width;
68 |
69 | if (text_line1_enabled) {
70 | color(env_colour(color_wallcutout))
71 | translate([
72 | _text_x,
73 | _text_1_y,
74 | -1 * text_depth
75 | ])
76 | linear_extrude(height = text_depth * 2) {
77 | rotate(a = [0, 180, 180])
78 | text(
79 | text = _text_1_text,
80 | size = _text_1_size,
81 | font = text_font,
82 | halign = "left",
83 | valign = "top"
84 | );
85 | }
86 | }
87 |
88 | if (text_line2_enabled) {
89 | _text_2_size = text_size > 0 ? text_size :
90 | let(sample_text_2_width = textmetrics(text=text_line2_value, size=5, font=text_font).size.x)
91 | min(5*maxTextWidth/sample_text_2_width, maxTextSize);
92 |
93 | _text_2_y = _text_1_y + _text_1_size + min(_text_1_size * 0.25, 3);
94 |
95 | color(env_colour(color_wallcutout))
96 | translate([
97 | _text_x,
98 | _text_2_y,
99 | -1 * text_depth
100 | ])
101 | linear_extrude(height = text_depth * 2) {
102 | rotate(a = [0, 180, 180])
103 | text(
104 | text = text_line2_value,
105 | size = _text_2_size,
106 | font = text_font,
107 | halign = "left",
108 | valign = "top"
109 | );
110 | }
111 | }
112 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_efficient_floor.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 | include
4 | use
5 | use
6 |
7 | //creates the gird of efficient floor pads to be added to the cavity for removal from the overall filled in bin.
8 | module efficient_floor_grid(
9 | num_x, num_y,
10 | floorStyle = "on",
11 | half_pitch=false,
12 | flat_base="off",
13 | floor_thickness,
14 | efficientFloorGridHeight=0,
15 | margins=0) {
16 | if (flat_base != FlatBase_off) {
17 | EfficientFloor(num_x, num_y,
18 | floor_thickness,
19 | margins,
20 | floorRounded=(floorStyle == "rounded"),
21 | floorSmooth=(floorStyle == "smooth" ? 2 : 0),
22 | efficientFloorGridHeight=efficientFloorGridHeight);
23 | }
24 | else if (half_pitch) {
25 | gridcopy(ceil(num_x*2), ceil(num_y*2), env_pitch()/2) {
26 | EfficientFloor(
27 | ($gci.x == ceil(num_x*2)-1 ? (num_x*2-$gci.x)/2 : 0.5),
28 | ($gci.y == ceil(num_y*2)-1 ? (num_y*2-$gci.y)/2 : 0.5),
29 | floor_thickness,
30 | margins,
31 | floorRounded=(floorStyle == "rounded"),
32 | floorSmooth=(floorStyle == "smooth" ? 1 : 0),
33 | efficientFloorGridHeight=efficientFloorGridHeight);
34 | }
35 | }
36 | else {
37 | gridcopy(ceil(num_x), ceil(num_y)) {
38 | EfficientFloor(
39 | //Calculate pad size, last cells might not be 100%
40 | ($gci.x == ceil(num_x)-1 ? num_x-$gci.x : 1),
41 | ($gci.y == ceil(num_y)-1 ? num_y-$gci.y : 1),
42 | floor_thickness,
43 | margins,
44 | floorRounded=(floorStyle == "rounded"),
45 | floorSmooth=(floorStyle == "smooth" ? 1 :0),
46 | efficientFloorGridHeight=efficientFloorGridHeight);
47 | }
48 | }
49 | }
50 |
51 | module EfficientFloorAttachmentCaps(
52 | grid_copy_corner_index,
53 | floor_thickness,
54 | magnet_size,
55 | screw_size,
56 | //cornerRadius,
57 | wall_thickness)
58 | {
59 | assert(is_list(grid_copy_corner_index) && len(grid_copy_corner_index) >= 3, "grid_copy_corner_index must be a list of length > 3");
60 | assert(is_num(floor_thickness));
61 | assert(is_list(magnet_size));
62 | assert(is_list(screw_size));
63 | assert(is_num(wall_thickness));
64 |
65 | fudgeFactor = 0.01;
66 | magnetPosition = calculateAttachmentPositions(magnet_size[iCylinderDimension_Diameter], screw_size[iCylinderDimension_Diameter]);
67 | blockSize = [env_pitch().x/2-magnetPosition.x+wall_thickness,env_pitch().y/2-magnetPosition.y+wall_thickness];
68 |
69 | //$gcci=[trans,xi,yi,xx,yy];
70 | rotate( grid_copy_corner_index[2] == [ 1, 1] ? [0,0,270]
71 | : grid_copy_corner_index[2] == [ 1,-1] ? [0,0,180]
72 | : grid_copy_corner_index[2] == [-1,-1] ? [0,0,90]
73 | : [0,0,0])
74 | tz(floor_thickness-fudgeFactor)
75 | hull(){
76 | if(screw_size[iCylinderDimension_Diameter] > 0){
77 | cornerRadius = screw_size[iCylinderDimension_Diameter]/2+wall_thickness*2;
78 | rotate([0,0,90])
79 | translate([-cornerRadius,-cornerRadius,0])
80 | CubeWithRoundedCorner(
81 | size=[blockSize.x+cornerRadius, blockSize.y+cornerRadius, screw_size[iCylinderDimension_Height]],
82 | cornerRadius = cornerRadius,
83 | edgeRadius = wall_thickness);
84 | }
85 | if(magnet_size[iCylinderDimension_Diameter] > 0){
86 | cornerRadius = magnet_size[iCylinderDimension_Diameter]/2+wall_thickness*2;
87 | rotate([0,0,90])
88 | translate([-cornerRadius,-cornerRadius,0])
89 | CubeWithRoundedCorner(
90 | size=[blockSize.x+cornerRadius, blockSize.y+cornerRadius, magnet_size[iCylinderDimension_Height]],
91 | cornerRadius = cornerRadius,
92 | edgeRadius = wall_thickness);
93 | }
94 | }
95 | }
96 | //Creates the efficient floor pad that will be removed from the floor
97 | module EfficientFloor(
98 | num_x=1,
99 | num_y=1,
100 | floor_thickness,
101 | margins=0,
102 | floorRounded = true,
103 | floorSmooth = 0,
104 | efficientFloorGridHeight=0){
105 | fudgeFactor = 0.01;
106 | floorRadius=floorRounded ? 1 : 0;
107 |
108 | seventeen = [env_pitch().x/2-4, env_pitch().y/2-4];
109 | minEfficientPadSize = floorSmooth ? 0.3 : 0.15;
110 |
111 | cornerRadius = 1.15+margins;
112 | topChampherCornerRadius = cornerRadius;
113 |
114 | smoothVersion=2;
115 | //Less than minEfficientPadSize is to small and glitches the cut away
116 | if(num_x > minEfficientPadSize && num_y > minEfficientPadSize )
117 | if(floorSmooth == 2) {
118 | //Smooth floor that does not round over the divider walls
119 |
120 | // tapered top portion
121 | union() {
122 | tz(5+floor_thickness-fudgeFactor)
123 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen)
124 | cylinder(r=cornerRadius, h=4);
125 |
126 | // tapered top portion
127 | tz(floor_thickness+cornerRadius)
128 | hull() {
129 | cornercopy(num_x=num_x, num_y=num_y, r=[seventeen.x-cornerRadius,seventeen.y-cornerRadius])
130 | sphere(r=cornerRadius);
131 |
132 | tz(2.5)
133 | union(){
134 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen)
135 | cylinder(r=cornerRadius, h=cornerRadius);
136 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen)
137 | sphere(r=cornerRadius);
138 | }
139 | }
140 | }
141 | } else if(floorSmooth == 1) {
142 | //Smooth floor that rounds over the divider walls
143 | union() {
144 | wallStartHeight = 5;
145 | topSmoothTransition = efficientFloorGridHeight-wallStartHeight;
146 | wallTaper = topSmoothTransition/2;
147 |
148 | // tapered top portion
149 | topChampherRadius = topSmoothTransition/2;
150 | topChampherZBottom = wallStartHeight+wallTaper;
151 | translate([
152 | env_pitch().x/2*num_x,
153 | env_pitch().y/2*num_y,
154 | topChampherZBottom])
155 | roundedNegativeChampher(
156 | champherRadius = topChampherRadius,
157 | size=[
158 | (seventeen.x*2+(topChampherCornerRadius)*2+env_pitch().x*(num_x-1)),
159 | (seventeen.y*2+(topChampherCornerRadius)*2+env_pitch().y*(num_y-1))],
160 | cornerRadius = topChampherCornerRadius,
161 | champher = true,
162 | height = 4);
163 | // tapered top portion
164 | //wallTaper;
165 | hull() {
166 | //Bottom layer
167 | tz(floor_thickness+cornerRadius)
168 | cornercopy(num_x=num_x, num_y=num_y, r=[seventeen.x-cornerRadius,seventeen.y-cornerRadius])
169 | sphere(r=cornerRadius);
170 |
171 | //Top Layer
172 | tz(wallStartHeight)
173 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen)
174 | roundedCylinder(
175 | h=cornerRadius,
176 | r=cornerRadius,
177 | roundedr1=wallTaper);
178 | }
179 | }
180 | } else {
181 | //Efficient floor
182 | taperTopPos = 5-(+2.5-1.15-margins);
183 | union(){
184 | // establishes floor
185 | tz(floor_thickness)
186 | hull(){
187 | cornercopy(num_x=num_x, num_y=num_y, r=[seventeen.x-0.5,seventeen.y-0.5])
188 | roundedCylinder(
189 | h=3,
190 | r=1,
191 | roundedr1=floorRadius);
192 | }
193 |
194 | // tapered top portion
195 | hull() {
196 | /*tz(3)
197 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen-0.5)
198 | cylinder(r=1, h=1);*/
199 |
200 | //Not sure why this was changed to a sphere
201 | tz(3+1/2)
202 | cornercopy(num_x=num_x, num_y=num_y, r=[seventeen.x-0.5,seventeen.y-0.5])
203 | sphere(r=1);
204 |
205 | tz(taperTopPos)
206 | cornercopy(num_x=num_x, num_y=num_y, r=seventeen)
207 | cylinder(r=cornerRadius, h=4);
208 | }
209 |
210 | tz(taperTopPos)
211 | if(floorRounded){
212 | maxRoundOver = 1.25;
213 | champherRadius = min(efficientFloorGridHeight-taperTopPos,maxRoundOver);
214 |
215 | translate([
216 | env_pitch().x/2,
217 | env_pitch().y/2,
218 | efficientFloorGridHeight-taperTopPos > maxRoundOver ? efficientFloorGridHeight-taperTopPos-champherRadius : 0])
219 | roundedNegativeChampher(
220 | champherRadius = champherRadius,
221 | size=[
222 | (seventeen.x*2+(topChampherCornerRadius)*2+env_pitch().x*(num_x-1)),
223 | (seventeen.y*2+(topChampherCornerRadius)*2+env_pitch().y*(num_y-1))],
224 | cornerRadius = cornerRadius,
225 | height = 4);
226 | }
227 | }
228 | }
229 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_lid.scad:
--------------------------------------------------------------------------------
1 | // include instead of use, so we get the pitch
2 | include
3 | use
4 | use
5 | use
6 |
7 | /* [Lid] */
8 | // Plate Style
9 | Default_Lid_Options = "default";//[default, flat:Flat Removes the internal grid from base, halfpitch: halfpitch base, efficient]
10 | Default_Oversize_method = "fill"; //[crop, fill]
11 |
12 | Default_Lid_Include_Magnets = true;
13 | // Base height, when the bin on top will sit, in GF units
14 | Default_Lid_Efficient_Base_Height = 0.4;// [0.4:0.1:1]
15 | // Thickness of the efficient floor
16 | Default_Lid_Efficient_Floor_Thickness = 0.7;// [0.7:0.1:7]
17 |
18 | // Magnet
19 | // Enable magnets
20 | Default_Enable_Magnets = true;
21 | //size of magnet, diameter and height. Zacks original used 6.5 and 2.4
22 | Default_Magnet_Size = [6.5, 2.4]; // .1
23 |
24 | module gridfinity_lid(
25 | num_x = 2,
26 | num_y = 3,
27 | center_fill_grid_x = false,
28 | center_fill_grid_y = false,
29 | magnetSize = Default_Magnet_Size,
30 | plateStyle = "lid",
31 | reducedWallHeight = -1,
32 | lidOptions = Default_Lid_Options,
33 | lidIncludeMagnets = Default_Lid_Include_Magnets,
34 | lidEfficientFloorThickness =Default_Lid_Efficient_Floor_Thickness,
35 | lidEfficientBaseHeight = Default_Lid_Efficient_Base_Height)
36 | {
37 | assert_openscad_version();
38 |
39 | union(){
40 | if (plateStyle == "lid_legacy") {
41 | base_lid(
42 | num_x=num_x,
43 | num_y=num_y,
44 | lidOptions=lidOptions,
45 | lidIncludeMagnets = lidIncludeMagnets,
46 | lidEfficientFloorThickness = lidEfficientFloorThickness,
47 | lidEfficientBaseHeight = lidEfficientBaseHeight);
48 | } else{
49 | baseplate_lid(
50 | num_x=num_x,
51 | num_y=num_y,
52 | lidOptions=lidOptions,
53 | magnetSize = magnetSize,
54 | reducedWallHeight=reducedWallHeight,
55 | lidIncludeMagnets = lidIncludeMagnets,
56 | lidEfficientFloorThickness = lidEfficientFloorThickness,
57 | lidEfficientBaseHeight = lidEfficientBaseHeight);
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/src/modules/module_gridfinity_sliding_lid.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 | include
4 |
5 | iSlidingLidEnabled=0;
6 | iSlidingLidThickness=1;
7 | iSlidingLidMinWallThickness=2;
8 | iSlidingLidMinSupport=3;
9 | iSlidingClearance=4;
10 | slidingLidLipEnabled=5;
11 |
12 | function SlidingLidSettings(
13 | slidingLidEnabled,
14 | slidingLidThickness,
15 | slidingMinWallThickness,
16 | slidingMinSupport,
17 | slidingClearance,
18 | wallThickness,
19 | slidingLidLipEnabled = false) =
20 | let(
21 | thickness = slidingLidThickness > 0 ? slidingLidThickness : wallThickness*2,
22 | minWallThickness = slidingMinWallThickness > 0 ? slidingMinWallThickness : wallThickness/2,
23 | minSupport = slidingMinSupport > 0 ? slidingMinSupport : thickness/2
24 | ) [
25 | slidingLidEnabled,
26 | thickness,
27 | minWallThickness,
28 | minSupport,
29 | slidingClearance,
30 | slidingLidLipEnabled];
31 |
32 | module AssertSlidingLidSettings(settings){
33 | assert(is_list(settings), "SlidingLid Settings must be a list")
34 | assert(len(settings)==6, "SlidingLid Settings must length 5");
35 | }
36 |
37 | //SlidingLid(4,3,.8,0.1,1.6,0.8,0.4,true, true, [-2,-2],5,[0,0]);
38 |
39 | module SlidingLid(
40 | num_x,
41 | num_y,
42 | wall_thickness,
43 | clearance = 0,
44 | lidThickness,
45 | lidMinSupport,
46 | lidMinWallThickness,
47 | limitHeight = false,
48 | lipStyle = "normal",
49 | lip_notches = true,
50 | lip_top_relief_height = -1,
51 | addLiptoLid = true,
52 | cutoutEnabled = false,
53 | cutoutSize = [0,0],
54 | cutoutRadius = 0,
55 | cutoutPosition = [0,0]
56 | ){
57 | assert(is_num(num_x));
58 | assert(is_num(num_y));
59 | assert(is_num(wall_thickness));
60 | assert(is_num(clearance));
61 | assert(is_num(lidThickness));
62 | assert(is_num(lidMinSupport));
63 | assert(is_num(lidMinWallThickness));
64 | assert(is_bool(limitHeight));
65 | assert(is_string(lipStyle));
66 | assert(is_bool(lip_notches));
67 | assert(is_num(lip_top_relief_height));
68 | assert(is_bool(addLiptoLid));
69 | assert(is_bool(cutoutEnabled));
70 | assert(is_list(cutoutSize));
71 | assert(is_num(cutoutRadius));
72 | assert(is_list(cutoutPosition));
73 |
74 | innerWallRadius = gf_cup_corner_radius-wall_thickness-clearance;
75 | seventeen = [env_pitch().x/2-4,env_pitch().y/2-4];
76 |
77 | lidSize = [num_x*env_pitch().x-lidMinWallThickness, num_y*env_pitch().y-lidMinWallThickness];
78 |
79 | lidLowerRadius = innerWallRadius+lidMinWallThickness;
80 | lidUpperRadius = limitHeight ? lidLowerRadius-lidThickness/2 : fudgeFactor;
81 | height = limitHeight ? lidThickness : innerWallRadius+lidMinWallThickness-fudgeFactor;
82 | difference()
83 | {
84 | union(){
85 | if(addLiptoLid)
86 | color(env_colour(color_topcavity, isLip = true))
87 | difference(){
88 | translate([0,0,lidThickness-fudgeFactor*3])
89 | cupLip(
90 | num_x = num_x,
91 | num_y = num_y,
92 | lipStyle = lipStyle,
93 | lip_notches = lip_notches,
94 | lip_top_relief_height = lip_top_relief_height,
95 | wall_thickness = 1.2);
96 | translate([0,lidLowerRadius,lidThickness-fudgeFactor*4])
97 | cube([num_x*env_pitch().x,num_y*env_pitch().y,4+fudgeFactor*2]);
98 | }
99 |
100 | color(env_colour(color_lid))
101 | union(){
102 | hull()
103 | cornercopy(seventeen, num_x, num_y){
104 | tz(lidThickness-lidMinSupport)
105 | cylinder(
106 | r1=innerWallRadius+lidMinWallThickness,
107 | r2=lidUpperRadius,
108 | h=limitHeight ? lidThickness/2 : innerWallRadius+lidMinWallThickness-fudgeFactor);
109 | cylinder(r=lidLowerRadius, h=lidThickness/2);
110 | }
111 | if(addLiptoLid)
112 | difference(){
113 | hull()
114 | cornercopy(seventeen, num_x, num_y){
115 | cylinder(r=gf_cup_corner_radius, h=lidThickness);
116 | }
117 | translate([-fudgeFactor,lidLowerRadius,-fudgeFactor])
118 | cube([num_x*env_pitch().x+fudgeFactor*2,num_y*env_pitch().y+fudgeFactor,lidThickness+fudgeFactor*2]);
119 | }
120 | }
121 | }
122 |
123 | if(env_help_enabled("debug")) echo("SlidingLid", cutoutSize=cutoutSize, cutoutRadius=cutoutRadius );
124 | if(cutoutSize.x != 0 && cutoutSize.y != 0 && cutoutRadius>0){
125 |
126 | cSize = [
127 | cutoutSize.x<0
128 | ? lidSize.x/abs(cutoutSize.x)
129 | : cutoutSize.x,
130 | cutoutSize.y<0
131 | ? lidSize.y/abs(cutoutSize.y)
132 | : cutoutSize.y
133 | ];
134 | cRadius = min(cSize.x/2,cSize.y/2,cutoutRadius);
135 | positions = [
136 | [-cSize.x/2+cRadius, -cSize.y/2+cRadius],
137 | [-cSize.x/2+cRadius, cSize.y/2-cRadius],
138 | [cSize.x/2-cRadius, cSize.y/2-cRadius],
139 | [cSize.x/2-cRadius, -cSize.y/2+cRadius]
140 | ];
141 |
142 | translate([cutoutPosition.x,cutoutPosition.y,0])
143 | translate([lidSize.x/2-env_pitch().x/2,lidSize.y/2-env_pitch().y/2,-fudgeFactor])
144 | hull(){
145 | for(i=[0:len(positions)-1]){
146 | translate([positions[i].x,positions[i].y,0])
147 | cylinder(r=cRadius, h=lidThickness+fudgeFactor*2);
148 | }
149 | }
150 | }
151 | }
152 |
153 | if(env_help_enabled("debug")) echo("SlidingLid", num_x=num_x, num_y=num_y, wall_thickness=wall_thickness, clearance=clearance, lidThickness=lidThickness, lidMinSupport=lidMinSupport, lidMinWallThickness=lidMinWallThickness);
154 | if(env_help_enabled("debug")) echo("SlidingLid", cutoutSize=cutoutSize, cutoutRadius=cutoutRadius, cutoutPosition=cutoutPosition);
155 | }
156 |
157 | module SlidingLidSupportMaterial(
158 | num_x,
159 | num_y,
160 | wall_thickness,
161 | sliding_lid_settings,
162 | innerWallRadius,
163 | zpoint){
164 |
165 | seventeen = [env_pitch().x/2-4,env_pitch().y/2-4];
166 |
167 | aboveLipHeight = sliding_lid_settings[iSlidingLidThickness];
168 | belowLedgeHeight = sliding_lid_settings[iSlidingLidThickness]/4;
169 | belowRampHeight = sliding_lid_settings[iSlidingLidMinSupport];
170 |
171 | belowLipHeight = belowLedgeHeight+belowRampHeight;
172 | slidingLidEdge = gf_cup_corner_radius-sliding_lid_settings[iSlidingLidMinWallThickness];
173 |
174 | //Sliding lid lower support lip
175 | tz(zpoint-belowLipHeight)
176 | difference(){
177 | hull()
178 | cornercopy(seventeen, num_x, num_y)
179 | cylinder(r=innerWallRadius, h=belowLipHeight);
180 |
181 | union(){
182 | hull() cornercopy(seventeen, num_x, num_y)
183 | tz(belowRampHeight-fudgeFactor)
184 | cylinder(r=slidingLidEdge-sliding_lid_settings[iSlidingLidMinSupport], h=belowLedgeHeight+fudgeFactor*2);
185 |
186 | hull() cornercopy(seventeen, num_x, num_y)
187 | tz(-fudgeFactor)
188 | cylinder(r1=slidingLidEdge, r2=slidingLidEdge-sliding_lid_settings[iSlidingLidMinSupport], h=belowRampHeight+fudgeFactor);
189 | }
190 | }
191 |
192 | //Sliding lid upper lip
193 | tz(zpoint)
194 | difference(){
195 | hull()
196 | cornercopy(seventeen, num_x, num_y)
197 | tz(fudgeFactor)
198 | cylinder(r=slidingLidEdge, h=aboveLipHeight);
199 | union(){
200 | hull()
201 | cornercopy(seventeen, num_x, num_y)
202 | tz(fudgeFactor)
203 | cylinder(r=slidingLidEdge-sliding_lid_settings[iSlidingLidMinSupport], h=aboveLipHeight+fudgeFactor);
204 |
205 | *SlidingLid(
206 | num_x=num_x,
207 | num_y=num_y,
208 | wall_thickness,
209 | clearance = 0,
210 | slidingLidThickness=sliding_lid_settings[iSlidingLidThickness],
211 | slidingLidMinSupport=sliding_lid_settings[iSlidingLidMinSupport],
212 | slidingLidMinWallThickness=sliding_lid_settings[iSlidingLidMinWallThickness]);
213 | }
214 | }
215 | }
216 |
217 | module SlidingLidCavity(
218 | num_x,
219 | num_y,
220 | wall_thickness,
221 | sliding_lid_settings,
222 | aboveLidHeight
223 | ){
224 | SlidingLid(
225 | num_x=num_x,
226 | num_y=num_y,
227 | wall_thickness,
228 | clearance = 0,
229 | lidThickness=sliding_lid_settings[iSlidingLidThickness],
230 | lidMinSupport=sliding_lid_settings[iSlidingLidMinSupport],
231 | lidMinWallThickness=sliding_lid_settings[iSlidingLidMinWallThickness],
232 | limitHeight = false);
233 |
234 | if(sliding_lid_settings[slidingLidLipEnabled])
235 | {
236 | translate([0,0,0])
237 | cube([num_x*env_pitch().x,gf_cup_corner_radius,aboveLidHeight+fudgeFactor*3]);
238 | } else {
239 | //translate([-env_pitch().x/2,-env_pitch().y/2,zpoint])
240 | //cube([num_x*env_pitch().x,gf_cup_corner_radius,headroom+gf_Lip_Height]);
241 | //innerWallRadius = gf_cup_corner_radius-wall_thickness;
242 | translate([0,gf_cup_corner_radius,aboveLidHeight])
243 | rotate([270,0,0])
244 | chamferedCorner(
245 | cornerRadius = aboveLidHeight/4,
246 | chamferLength = aboveLidHeight,
247 | length=num_x*env_pitch().x,
248 | height = aboveLidHeight,
249 | width = gf_cup_corner_radius);
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/src/modules/module_item_holder_data.scad:
--------------------------------------------------------------------------------
1 | iitemDiameter= 0;
2 | iitemx = 1;
3 | iitemy = 2;
4 | idepthneeded = 3;
5 | iitemHeight = 4;
6 | ishape = 5;
7 |
8 | function LookupKnownShapes(name="round", default_sides=64) =
9 | name == "square" || name == "halfround" || name == "multicard" ? 4 :
10 | name == "hex" ? 6 :
11 | name == "round" ? 64 :
12 | default_sides;
13 |
14 | // result is dimensions for commonly know items
15 | //[diameter, x, y, z, item height, shape]
16 | //x=diameter on round, flat to flat on hex, corner diameter in square.
17 | //x=width for square
18 | //y=length for square
19 | //z=desired depth of hole
20 | //item height=the hight object, not used currently.
21 | //shape=the item shape, (circle, hex, halfround or square)
22 | //[diameter, width, thickness, depthneeded, itemHeight, shape]
23 | function LookupKnownTool(name="custom") =
24 | name == "4hexshank" ? [4, 0, 0, 5, 20, "hex"] :
25 | name == "1/4hexshank" ? [6.35, 0, 0, 8, 15, "hex"] :
26 | name == "1/4hexshanklong" ? [6.35, 0, 0, 15, 40, "hex"] :
27 | name == "5/16hexshank" ? [7.94, 0, 0, 7, 0, "hex"] :
28 | name == "3/8hexshank" ? [9.52, 0, 0, 10, 0, "hex"] :
29 | name == "1/2shank" ? [12.7, 0, 0, 20, 50, "round"] :
30 | name == "12shank" ? [12, 0, 0, 20, 50, "round"] :
31 | name == "10shank" ? [10, 0, 0, 20, 50, "round"] :
32 | name == "3/8shank" ? [9.525, 0, 0, 20, 50, "round"] :
33 | name == "8shank" ? [8, 0, 0, 20, 50, "round"] :
34 | name == "1/4shank" ? [6.35, 0, 0, 20, 50, "round"] :
35 | name == "6shank" ? [6, 0, 0, 15, 40, "round"] :
36 | name == "1/8shank" ? [3.2, 0, 0, 15, 40, "round"] :
37 | [0,0,0,0,0,"","LookupKnownTool"];
38 |
39 | //[diameter, width, thickness, depthneeded, itemHeight, shape]
40 | function LookupKnownBattery(name="custom") =
41 | name == "aaaa" ? [8.3, 0, 0, 10.625, 42.5, "round"] :
42 | name == "aaa" ? [10.5, 0, 0, 11.125, 44.5, "round"] :
43 | name == "aa" ? [14.5, 0, 0, 12.625, 50.5, "round"] :
44 | name == "9v" ? [1, 17.5, 26.5, 12.5, 48.5, "square"] :
45 | name == "c" ? [26.2, 0, 0, 12.5, 50, "round"] :
46 | name == "d" ? [34.2, 0, 0, 15.375, 61.5, "round"] :
47 | name == "7540cell" ? [7.5, 0, 0, 10, 40, "round"] :
48 | name == "8570cell" ? [8.5, 0, 0, 17.5, 70, "round"] :
49 | name == "10180cell" ? [10, 0, 0, 4.5, 18, "round"] :
50 | name == "10280cell" ? [10, 0, 0, 7, 28, "round"] :
51 | name == "10440cell" ? [10, 0, 0, 11, 44, "round"] :
52 | name == "10850cell" ? [10, 0, 0, 21.25, 85, "round"] :
53 | name == "13400cell" ? [13, 0, 0, 10, 40, "round"] :
54 | name == "14250cell" ? [14, 0, 0, 6.25, 25, "round"] :
55 | name == "14300cell" ? [14, 0, 0, 7.5, 30, "round"] :
56 | name == "14430cell" ? [14, 0, 0, 10.75, 43, "round"] :
57 | name == "14500cell" ? [14, 0, 0, 13.25, 53, "round"] :
58 | name == "14650cell" ? [14, 0, 0, 16.25, 65, "round"] :
59 | name == "15270cell" ? [15, 0, 0, 6.75, 27, "round"] :
60 | name == "16340cell" ? [16, 0, 0, 8.5, 34, "round"] :
61 | name == "16650cell" ? [16, 0, 0, 16.25, 65, "round"] :
62 | name == "17500cell" ? [17, 0, 0, 12.5, 50, "round"] :
63 | name == "17650cell" ? [17, 0, 0, 16.25, 65, "round"] :
64 | name == "17670cell" ? [17, 0, 0, 16.75, 67, "round"] :
65 | name == "18350cell" ? [18, 0, 0, 8.75, 35, "round"] :
66 | name == "18490cell" ? [18, 0, 0, 12.25, 49, "round"] :
67 | name == "18500cell" ? [18, 0, 0, 12.5, 50, "round"] :
68 | name == "18650cell" ? [18, 0, 0, 16.25, 65, "round"] :
69 | name == "20700cell" ? [20, 0, 0, 17.5, 70, "round"] :
70 | name == "21700cell" ? [21, 0, 0, 17.5, 70, "round"] :
71 | name == "25500cell" ? [25, 0, 0, 12.5, 50, "round"] :
72 | name == "26500cell" ? [26, 0, 0, 12.5, 50, "round"] :
73 | name == "26650cell" ? [26, 0, 0, 16.25, 65, "round"] :
74 | name == "26700cell" ? [26, 0, 0, 17.5, 70, "round"] :
75 | name == "26800cell" ? [26, 0, 0, 20, 80, "round"] :
76 | name == "32600cell" ? [32, 0, 0, 15, 60, "round"] :
77 | name == "32650cell" ? [32, 0, 0, 16.925, 67.7, "round"] :
78 | name == "32700cell" ? [32, 0, 0, 17.5, 70, "round"] :
79 | name == "38120cell" ? [38, 0, 0, 30, 120, "round"] :
80 | name == "38140cell" ? [38, 0, 0, 35, 140, "round"] :
81 | name == "40152cell" ? [40, 0, 0, 38, 152, "round"] :
82 | name == "4680cell" ? [46, 0, 0, 20, 80, "round"] :
83 | [0,0,0,0,0,"","LookupKnownBattery"];
84 |
85 | //[diameter, width, thickness, depthneeded, itemHeight, shape]
86 | function LookupKnownCellBattery(name="custom") =
87 | name == "cr927" ? [0, 9.5, 2.7, 0, 0, "halfround"] :
88 | name == "cr1025" ? [0, 10, 2.5, 0, 0, "halfround"] :
89 | name == "cr1130" ? [0, 11.5, 3, 0, 0, "halfround"] :
90 | name == "cr1216" ? [0, 12.5, 1.6, 0, 0, "halfround"] :
91 | name == "cr1220" ? [0, 12.5, 2, 0, 0, "halfround"] :
92 | name == "cr1225" ? [0, 12.5, 2.5, 0, 0, "halfround"] :
93 | name == "cr1616" ? [0, 16, 1.6, 0, 0, "halfround"] :
94 | name == "cr1620" ? [0, 16, 2, 0, 0, "halfround"] :
95 | name == "cr1632" ? [0, 16, 3.2, 0, 0, "halfround"] :
96 | name == "cr2012" ? [0, 20, 1.2, 0, 0, "halfround"] :
97 | name == "cr2016" ? [0, 20, 1.6, 0, 0, "halfround"] :
98 | name == "cr2020" ? [0, 20, 2, 0, 0, "halfround"] :
99 | name == "cr2025" ? [0, 20, 2.5, 0, 0, "halfround"] :
100 | name == "cr2032" ? [0, 20, 3.2, 0, 0, "halfround"] :
101 | name == "cr2040" ? [0, 20, 4, 0, 0, "halfround"] :
102 | name == "cr2050" ? [0, 20, 5, 0, 0, "halfround"] :
103 | name == "cr2320" ? [0, 23, 2, 0, 0, "halfround"] :
104 | name == "cr2325" ? [0, 23, 2.5, 0, 0, "halfround"] :
105 | name == "cr2330" ? [0, 23, 3, 0, 0, "halfround"] :
106 | name == "br2335" ? [0, 23, 3.5, 0, 0, "halfround"] :
107 | name == "cr2354" ? [0, 23, 5.4, 0, 0, "halfround"] :
108 | name == "cr2412" ? [0, 24.5, 1.2, 0, 0, "halfround"] :
109 | name == "cr2430" ? [0, 24.5, 3, 0, 0, "halfround"] :
110 | name == "cr2450" ? [0, 24.5, 5, 0, 0, "halfround"] :
111 | name == "cr2477" ? [0, 24.5, 7.7, 0, 0, "halfround"] :
112 | name == "cr3032" ? [0, 30, 3.2, 0, 0, "halfround"] :
113 | name == "cr11108" ? [0, 11.6, 10.8, 0, 0, "halfround"] :
114 | [0,0,0,0,0,"","LookupKnownCellBattery"];
115 |
116 | //[diameter, width, thickness, depthneeded, itemHeight, shape]
117 | function LookupKnownCard(name="custom") =
118 | name == "multicard" ? [0, 0, 0, 0, 0, "multicard"] :
119 | name == "compactflashi" ? [0, 43, 3.3, 9, 36, "square"] :
120 | name == "compactflashii" ? [0, 43, 5, 9, 36, "square"] :
121 | name == "smartmedia" ? [0, 37, 0.76, 11.25, 45, "square"] :
122 | name == "mmc" ? [0, 24, 1.4, 8, 32, "square"] :
123 | name == "mmcmobile" ? [0, 24, 1.4, 4.5, 18, "square"] :
124 | name == "mmcmicro" ? [0, 14, 1.1, 3, 12, "square"] :
125 | name == "sd" ? [0, 24, 2.1, 18, 32, "square"] :
126 | name == "minisd" ? [0, 20, 1.4, 10, 21.5, "square"] :
127 | name == "microsd" ? [0, 11, 0.7, 9, 15, "square"] :
128 | name == "memorystickstandard" ? [0, 21.5, 2.8, 12.5, 50, "square"] :
129 | name == "memorystickduo" ? [0, 20, 1.6, 7.75, 31, "square"] :
130 | name == "memorystickmicro" ? [0, 12.5, 1.2, 3.75, 15, "square"] :
131 | name == "nano" ? [0, 12.3, 0.7, 2.2, 8.8, "square"] :
132 | name == "psvita" ? [0, 15, 1.6, 3.125, 12.5, "square"] :
133 | name == "xqd" ? [0, 38.5, 3.8, 7.45, 29.8, "square"] :
134 | name == "xD" ? [0, 25, 1.78, 5, 20, "square"] :
135 | name == "usba" ? [0, 12, 4.5, 13, 13, "square"] :
136 | name == "usbc" ? [0, 8.5, 4, 10, 0, "square"] :
137 | [0,0,0,0,0,"","LookupKnownCard"];
138 |
139 | //[diameter, width, thickness, depthneeded, itemHeight, shape]
140 | function LookupKnownCartridge(name="custom") =
141 | name == "atari800" ? [0, 68, 21, 19.25, 77, "square"] :
142 | name == "atari2600" ? [0, 81, 19, 21.75, 87, "square"] :
143 | name == "atari5200" ? [0, 104, 20, 28, 112, "square"] :
144 | name == "atari7800" ? [0, 81, 19, 21.75, 87, "square"] :
145 | name == "commodore" ? [0, 79, 17, 34.75, 139, "square"] :
146 | name == "magnavoxodyssey" ? [0, 100, 5.5, 15, 60, "square"] :
147 | name == "magnavoxodysseymulticard" ? [0, 105, 15, 27.5, 110, "square"] :
148 | name == "magnavoxodyssey2" ? [0, 80, 21, 31.75, 127, "square"] :
149 | name == "mattelintellivision" ? [0, 68, 16, 22, 88, "square"] :
150 | name == "nintendofamicom" ? [0, 71, 17, 27, 108, "square"] :
151 | name == "nintendofamicomdisk" ? [0, 76, 4, 22.5, 90, "square"] :
152 | name == "nintendosuperfamicom" ? [0, 127, 20, 21.5, 86, "square"] :
153 | name == "nes" ? [0, 120, 17, 33.5, 134, "square"] :
154 | name == "snes" ? [0, 136, 20, 21.925, 87.7, "square"] :
155 | name == "nintendo64" ? [0, 116, 18, 18.75, 75, "square"] :
156 | name == "nintendogb" ? [0, 57, 7.5, 16.375, 65.5, "square"] :
157 | name == "nintendogbc" ? [0, 57, 9, 16.375, 65.5, "square"] :
158 | name == "nintendogba" ? [0, 35, 6, 14.25, 57, "square"] :
159 | name == "nintendods" ? [0, 33, 3.8, 8.75, 35, "square"] :
160 | name == "nintendo2ds" ? [0, 35, 3.8, 8.75, 35, "square"] :
161 | name == "nintendovb" ? [0, 75, 7, 17, 68, "square"] :
162 | name == "nintendoswitch" ? [0, 21, 3, 7.75, 31, "square"] :
163 | name == "segagamegear" ? [0, 66, 10, 17, 68, "square"] :
164 | name == "segagenesis" ? [0, 118, 15, 17, 68, "square"] :
165 | name == "segagenesistall" ? [0, 96, 16, 22, 88, "square"] :
166 | name == "segamegadrive" ? [0, 93, 17, 16.75, 67, "square"] :
167 | name == "segamegadrivecodemasters" ? [0, 109, 17, 18.75, 75, "square"] :
168 | name == "segamastersystem" ? [0, 69, 17, 27, 108, "square"] :
169 | name == "sega32x" ? [0, 72, 16, 27.75, 111, "square"] :
170 | name == "segacard" ? [0, 84, 2, 13.25, 53, "square"] :
171 | name == "segapico" ? [0, 181, 15, 55.75, 223, "square"] :
172 | name == "sonyumd" ? [0, 64, 0, 1.05, 4.2, "round"] :
173 | name == "sonypsvita" ? [0, 22, 2, 7.5, 30, "square"] :
174 | name == "sonypsvitamemcard" ? [0, 12.5, 1.6, 3.75, 15, "square"] :
175 | name == "necpcehucard" ? [0, 53, 2, 21, 84, "square"] :
176 | name == "snkneogeoaes" ? [0, 146.05, 31.75, 47.625, 190.5, "square"] :
177 | name == "snkneogeomvs" ? [0, 145, 35, 46.25, 185, "square"] :
178 | name == "bandai" ? [0, 41, 6, 16.5, 66, "square"] :
179 | name == "msx" ? [0, 109, 16.8, 17.35, 69.4, "square"] :
180 | [0,0,0,0,0,"","LookupKnownCartridge"];
--------------------------------------------------------------------------------
/src/modules/module_lip.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 | include
4 |
5 | //Lip object configuration
6 | iLipStyle=0;
7 | iLipSideReliefTrigger=1;
8 | iLipTopReliefHeight=2;
9 | iLipTopReliefWidth=3;
10 | iLipNotch=4;
11 | iLipClipPosition=5;
12 | iLipNonBlocking=6;
13 |
14 | LipStyle_normal = "normal";
15 | LipStyle_reduced = "reduced";
16 | LipStyle_reduced_double = "reduced_double";
17 | LipStyle_minimum = "minimum";
18 | LipStyle_none = "none";
19 | LipStyle_values = [LipStyle_normal,LipStyle_reduced, LipStyle_reduced_double, LipStyle_minimum,LipStyle_none];
20 | function validateLipStyle(value) =
21 | assert(list_contains(LipStyle_values, value), typeerror("LipStyle", value))
22 | value;
23 |
24 | LipClipPosition_disabled = "disabled";
25 | LipClipPosition_center_wall = "center_wall";
26 | LipClipPosition_intersection = "intersection";
27 | LipClipPosition_both = "both";
28 | LipClipPosition_values = [LipClipPosition_disabled,LipClipPosition_center_wall,LipClipPosition_intersection,LipClipPosition_both];
29 | function validateLipClipPosition(value) =
30 | assert(list_contains(LipClipPosition_values, value), typeerror("LipClipPosition", value))
31 | value;
32 |
33 | function LipSettings(
34 | lipStyle = LipStyle_normal,
35 | lipSideReliefTrigger = [1,1],
36 | lipTopReliefHeight = -1,
37 | lipTopReliefWidth = -1,
38 | lipNotch = true,
39 | lipClipPosition = LipClipPosition_disabled,
40 | lipNonBlocking = false) =
41 | let(
42 | result = [
43 | lipStyle,
44 | lipSideReliefTrigger,
45 | lipTopReliefHeight,
46 | lipTopReliefWidth,
47 | lipNotch,
48 | lipClipPosition,
49 | lipNonBlocking],
50 | validatedResult = ValidateLipSettings(result)
51 | ) validatedResult;
52 |
53 | function ValidateLipSettings(settings) =
54 | assert(is_list(settings), "LipStyle Settings must be a list")
55 | assert(len(settings)==7, "LipStyle Settings must length 7")
56 | assert(is_bool(settings[iLipNotch]), "Lip Notch must be a bool")
57 |
58 | [validateLipStyle(settings[iLipStyle]),
59 | settings[iLipSideReliefTrigger],
60 | settings[iLipTopReliefHeight],
61 | settings[iLipTopReliefWidth],
62 | settings[iLipNotch],
63 | validateLipClipPosition(settings[iLipClipPosition]),
64 | settings[iLipNonBlocking]];
65 |
66 | module cupLip(
67 | num_x = 2,
68 | num_y = 3,
69 | lipStyle = LipStyle_normal,
70 | wall_thickness = 1.2,
71 | lip_notches = true,
72 | lip_top_relief_height = -1,
73 | lip_top_relief_width = -1,
74 | lip_clip_position = LipClipPosition_disabled,
75 | lip_non_blocking = false){
76 |
77 | assert(is_num(num_x) && num_x > 0, "num_x must be a number greater than 0");
78 | assert(is_num(num_y) && num_y > 0, "num_y must be a number greater than 0");
79 | assert(is_string(lipStyle));
80 | assert(is_num(wall_thickness) && wall_thickness > 0, "wall_thickness must be a number greater than 0");
81 | assert(is_num(lip_top_relief_height));
82 | assert(is_num(lip_top_relief_width));
83 | assert(is_bool(lip_notches));
84 | assert(is_string(lip_clip_position));
85 | assert(is_bool(lip_non_blocking));
86 |
87 | connectorsEnabled = lip_clip_position != LipClipPosition_disabled;
88 | $allowConnectors = connectorsEnabled ? [1,1,1,1] : [0,0,0,0];
89 | $frameBaseHeight = 0; //$num_z * env_pitch().z;
90 |
91 | //Difference between the wall and support thickness
92 | lipSupportThickness = (lipStyle == "minimum" || lipStyle == "none") ? 0
93 | : lipStyle == "reduced" ? gf_lip_upper_taper_height - wall_thickness
94 | : lipStyle == "reduced_double" ? gf_lip_upper_taper_height - wall_thickness
95 | : gf_lip_upper_taper_height + gf_lip_lower_taper_height- wall_thickness;
96 |
97 | floorht=0;
98 |
99 | seventeen = [env_pitch().x/2-4, env_pitch().y/2-4];
100 | innerLipRadius = gf_cup_corner_radius-gf_lip_lower_taper_height-gf_lip_upper_taper_height; //1.15
101 | innerWallRadius = gf_cup_corner_radius-wall_thickness;
102 |
103 | // I couldn't think of a good name for this ('q') but effectively it's the
104 | // size of the overhang that produces a wall thickness that's less than the lip
105 | // around the top inside edge.
106 | q = 1.65-wall_thickness+0.95; // default 1.65 corresponds to wall thickness of 0.95
107 | lipHeight = 3.75;
108 |
109 | outer_size = [env_pitch().x - gf_tolerance, env_pitch().y - gf_tolerance]; // typically 41.5
110 | block_corner_position = [outer_size.x/2 - gf_cup_corner_radius, outer_size.y/2 - gf_cup_corner_radius]; // need not match center of pad corners
111 |
112 | coloredLipHeight=min(2,lipHeight);
113 |
114 | if(lipStyle != "none")
115 | color(env_colour(color_topcavity, isLip = true))
116 |
117 | tz(-fudgeFactor*2)
118 | difference() {
119 | //Lip outer shape
120 | tz(fudgeFactor*2)
121 | hull()
122 | cornercopy(block_corner_position, num_x, num_y)
123 | cylinder(r=gf_cup_corner_radius, h=lipHeight+fudgeFactor);
124 |
125 | pitch=env_pitch();
126 | // remove top so XxY can fit on top
127 | //pad_oversize(num_x, num_y, 1);
128 | union(){
129 | //Top cavity, with lip relief
130 | frame_cavity(
131 | num_x = lip_non_blocking ? ceil(num_x) : num_x,
132 | num_y = lip_non_blocking ? ceil(num_y) : num_y,
133 | position_fill_grid_x = "far",
134 | position_fill_grid_y = "far",
135 | render_top = lip_notches,
136 | render_bottom = false,
137 | frameLipHeight = 4,
138 | reducedWallHeight = lip_top_relief_height,
139 | reducedWallWidth = lip_top_relief_width,
140 | reducedWallOuterEdgesOnly=true){
141 | echo("donothign");
142 | frame_connectors(
143 | width = num_x,
144 | depth = num_y,
145 | connectorPosition = lip_clip_position,
146 | connectorClipEnabled = connectorsEnabled);
147 | };
148 |
149 | //lower cavity
150 | frame_cavity(
151 | num_x = 1,
152 | num_y = 1,
153 | position_fill_grid_x = "far",
154 | position_fill_grid_y = "far",
155 | render_top = !lip_notches,
156 | render_bottom = true,
157 | frameLipHeight = 4,
158 | reducedWallHeight = -1,
159 | reducedWallWidth = -1,
160 | $pitch=[
161 | pitch.x*(lip_non_blocking ? ceil(num_x) : num_x),
162 | pitch.y*(lip_non_blocking ? ceil(num_y) : num_y),
163 | pitch.z]);
164 | }
165 |
166 | if (lipStyle == "minimum" || lipStyle == "none") {
167 | hull() cornercopy(seventeen, num_x, num_y)
168 | tz(-fudgeFactor)
169 | cylinder(r=innerWallRadius, h=gf_Lip_Height); // remove entire lip
170 | }
171 | else if (lipStyle == "reduced" || lipStyle == "reduced_double") {
172 | lowerTaperZ = gf_lip_lower_taper_height;
173 | hull() cornercopy(seventeen, num_x, num_y)
174 | union(){
175 | tz(lowerTaperZ)
176 | cylinder(
177 | r1=innerWallRadius,
178 | r2=gf_cup_corner_radius-gf_lip_upper_taper_height,
179 | h=lipSupportThickness);
180 | tz(-fudgeFactor)
181 | cylinder(
182 | r=innerWallRadius,
183 | h=lowerTaperZ+fudgeFactor*2);
184 | }
185 | }
186 | else { // normal
187 | lowerTaperZ = -gf_lip_height-lipSupportThickness;
188 | if(lowerTaperZ <= floorht){
189 | hull() cornercopy(seventeen, num_x, num_y)
190 | tz(floorht)
191 | cylinder(r=innerLipRadius, h=-floorht+fudgeFactor*2); // lip
192 | } else {
193 | hull() cornercopy(seventeen, num_x, num_y)
194 | tz(-gf_lip_height-fudgeFactor)
195 | cylinder(r=innerLipRadius, h=gf_lip_height+fudgeFactor*2); // lip
196 |
197 | hull() cornercopy(seventeen, num_x, num_y)
198 | tz(-gf_lip_height-lipSupportThickness-fudgeFactor)
199 | cylinder(
200 | r1=innerWallRadius,
201 | r2=innerLipRadius, h=q+fudgeFactor); // ... to top of thin wall ...
202 | }
203 | }
204 | }
205 | }
206 |
207 |
--------------------------------------------------------------------------------
/src/modules/module_pattern_brick.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 |
4 | module brick_pattern(
5 | canvis_size=[31,31],
6 | thickness = 1,
7 | spacing = 1,
8 | border = 0,
9 | cell_size = [15,5],
10 | corner_radius = 3,
11 | center_weight = 3,
12 | offset_layers = false,
13 | center = true,
14 | rotateGrid = false){
15 |
16 | assert(is_list(canvis_size) && len(canvis_size) == 2, "canvis_size must be a list of len 2");
17 | assert(is_num(thickness), "thickness must be a number");
18 | assert(is_num(spacing), "spacing must be a number");
19 | assert(is_num(border), "border must be a number");
20 | assert(is_list(cell_size) && len(cell_size) == 2, "cell_size must be a list of len 2");
21 | assert(is_num(corner_radius), "corner_radius must be a number");
22 | assert(is_num(center_weight), "center_weight must be a number");
23 | assert(is_bool(offset_layers), "offset_layers must be a bool");
24 | assert(is_bool(center), "center must be a bool");
25 | assert(is_bool(rotateGrid), "rotateGrid must be a bool");
26 |
27 | corner_radius = min(corner_radius,cell_size.x/2, cell_size.y/2);
28 |
29 | working_canvis_size =
30 | let (cs = rotateGrid ? [canvis_size.y,canvis_size.x] : canvis_size)
31 | border > 0 ? [cs.x-border*2,cs.y-border*2] : cs;
32 |
33 | ny = floor((working_canvis_size.y + spacing) / (cell_size.y + spacing));
34 | nx = floor((working_canvis_size.x + spacing) / (cell_size.x + spacing));
35 |
36 | function course(canvis_length, count, spacing, center_weight, half_offset=false) =
37 | let(c = count - (half_offset ? 0 : 1),
38 | l = [for (i=[0:c])
39 | (((canvis_length+spacing)/(c) + cos((i)*360/(c))*-1*center_weight)/(half_offset && (i==0 || i==c) ? 2 : 1) - spacing)],
40 | suml = sum(l),
41 | comp = half_offset ? 1 : (canvis_length-(c)*spacing)/suml)
42 | [for (i=[0:c]) l[i]*comp];
43 |
44 | if(ny> 0 && nx > 0)
45 | translate(center ? [0,0,0] : [canvis_size.x/2,canvis_size.y/2,0])
46 | rotate(rotateGrid?[0,0,90]:[0,0,0])
47 | translate([-working_canvis_size.x/2,-working_canvis_size.y/2])
48 | for(iy=[0:ny-1]){
49 | let(h=(working_canvis_size.y + spacing)/ny-spacing)
50 | translate([0,(h+spacing)*iy])
51 | {
52 | bricks = course(canvis_length=working_canvis_size.x, count=nx, spacing=spacing, center_weight=center_weight, half_offset=offset_layers && iy%2==1);
53 | for(ix=[0:len(bricks)-1]) {
54 | pos = sum(bricks, end = ix-1) + spacing*ix;
55 | size = [bricks[ix], h, thickness];
56 | if(size.x > min(cell_size.x,cell_size.y)*0.5 && size.y > min(cell_size.x,cell_size.y)*0.5)
57 | translate([pos,0])
58 | roundedCube(
59 | size = size,
60 | sideRadius = corner_radius,
61 | supportReduction_x = [0,1]
62 | //supportReduction_y = [0,0],
63 | //supportReduction_z = [0,0]
64 | );
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/modules/module_pattern_voronoi.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * m_transpose.scad
3 | * use <../matrix/m_transpose.scad>
4 | * @copyright Justin Lin, 2021
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-m_transpose.html
8 | **/
9 | function m_transpose(m) =
10 | let(
11 | column = len(m[0]),
12 | row = len(m)
13 | )
14 | [
15 | for(y = 0; y < column; y = y + 1)
16 | [
17 | for(x = 0; x < row; x = x + 1)
18 | m[x][y]
19 | ]
20 | ];
21 |
22 | /**
23 | * unit_vector.scad
24 | * use <../util/unit_vector.scad>
25 | * @copyright Justin Lin, 2021
26 | * @license https://opensource.org/licenses/lgpl-3.0.html
27 | **/
28 | function unit_vector(v) = v / norm(v);
29 |
30 | /**
31 | * vrn2_from.scad
32 | * @copyright Justin Lin, 2020
33 | * @license https://opensource.org/licenses/lgpl-3.0.html
34 | *
35 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-vrn2_from.html
36 | **/
37 | module vrn2_from(points, spacing = 1, r = 0, delta = 0, chamfer = false, region_type = "square") {
38 | transposed = m_transpose(points);
39 | xs = transposed[0];
40 | ys = transposed[1];
41 |
42 | region_size = max([max(xs) - min(xs), max(ys) - min(ys)]);
43 | half_region_size = 0.5 * region_size;
44 | offset_leng = spacing * 0.5 + half_region_size;
45 |
46 | module region(pt) {
47 | intersection_for(p = [for(p = points) if(pt != p) p]) {
48 | v = p - pt;
49 | translate((pt + p) / 2 - unit_vector(v) * offset_leng)
50 | rotate(atan2(v.y, v.x))
51 | children();
52 | }
53 | }
54 |
55 | module offseted_region(pt) {
56 | if(r != 0) {
57 | offset(r)
58 | region(pt)
59 | children();
60 | }
61 | else {
62 | offset(delta = delta, chamfer = chamfer)
63 | region(pt)
64 | children();
65 | }
66 | }
67 |
68 | for(p = points) {
69 | if(region_type == "square") {
70 | offseted_region(p)
71 | square(region_size, center = true);
72 | }
73 | else {
74 | offseted_region(p)
75 | circle(half_region_size);
76 | }
77 | }
78 | }
79 |
80 | module rectangle_voronoi(
81 | canvasSize = [200,200,10],
82 | points=[],
83 | cellsize=10,
84 | noise=0.5,
85 | grid=false,
86 | gridOffset = false,
87 | spacing = 2,
88 | radius = 0.5,
89 | seed = undef,
90 | center=true)
91 | {
92 | _spacing = spacing + radius*2;
93 | points = points != undef && is_list(points) && len(points) > 0
94 | ? points
95 | : grid ?
96 | let(
97 | _pointCount = [ceil(canvasSize.x/cellsize)+1,ceil(canvasSize.y/cellsize)+1],
98 | seed = seed == undef ? rands(0, 100000, 2)[0] : seed,
99 | seeds = rands(0, 100000, 2, seed), // you need a different seed for x and y
100 | pointsx = rands(-cellsize/2*noise, cellsize/2*noise, _pointCount.x*_pointCount.y, seeds[0]),
101 | pointsy = rands(-cellsize/2*noise, cellsize/2*noise, _pointCount.x*_pointCount.y, seeds[1])
102 | )[for(i = [0:_pointCount.x-1], y = [0:_pointCount.y-1])
103 | [i*cellsize + pointsx[i+y*_pointCount.x]-canvasSize.x/2 + (y % 2 == 0 && gridOffset ? cellsize/2 : 0),
104 | y*cellsize + pointsy[i*_pointCount.y+y]-canvasSize.y/2]]
105 | : let(
106 | _pointCount = max((canvasSize.x * canvasSize.y)/(cellsize^2), 30),
107 | seed = seed == undef ? rands(0, 100000, 2)[0] : seed,
108 | seeds = rands(0, 100000, 2, seed), // you need a different seed for x and y
109 | pointsx = rands(-canvasSize.x/2, canvasSize.x/2, _pointCount, seeds[0]),
110 | pointsy = rands(-canvasSize.y/2, canvasSize.y/2, _pointCount, seeds[1])
111 | )[for(i = [0:_pointCount-1]) [pointsx[i],pointsy[i]]];
112 |
113 | translate(center ? [0, 0, 0] : [canvasSize.x/2, canvasSize.y/2, 0])
114 | intersection() {
115 | translate([0,0,canvasSize.z/2])
116 | cube(size = [canvasSize.x,canvasSize.y,canvasSize.z*2], center=true);
117 |
118 | linear_extrude(height = canvasSize.z)
119 | vrn2_from(
120 | points,
121 | spacing=_spacing,
122 | chamfer=false,
123 | delta=0,
124 | r=radius,
125 | region_type = "square");
126 | }
127 | }
--------------------------------------------------------------------------------
/src/modules/module_patterns.scad:
--------------------------------------------------------------------------------
1 | include
2 | include
3 | include
4 |
5 | iPatternEnabled=0;
6 | iPatternStyle=1;
7 | iPatternFill=2;
8 | iPatternBorder=3;
9 | iPatternHoleSize=4;
10 | iPatternHoleSides=5;
11 | iPatternHoleSpacing=6;
12 | iPatternHoleRadius=7;
13 | iPatternGridChamfer=8;
14 | iPatternVoronoiNoise=9;
15 | iPatternBrickWeight=10;
16 | iPatternFs=11;
17 |
18 | PatternStyle_grid = "grid";
19 | PatternStyle_gridrotated = "gridrotated";
20 | PatternStyle_hexgrid = "hexgrid";
21 | PatternStyle_hexgridrotated = "hexgridrotated";
22 | PatternStyle_voronoi = "voronoi";
23 | PatternStyle_voronoigrid = "voronoigrid";
24 | PatternStyle_voronoihexgrid = "voronoihexgrid";
25 | PatternStyle_brick = "brick";
26 | PatternStyle_brickrotated = "brickrotated";
27 | PatternStyle_brickoffset = "brickoffset";
28 | PatternStyle_brickoffsetrotated = "brickoffsetrotated";
29 |
30 | PatternStyle_values = [PatternStyle_grid, PatternStyle_gridrotated, PatternStyle_hexgrid, PatternStyle_hexgridrotated, PatternStyle_voronoi, PatternStyle_voronoigrid, PatternStyle_voronoihexgrid, PatternStyle_brick, PatternStyle_brickrotated, PatternStyle_brickoffset, PatternStyle_brickoffsetrotated];
31 | function validatePatternStyle(value, name = "PatternStyle") =
32 | assert(list_contains(PatternStyle_values, value), typeerror(name, value))
33 | value;
34 |
35 | PatternFill_none = "none";
36 | PatternFill_space = "space";
37 | PatternFill_crop = "crop";
38 | PatternFill_crophorizontal = "crophorizontal";
39 | PatternFill_cropvertical = "cropvertical";
40 | PatternFill_crophorizontal_spacevertical = "crophorizontal_spacevertical";
41 | PatternFill_cropvertical_spacehorizontal = "cropvertical_spacehorizontal";
42 | PatternFill_spacevertical = "spacevertical";
43 | PatternFill_spacehorizontal = "spacehorizontal";
44 |
45 | PatternFill_values = [PatternFill_none, PatternFill_space, PatternFill_crop, PatternFill_crophorizontal, PatternFill_cropvertical, PatternFill_crophorizontal_spacevertical, PatternFill_cropvertical_spacehorizontal, PatternFill_spacevertical, PatternFill_spacehorizontal];
46 |
47 | function validatePatternFill(value, name = "PatternFill") =
48 | assert(list_contains(PatternFill_values, value), typeerror(name, value))
49 | value;
50 |
51 | function PatternSettings(
52 | patternEnabled,
53 | patternStyle,
54 | patternFill,
55 | patternBorder = -1,
56 | patternHoleSize,
57 | patternHoleSides,
58 | patternHoleSpacing,
59 | patternHoleRadius,
60 | patternGridChamfer=0,
61 | patternVoronoiNoise=0,
62 | patternBrickWeight=0,
63 | patternFs = 0) =
64 | let(
65 | result = [
66 | patternEnabled,
67 | patternStyle,
68 | patternFill,
69 | patternBorder,
70 | is_num(patternHoleSize) ? [patternHoleSize, patternHoleSize] : patternHoleSize,
71 | patternHoleSides,
72 | patternHoleSpacing,
73 | patternHoleRadius,
74 | patternGridChamfer,
75 | patternVoronoiNoise,
76 | patternBrickWeight,
77 | patternFs],
78 | validatedResult = ValidatePatternSettings(result)
79 | ) validatedResult;
80 |
81 | function ValidatePatternSettings(settings, num_x, num_y) =
82 | assert(is_list(settings), "PatternStyle Settings must be a list")
83 | assert(len(settings)==12, "PatternStyle Settings must length 10")
84 | [settings[iPatternEnabled],
85 | validatePatternStyle(settings[iPatternStyle]),
86 | validatePatternFill(settings[iPatternFill]),
87 | settings[iPatternBorder],
88 | settings[iPatternHoleSize],
89 | settings[iPatternHoleSides],
90 | settings[iPatternHoleSpacing],
91 | settings[iPatternHoleRadius],
92 | settings[iPatternGridChamfer],
93 | settings[iPatternVoronoiNoise],
94 | settings[iPatternBrickWeight],
95 | settings[iPatternFs]];
96 |
97 | module cutout_pattern(
98 | patternStyle,
99 | canvasSize,
100 | customShape = false,
101 | circleFn,
102 | holeSize = [],
103 | holeSpacing,
104 | holeHeight,
105 | holeRadius,
106 | center = true,
107 | centerz = false,
108 | fill,
109 | patternGridChamfer=0,
110 | patternVoronoiNoise=0,
111 | patternBrickWeight=0,
112 | border = 0,
113 | patternFs = 0,
114 | source = ""){
115 |
116 | canvasSize = border > 0
117 | ? [canvasSize.x-border*2, canvasSize.y-border*2]
118 | : canvasSize;
119 | if(env_help_enabled("trace")) echo("cutout_pattern", source=source, canvasSize=canvasSize, patternFs=patternFs, border=border);
120 |
121 | $fs = patternFs > 0 ? patternFs : $fs;
122 |
123 | //translate(border>0 ? [border,border,0] : [0,0,0])
124 | translate(center ? [0,0,0] : [border,border,0])
125 | translate(centerz ? [0,0,-holeHeight/2] : [0,0,0])
126 | union(){
127 | if(patternStyle == PatternStyle_grid || patternStyle == PatternStyle_hexgrid || patternStyle == PatternStyle_gridrotated || patternStyle == PatternStyle_hexgridrotated) {
128 | GridItemHolder(
129 | canvasSize = canvasSize,
130 | hexGrid = (patternStyle == PatternStyle_hexgrid || patternStyle == PatternStyle_hexgridrotated),
131 | customShape = customShape,
132 | circleFn = circleFn,
133 | holeSize = holeSize,
134 | holeSpacing = holeSpacing,
135 | holeHeight = holeHeight,
136 | center=center,
137 | fill=fill, //"none", "space", "crop"
138 | rotateGrid = !(patternStyle == PatternStyle_gridrotated || patternStyle == PatternStyle_hexgridrotated),
139 | //border = border,
140 | holeChamfer=[patternGridChamfer,patternGridChamfer]);
141 | }
142 | else if(patternStyle == PatternStyle_voronoi || patternStyle == PatternStyle_voronoigrid || patternStyle == "voronoihexgrid"){
143 | if(env_help_enabled("trace")) echo("cutout_pattern", canvasSize = [canvasSize.x,canvasSize.y,holeHeight], thickness = holeSpacing.x, round=1);
144 | rectangle_voronoi(
145 | canvasSize = [canvasSize.x,canvasSize.y,holeHeight],
146 | spacing = holeSpacing.x,
147 | cellsize = holeSize.x,
148 | grid = (patternStyle == PatternStyle_voronoigrid || patternStyle == PatternStyle_voronoihexgrid),
149 | gridOffset = (patternStyle == PatternStyle_voronoihexgrid),
150 | noise=patternVoronoiNoise,
151 | radius = holeRadius,
152 | center=center,
153 | seed=env_random_seed());
154 | }
155 | else if(patternStyle == PatternStyle_brick || patternStyle == PatternStyle_brickrotated ||
156 | patternStyle == PatternStyle_brickoffset || patternStyle == PatternStyle_brickoffsetrotated){
157 | if(env_help_enabled("trace")) echo("cutout_pattern", canvasSize = [canvasSize.x,canvasSize.y,holeHeight], thickness = holeSpacing.x, round=1);
158 | brick_pattern(
159 | canvis_size=[canvasSize.x,canvasSize.y],
160 | thickness = holeHeight,
161 | spacing=holeSpacing.x,
162 | center=center,
163 | cell_size = holeSize,
164 | corner_radius = holeRadius,
165 | center_weight = patternBrickWeight,
166 | rotateGrid = !(patternStyle == PatternStyle_brickrotated || patternStyle == PatternStyle_brickoffsetrotated),
167 | offset_layers = (patternStyle == PatternStyle_brickoffset || patternStyle == PatternStyle_brickoffsetrotated)
168 | );
169 | }
170 | }
171 | }
--------------------------------------------------------------------------------
/src/modules/module_rounded_negative_champher.scad:
--------------------------------------------------------------------------------
1 | champher_demo = false;
2 |
3 | if(champher_demo)
4 | {
5 | rotate([90,0,0])
6 | chamferedSquare(size=50, radius = 0, $fn=64);
7 |
8 | translate([0,0,30])
9 | roundedNegativeChampher(
10 | champherRadius = 10,
11 | size=[400,200],
12 | cornerRadius = 20, $fn=64);
13 |
14 | translate([0,0,60])
15 | roundedNegativeChampher(
16 | champherRadius = 20,
17 | size=[200,100],
18 | cornerRadius = 10,
19 | champher = false, $fn=64);
20 |
21 | translate([0,0,90])
22 | roundedNegativeChampher(
23 | champherRadius = 10,
24 | size=[100,50],
25 | cornerRadius = 5,
26 | height = 20, $fn=64);
27 | }
28 |
29 | module roundedNegativeChampher(
30 | champherRadius = 10,
31 | size=[80,100],
32 | cornerRadius = 10,
33 | height = 0,
34 | champher = false)
35 | {
36 | eps = 0.01;
37 |
38 | height = height <= champherRadius ? champherRadius : height;
39 | postions = [
40 | [[0,0,0],
41 | size.y-cornerRadius*2,
42 | [size.x/2+champherRadius,size.y/2-cornerRadius,0],
43 | [size.x/2-cornerRadius,size.y/2-cornerRadius,0]],
44 | [[90,0,0],
45 | size.x-cornerRadius*2,
46 | [size.y/2+champherRadius,size.x/2-cornerRadius,0],
47 | [-size.x/2+cornerRadius,-size.y/2+cornerRadius,0]],
48 | [[180,0,0],
49 | size.y-cornerRadius*2,
50 | [size.x/2+champherRadius,size.y/2-cornerRadius,0],
51 | [size.x/2-cornerRadius,-size.y/2+cornerRadius,0]],
52 | [[270,0,0],
53 | size.x-cornerRadius*2,
54 | [size.y/2+champherRadius,size.x/2-cornerRadius,0],
55 | [-size.x/2+cornerRadius,size.y/2-cornerRadius,0]]
56 | ];
57 |
58 | difference(){
59 | hull(){
60 | for (pos = postions){
61 | translate(pos[3])
62 | cylinder(r=(cornerRadius+champherRadius), h=height);
63 | }
64 | }
65 |
66 | union(){
67 | for (pos = postions){
68 | rotate(pos[0][0])
69 | translate(pos[2])
70 | union(){
71 | translate([-cornerRadius-champherRadius, 0])
72 | rotate_extrude(angle=90, convexity=cornerRadius)
73 | translate([cornerRadius+champherRadius, 0])
74 | if(champher){
75 | chamferedSquare(champherRadius*2);
76 | } else {
77 | circle(champherRadius);
78 | }
79 |
80 | translate([0, eps, 0])
81 | rotate([90, 0, 0])
82 | if(champher){
83 | linear_extrude(height=pos[1]+eps*2)
84 | chamferedSquare(champherRadius*2);
85 | } else {
86 | cylinder(r=champherRadius, h=pos[1]+eps*2);
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 |
94 | module chamferedSquare(size=0, radius = 0){
95 | assert(is_num(size), "size must be a number");
96 | assert(is_num(radius), "radius must be a number");
97 | radius = radius <= 0 ? size/4 : radius;
98 | hull()
99 | {
100 | translate([0,radius])
101 | circle(radius);
102 | translate([-radius,0])
103 | circle(radius);
104 | translate([0,-size/2])
105 | square([size/2,size]);
106 | translate([-size/2,-size/2])
107 | square([size,size/2]);
108 | }
109 | }
--------------------------------------------------------------------------------
/src/modules/module_utility_wallcutout.scad:
--------------------------------------------------------------------------------
1 | iwalcutoutconfig_type = 0;
2 | iwalcutoutconfig_position = 1;
3 | iwalcutoutconfig_width = 2;
4 | iwalcutoutconfig_angle = 3;
5 | iwalcutoutconfig_height = 4;
6 | iwalcutoutconfig_cornerradius = 5;
7 |
8 | iwalcutout_config = 0;
9 | iwalcutout_enabled = 1;
10 | iwalcutout_position = 2;
11 | iwalcutout_size = 3;
12 | iwalcutout_rotation = 4;
13 | iwalcutout_reposition = 5;
14 |
15 | function calculateWallCutout(
16 | wall_length,
17 | opposite_wall_distance,
18 | wallcutout_type,
19 | wallcutout_position,
20 | wallcutout_width,
21 | wallcutout_angle,
22 | wallcutout_height,
23 | wallcutout_corner_radius,
24 | wallcutout_rotation = [0,0,0],
25 | wallcutout_reposition = [0,0,0],
26 | wall_thickness,
27 | cavityFloorRadius,
28 | wallTop,
29 | floorHeight,
30 | pitch,
31 | pitch_opposite) =
32 | let(
33 | fullEnabled = wallcutout_type == "enabled",
34 | closeEnabled = wallcutout_type == "wallsonly" || wallcutout_type == "leftonly" || wallcutout_type == "frontonly",
35 | farEnabled = wallcutout_type == "wallsonly" || wallcutout_type == "rightonly" || wallcutout_type == "backonly",
36 | wallcutoutThickness = wall_thickness*2+max(wall_thickness*2,cavityFloorRadius), //wall_thickness*2 should be lip thickness
37 | wallcutoutHeight = wallcutout_height < 0
38 | ? (wallTop - floorHeight)/abs(wallcutout_height)
39 | : wallcutout_height == 0 ? wallTop - floorHeight - cavityFloorRadius
40 | : wallcutout_height,
41 | wallcutoutLowerWidth=wallcutout_width <= 0 ? max(wallcutout_corner_radius*2, wall_length*pitch/3) : wallcutout_width,
42 | closeThickness = fullEnabled ? opposite_wall_distance*pitch_opposite : wallcutoutThickness,
43 |
44 | //This could be more specific based on the base height, and the lip style.
45 | wallcutout_close = [
46 | [wallcutout_type, wallcutout_position, wallcutout_width, wallcutout_angle, wallcutout_height, wallcutout_corner_radius],
47 | closeEnabled || fullEnabled,
48 | [wallCutoutPosition_mm(wallcutout_position,wall_length,pitch), closeThickness/2+gf_tolerance/2-fudgeFactor, wallTop],
49 | [wallcutoutLowerWidth, closeThickness, wallcutoutHeight],
50 | wallcutout_rotation,
51 | wallcutout_reposition],
52 | wallcutout_far = [
53 | [wallcutout_type, wallcutout_position, wallcutout_width, wallcutout_angle, wallcutout_height, wallcutout_corner_radius],
54 | farEnabled,
55 | [wallCutoutPosition_mm(wallcutout_position,wall_length,pitch), opposite_wall_distance*pitch_opposite-wallcutoutThickness/2-gf_tolerance/2+fudgeFactor, wallTop],
56 | [wallcutoutLowerWidth, wallcutoutThickness, wallcutoutHeight],
57 | wallcutout_rotation,
58 | wallcutout_reposition]) [wallcutout_close, wallcutout_far];
59 |
60 | module WallCutout(
61 | lowerWidth=50,
62 | wallAngle=70,
63 | height=21,
64 | thickness=10,
65 | cornerRadius=5,
66 | topHeight) {
67 |
68 | topHeight = is_undef(topHeight) || topHeight < 0 ? cornerRadius*4 : topHeight;
69 | bottomWidth = lowerWidth;
70 | topWidth = lowerWidth+(height/tan(wallAngle))*2;
71 |
72 | rotate([90,0,0])
73 | translate([0,0,-thickness/2])
74 | linear_extrude(height=thickness)
75 | intersection(){
76 | translate([0,-height/2+topHeight/2,0])
77 | square([topWidth+cornerRadius*2,height+topHeight], true);
78 |
79 | //Use triple offset to fillet corners
80 | //https://www.reddit.com/r/openscad/comments/ut1n7t/quick_tip_simple_fillet_for_2d_shapes/
81 | offset(r=-cornerRadius)
82 | offset(r=2 * cornerRadius)
83 | offset(r=-cornerRadius)
84 | union(){
85 | translate([0,cornerRadius*4/2])
86 | square([topWidth*2,cornerRadius*4], true);
87 | hull(){
88 | translate([0,cornerRadius*4/2])
89 | square([topWidth,cornerRadius*4], true);
90 | translate([0,-height/2])
91 | square([bottomWidth,height], true);
92 | }
93 | }
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/src/modules/module_voronoi.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * m_transpose.scad
3 | * use <../matrix/m_transpose.scad>
4 | * @copyright Justin Lin, 2021
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-m_transpose.html
8 | **/
9 | function m_transpose(m) =
10 | let(
11 | column = len(m[0]),
12 | row = len(m)
13 | )
14 | [
15 | for(y = 0; y < column; y = y + 1)
16 | [
17 | for(x = 0; x < row; x = x + 1)
18 | m[x][y]
19 | ]
20 | ];
21 |
22 | /**
23 | * unit_vector.scad
24 | * use <../util/unit_vector.scad>
25 | * @copyright Justin Lin, 2021
26 | * @license https://opensource.org/licenses/lgpl-3.0.html
27 | **/
28 | function unit_vector(v) = v / norm(v);
29 |
30 | /**
31 | * vrn2_from.scad
32 | * @copyright Justin Lin, 2020
33 | * @license https://opensource.org/licenses/lgpl-3.0.html
34 | *
35 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-vrn2_from.html
36 | **/
37 | module vrn2_from(points, spacing = 1, r = 0, delta = 0, chamfer = false, region_type = "square") {
38 | transposed = m_transpose(points);
39 | xs = transposed[0];
40 | ys = transposed[1];
41 |
42 | region_size = max([max(xs) - min(xs), max(ys) - min(ys)]);
43 | half_region_size = 0.5 * region_size;
44 | offset_leng = spacing * 0.5 + half_region_size;
45 |
46 | module region(pt) {
47 | intersection_for(p = [for(p = points) if(pt != p) p]) {
48 | v = p - pt;
49 | translate((pt + p) / 2 - unit_vector(v) * offset_leng)
50 | rotate(atan2(v.y, v.x))
51 | children();
52 | }
53 | }
54 |
55 | module offseted_region(pt) {
56 | if(r != 0) {
57 | offset(r)
58 | region(pt)
59 | children();
60 | }
61 | else {
62 | offset(delta = delta, chamfer = chamfer)
63 | region(pt)
64 | children();
65 | }
66 | }
67 |
68 | for(p = points) {
69 | if(region_type == "square") {
70 | offseted_region(p)
71 | square(region_size, center = true);
72 | }
73 | else {
74 | offseted_region(p)
75 | circle(half_region_size);
76 | }
77 | }
78 | }
79 |
80 | module rectangle_voronoi(
81 | canvasSize = [200,200,10],
82 | points=[],
83 | cellsize=10,
84 | noise=0.5,
85 | grid=false,
86 | gridOffset = false,
87 | spacing = 2,
88 | radius = 0.5,
89 | seed = undef,
90 | center=true)
91 | {
92 | _spacing = spacing + radius*2;
93 | points = points != undef && is_list(points) && len(points) > 0
94 | ? points
95 | : grid ?
96 | let(
97 | _pointCount = [ceil(canvasSize.x/cellsize)+1,ceil(canvasSize.y/cellsize)+1],
98 | seed = seed == undef ? rands(0, 100000, 2)[0] : seed,
99 | seeds = rands(0, 100000, 2, seed), // you need a different seed for x and y
100 | pointsx = rands(-cellsize/2*noise, cellsize/2*noise, _pointCount.x*_pointCount.y, seeds[0]),
101 | pointsy = rands(-cellsize/2*noise, cellsize/2*noise, _pointCount.x*_pointCount.y, seeds[1])
102 | )[for(i = [0:_pointCount.x-1], y = [0:_pointCount.y-1])
103 | [i*cellsize + pointsx[i+y*_pointCount.x]-canvasSize.x/2 + (y % 2 == 0 && gridOffset ? cellsize/2 : 0),
104 | y*cellsize + pointsy[i*_pointCount.y+y]-canvasSize.y/2]]
105 | : let(
106 | _pointCount = max((canvasSize.x * canvasSize.y)/(cellsize^2), 30),
107 | seed = seed == undef ? rands(0, 100000, 2)[0] : seed,
108 | seeds = rands(0, 100000, 2, seed), // you need a different seed for x and y
109 | pointsx = rands(-canvasSize.x/2, canvasSize.x/2, _pointCount, seeds[0]),
110 | pointsy = rands(-canvasSize.y/2, canvasSize.y/2, _pointCount, seeds[1])
111 | )[for(i = [0:_pointCount-1]) [pointsx[i],pointsy[i]]];
112 |
113 | translate(center ? [0, 0, 0] : [canvasSize.x/2, canvasSize.y/2, 0])
114 | intersection() {
115 | translate([0,0,canvasSize.z/2])
116 | cube(size = [canvasSize.x,canvasSize.y,canvasSize.z*2], center=true);
117 |
118 | linear_extrude(height = canvasSize.z)
119 | vrn2_from(
120 | points,
121 | spacing=_spacing,
122 | chamfer=false,
123 | delta=0,
124 | r=radius,
125 | region_type = "square");
126 | }
127 | }
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/__comm__/__frags.scad:
--------------------------------------------------------------------------------
1 | function __frags(radius) =
2 | $fn == 0 ?
3 | max(min(360 / $fa, radius * 6.283185307179586 / $fs), 5) :
4 | max($fn, 3);
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/__comm__/__ra_to_xy.scad:
--------------------------------------------------------------------------------
1 | function __ra_to_xy(r, a) = [r * cos(a), r * sin(a)];
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/__comm__/__to_ang_vect.scad:
--------------------------------------------------------------------------------
1 | function __to_3_elems_ang_vect(a) =
2 | let(leng = len(a))
3 | leng == 3 ? a :
4 | leng == 2 ? [each a, 0] : [a.x, 0, 0];
5 |
6 | function __to_ang_vect(a) = is_num(a) ? [0, 0, a] : __to_3_elems_ang_vect(a);
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/cross_sections.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * cross_sections.scad
3 | *
4 | * @copyright Justin Lin, 2017
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-cross_sections.html
8 | *
9 | **/
10 |
11 | use
12 | use
13 | use
14 |
15 | function cross_sections(shape_pts, path_pts, angles, twist = 0, scale = 1.0) =
16 | let(
17 | len_path_pts_minus_one = len(path_pts) - 1,
18 | sh_pts = len(shape_pts[0]) == 3 ? [for(p = shape_pts) [each p, 1]] : [for(p = shape_pts) [each p, 0, 1]],
19 | pth_pts = len(path_pts[0]) == 3 ? path_pts : [for(p = path_pts) [each p, 0]]
20 | )
21 | twist == 0 && scale == 1.0 ?
22 | [
23 | for(i = [0:len_path_pts_minus_one])
24 | let(transform_m = m_translation(pth_pts[i]) * m_rotation(angles[i]))
25 | [
26 | for(p = sh_pts)
27 | let(transformed = transform_m * p)
28 | [transformed.x, transformed.y, transformed.z]
29 | ]
30 | ] :
31 | let(
32 | twist_step = twist / len_path_pts_minus_one,
33 | scale_step_vt = (is_num(scale) ? let(s = scale - 1) [s, s, 0] : ([each scale, 0] - [1, 1, 0])) / len_path_pts_minus_one,
34 | one_s = [1, 1, 1]
35 | )
36 | [
37 | for(i = [0:len_path_pts_minus_one])
38 | let(
39 | transform_m = m_translation(pth_pts[i]) *
40 | m_rotation(angles[i]) *
41 | m_rotation(twist_step * i) *
42 | m_scaling(scale_step_vt * i + one_s)
43 | )
44 | [
45 | for(p = sh_pts)
46 | let(transformed = transform_m * p)
47 | [transformed.x, transformed.y, transformed.z]
48 | ]
49 | ];
50 |
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/_impl/_m_rotation_impl.scad:
--------------------------------------------------------------------------------
1 | use <../../__comm__/__to_ang_vect.scad>
2 |
3 | FINAL_ROW = [0, 0, 0, 1];
4 | function __m_rotation_q_rotation(a, v) =
5 | let(
6 | uv = v / norm(v),
7 | s = sin(a / 2) * uv,
8 | w = sin(a) * uv,
9 |
10 | xx = 2 * s.x ^ 2,
11 | yy = 2 * s.y ^ 2,
12 | zz = 2 * s.z ^ 2,
13 |
14 | xy = 2 * s.x * s.y,
15 | xz = 2 * s.x * s.z,
16 | yz = 2 * s.y * s.z
17 | )
18 | [
19 | [1 - yy - zz, xy - w.z, xz + w.y, 0],
20 | [xy + w.z, 1 - xx - zz, yz - w.x, 0],
21 | [xz - w.y, yz + w.x, 1 - xx - yy, 0],
22 | FINAL_ROW
23 | ];
24 |
25 | function __m_rotation_xRotation(a) =
26 | let(c = cos(a), s = sin(a))
27 | [
28 | [1, 0, 0, 0],
29 | [0, c, -s, 0],
30 | [0, s, c, 0],
31 | FINAL_ROW
32 | ];
33 |
34 | function __m_rotation_yRotation(a) =
35 | let(c = cos(a), s = sin(a))
36 | [
37 | [c, 0, s, 0],
38 | [0, 1, 0, 0],
39 | [-s, 0, c, 0],
40 | FINAL_ROW
41 | ];
42 |
43 | function __m_rotation_zRotation(a) =
44 | let(c = cos(a), s = sin(a))
45 | [
46 | [c, -s, 0, 0],
47 | [s, c, 0, 0],
48 | [0, 0, 1, 0],
49 | FINAL_ROW
50 | ];
51 |
52 | function __m_rotation_xyz_rotation(a) =
53 | let(ang = __to_ang_vect(a))
54 | __m_rotation_zRotation(ang[2]) * __m_rotation_yRotation(ang[1]) * __m_rotation_xRotation(ang[0]);
55 |
56 | function _m_rotation_impl(a, v) =
57 | (a == 0 || a == [0, 0, 0] || a == [0] || a == [0, 0]) ? [
58 | [1, 0, 0, 0],
59 | [0, 1, 0, 0],
60 | [0, 0, 1, 0],
61 | FINAL_ROW
62 | ] : (is_undef(v) ? __m_rotation_xyz_rotation(a) : __m_rotation_q_rotation(a, v));
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/_impl/_m_scaling_impl.scad:
--------------------------------------------------------------------------------
1 | function __m_scaling_to_3_elems_scaling_vect(s) =
2 | let(leng = len(s))
3 | leng == 3 ? s :
4 | leng == 2 ? [each s, 1] : [s.x, 1, 1];
5 |
6 | function __m_scaling_to_scaling_vect(s) = is_num(s) ? [s, s, s] : __m_scaling_to_3_elems_scaling_vect(s);
7 |
8 | FINAL_ROW = [0, 0, 0, 1];
9 | function _m_scaling_impl(s) =
10 | let(v = __m_scaling_to_scaling_vect(s))
11 | [
12 | [v.x, 0, 0, 0],
13 | [0, v.y, 0, 0],
14 | [0, 0, v.z, 0],
15 | FINAL_ROW
16 | ];
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/_impl/_m_translation_impl.scad:
--------------------------------------------------------------------------------
1 | function _to_3_elems_translation_vect(v) =
2 | let(leng = len(v))
3 | leng == 3 ? v :
4 | leng == 2 ? [each v, 0] : [v.x, 0, 0];
5 |
6 | function _to_translation_vect(v) = is_num(v) ? [v, 0, 0] : _to_3_elems_translation_vect(v);
7 |
8 | FINAL_ROW = [0, 0, 0, 1];
9 | function _m_translation_impl(v) =
10 | let(vt = _to_translation_vect(v))
11 | [
12 | [1, 0, 0, vt.x],
13 | [0, 1, 0, vt.y],
14 | [0, 0, 1, vt.z],
15 | FINAL_ROW
16 | ];
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/m_rotation.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * m_rotation.scad
3 | *
4 | * @copyright Justin Lin, 2019
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-m_rotation.html
8 | *
9 | **/
10 |
11 | use <_impl/_m_rotation_impl.scad>
12 |
13 | function m_rotation(a, v) = _m_rotation_impl(a, v);
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/m_scaling.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * m_scaling.scad
3 | *
4 | * @copyright Justin Lin, 2019
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-m_scaling.html
8 | *
9 | **/
10 |
11 | use <_impl/_m_scaling_impl.scad>
12 |
13 | function m_scaling(s) = _m_scaling_impl(s);
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/matrix/m_translation.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * m_translation.scad
3 | *
4 | * @copyright Justin Lin, 2019
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-m_translation.html
8 | *
9 | **/
10 |
11 | use <_impl/_m_translation_impl.scad>
12 |
13 | function m_translation(v) = _m_translation_impl(v);
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/ring_extrude.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * ring_extrude.scad
3 | *
4 | * @copyright Justin Lin, 2017
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-ring_extrude.html
8 | *
9 | **/
10 |
11 | use <__comm__/__frags.scad>
12 | use <__comm__/__ra_to_xy.scad>
13 | use
14 | use
15 |
16 | module ring_extrude(shape_pts, radius, angle = 360, twist = 0, scale = 1.0, triangles = "SOLID") {
17 | if(twist == 0 && scale == 1.0) {
18 | rotate_extrude(angle = angle)
19 | translate([radius, 0, 0])
20 | polygon(shape_pts);
21 | } else {
22 | a_step = 360 / __frags(radius);
23 |
24 | angles = is_num(angle) ? [0, angle] : angle;
25 |
26 | m = floor(angles[0] / a_step) + 1;
27 | n = floor(angles[1] / a_step);
28 |
29 | leng = radius * cos(a_step / 2);
30 |
31 | begin_r = leng / cos((m - 0.5) * a_step - angles[0]);
32 | end_r = leng / cos((n + 0.5) * a_step - angles[1]);
33 |
34 | angs = [
35 | [90, 0, angles[0]],
36 | each [for(i = m; i <= n; i = i + 1) [90, 0, a_step * i]]
37 | ];
38 |
39 | pts = [
40 | __ra_to_xy(begin_r, angles[0]),
41 | each [for(i = m; i <= n; i = i + 1) __ra_to_xy(radius, a_step * i)]
42 | ];
43 |
44 | is_angle_frag_end = angs[len(angs) - 1][2] == angles[1];
45 |
46 | all_angles = is_angle_frag_end ?
47 | angs : [each angs, [90, 0, angles[1]]];
48 |
49 | all_points = is_angle_frag_end ?
50 | pts : [each pts, __ra_to_xy(end_r, angles[1])];
51 |
52 | sections = cross_sections(shape_pts, all_points, all_angles, twist, scale);
53 |
54 | sweep(
55 | sections,
56 | triangles = triangles
57 | );
58 |
59 | // hook for testing
60 | test_ring_extrude(sections, angle);
61 | }
62 | }
63 |
64 | // Override it to test
65 | module test_ring_extrude(sections, angle) {
66 |
67 | }
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/sweep.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * sweep.scad
3 | *
4 | * @copyright Justin Lin, 2020
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-sweep.html
8 | *
9 | **/
10 |
11 | use
12 |
13 | module sweep(sections, triangles = "SOLID") {
14 |
15 | function side_indexes(sects, begin_idx = 0) =
16 | let(
17 | leng_sects = len(sects),
18 | leng_pts_sect = len(sects[0]),
19 | range_j = [begin_idx:leng_pts_sect:begin_idx + (leng_sects - 2) * leng_pts_sect],
20 | range_i = [0:leng_pts_sect - 1]
21 | )
22 | concat(
23 | [
24 | for(j = range_j, i = range_i)
25 | let(i2 = j + (i + 1) % leng_pts_sect)
26 | [
27 | j + i,
28 | i2,
29 | i2 + leng_pts_sect
30 | ]
31 | ],
32 | [
33 | for(j = range_j, i = range_i)
34 | let(ji = j + i)
35 | [
36 | ji,
37 | j + (i + 1) % leng_pts_sect + leng_pts_sect ,
38 | ji + leng_pts_sect
39 | ]
40 | ]
41 | );
42 |
43 |
44 | function the_same_after_twisting(f_sect, l_sect) =
45 | let(found = search([l_sect[0]], f_sect)[0], leng = len(l_sect))
46 | found != [] &&
47 | len([for(i = 0; l_sect[i] == f_sect[(found + i) % leng]; i = i + 1) undef]) == leng;
48 |
49 | function to_v_pts(sects) = [for(sect = sects) each sect];
50 |
51 | module solid_sections(sects) {
52 | leng_sects = len(sects);
53 | leng_pts_sect = len(sects[0]);
54 | first_sect = sects[0];
55 | last_sect = sects[leng_sects - 1];
56 |
57 | v_pts = to_v_pts(sects);
58 |
59 | begin_end_the_same =
60 | first_sect == last_sect || the_same_after_twisting(first_sect, last_sect);
61 |
62 | if(begin_end_the_same) {
63 | f_idxes = side_indexes(sects);
64 |
65 | polyhedron(v_pts, f_idxes);
66 |
67 | // hook for testing
68 | test_sweep_solid(v_pts, f_idxes, triangles);
69 | } else {
70 | from = leng_pts_sect * (leng_sects - 1);
71 | f_idxes = [
72 | [each [leng_pts_sect - 1:-1:0]],
73 | each side_indexes(sects),
74 | [each [from:from + leng_pts_sect - 1]]
75 | ];
76 |
77 | polyhedron(v_pts, f_idxes);
78 |
79 | // hook for testing
80 | test_sweep_solid(v_pts, f_idxes, triangles);
81 | }
82 | }
83 |
84 | module hollow_sections(sects) {
85 | leng_sects = len(sects);
86 | leng_sect = len(sects[0]);
87 | half_leng_sect = leng_sect / 2;
88 | half_leng_v_pts = leng_sects * half_leng_sect;
89 |
90 | function strip_sects(begin_idx, end_idx) =
91 | let(range = [begin_idx:end_idx])
92 | [for(sect = sects) [for(j = range) sect[j]]];
93 |
94 | range = [0:half_leng_sect - 1];
95 | function first_idxes() =
96 | [
97 | for(i = range)
98 | let(i3 = (i + 1) % half_leng_sect)
99 | [
100 | i,
101 | i + half_leng_v_pts,
102 | i3 + half_leng_v_pts,
103 | i3
104 | ]
105 | ];
106 |
107 | function last_idxes(begin_idx) =
108 | [
109 | for(i = range)
110 | let(bi = begin_idx + i, i2 = begin_idx + (i + 1) % half_leng_sect)
111 | [
112 | bi,
113 | i2,
114 | i2 + half_leng_v_pts,
115 | bi + half_leng_v_pts
116 | ]
117 | ];
118 |
119 | outer_sects = strip_sects(0, half_leng_sect - 1);
120 | inner_sects = strip_sects(half_leng_sect, leng_sect - 1);
121 |
122 | outer_idxes = side_indexes(outer_sects);
123 | inner_idxes = [for(idxes = side_indexes(inner_sects, half_leng_v_pts)) reverse(idxes)];
124 |
125 | first_outer_sect = outer_sects[0];
126 | last_outer_sect = outer_sects[leng_sects - 1];
127 | first_inner_sect = inner_sects[0];
128 | last_inner_sect = inner_sects[leng_sects - 1];
129 |
130 | leng_pts_sect = len(first_outer_sect);
131 |
132 | begin_end_the_same =
133 | (first_outer_sect == last_outer_sect && first_inner_sect == last_inner_sect) ||
134 | (
135 | the_same_after_twisting(first_outer_sect, last_outer_sect) &&
136 | the_same_after_twisting(first_inner_sect, last_inner_sect)
137 | );
138 |
139 | v_pts = concat(to_v_pts(outer_sects), to_v_pts(inner_sects));
140 |
141 | if(begin_end_the_same) {
142 | f_idxes = concat(outer_idxes, inner_idxes);
143 |
144 | polyhedron(
145 | v_pts,
146 | f_idxes
147 | );
148 |
149 | // hook for testing
150 | test_sweep_solid(v_pts, f_idxes, triangles);
151 | } else {
152 | f_idxes = concat(
153 | first_idxes(),
154 | outer_idxes,
155 | inner_idxes,
156 | last_idxes(half_leng_v_pts - half_leng_sect)
157 | );
158 |
159 | polyhedron(
160 | v_pts,
161 | f_idxes
162 | );
163 |
164 | // hook for testing
165 | test_sweep_solid(v_pts, f_idxes, triangles);
166 | }
167 | }
168 |
169 | module triangles_defined_sections() {
170 | faces = [
171 | [0, 1, 2], [3, 5, 4],
172 | [1, 3, 4], [2, 1, 4], [2, 3, 0],
173 | [0, 3, 1], [2, 4, 5], [2, 5, 3]
174 | ];
175 | module two_sections(section1, section2) {
176 | for(idx = triangles) {
177 | polyhedron(
178 | concat(
179 | [for(i = idx) section1[i]],
180 | [for(i = idx) section2[i]]
181 | ),
182 | faces
183 | );
184 | }
185 | }
186 |
187 | for(i = [0:len(sections) - 2]) {
188 | two_sections(
189 | sections[i],
190 | sections[i + 1]
191 | );
192 | }
193 | }
194 |
195 | if(triangles == "SOLID") {
196 | solid_sections(sections);
197 | } else if(triangles == "HOLLOW") {
198 | hollow_sections(sections);
199 | }
200 | else {
201 | triangles_defined_sections();
202 | }
203 | }
204 |
205 | // override it to test
206 |
207 | module test_sweep_solid(points, faces, triangles) {
208 |
209 | }
--------------------------------------------------------------------------------
/src/modules/thridparty/dotSCAD/util/reverse.scad:
--------------------------------------------------------------------------------
1 | /**
2 | * reverse.scad
3 | *
4 | * @copyright Justin Lin, 2019
5 | * @license https://opensource.org/licenses/lgpl-3.0.html
6 | *
7 | * @see https://openhome.cc/eGossip/OpenSCAD/lib3x-reverse.html
8 | *
9 | **/
10 |
11 | function reverse(lt) = [for(i = len(lt) - 1; i > -1; i = i - 1) lt[i]];
--------------------------------------------------------------------------------
/src/modules/thridparty/ub_caliper.scad:
--------------------------------------------------------------------------------
1 | // works from OpenSCAD version 2021 or higher maintained at https://github.com/UBaer21/UB.scad
2 |
3 | /** \mainpage
4 | * ##Open SCAD library www.openscad.org
5 | * **Author:** ulrich.baer+UBscad@gmail.com (mail me if you need help - i am happy to assist)
6 | */
7 |
8 | include
9 | include
10 |
11 | /** \page Helper
12 | Caliper() shows a distance and can be used as annotation
13 | \brief Caliper shows a distance and can be used as annotation
14 | \param l length to show
15 | \param in direction
16 | \param center centered length
17 | \param messpunkt show / size of gizmo
18 | \param translate translates the text and arrow
19 | \param end differnt end options [0:none,1:triangle, 2:square, 3:arrow in, 4:arrow out]
20 | \param h height while end=0,3,4 can be 2D if h=0
21 | \param on switch on=2 if Caliper should be rendered
22 | \param l2 arrow width
23 | \param txt l+mm is used optional text
24 | \param txt2 optional second text
25 | \param size size
26 | */
27 | //Caliper(end=0,messpunkt=0,in=1,translate=[20,-5],center=+1);
28 |
29 | //Caliper(end=3);
30 | //Caliper(end=3,txt2="X—Length",in=+1);
31 |
32 |
33 | module Caliper(l=40,in=1,center=true,messpunkt=true,translate=[0,0,0],end=1,h,on=$preview,l2,txt,txt2,size=$vpd/15,render,s,t,cx,cy,help=false){
34 |
35 | on=render?render:on;
36 | s=s?s:size;
37 | txt=is_undef(txt)?str(l,end==2?"":"mm"):str(txt);
38 | center=is_bool(center)?center?1:0:center;
39 | textl=in>1?s/2.5:s/4*(len(str(txt)));// end=0,3,4 use own def
40 | line=s/20;
41 | translate=t?v3(t):v3(translate);
42 | //l2=is_undef(l2)?s:l2;
43 |
44 |
45 | if(on&&$preview||on==2)translate(translate)translate(in>1?center?[0,0]:[0,l/2]:center?[0,0]:[l/2,0]){
46 | if(end==1)Col(5){
47 | h=is_undef(h)?1.1:max(minVal,h);
48 | rotate(in?in==2?90:in==3?-90:180:0)linear_extrude(h,center=true)Mklon(tx=l/2,mz=0)polygon([[max(-5,-l/3),0],[0,s],[0,0]]);
49 | rotate(in?in==2?90:in==3?-90:180:0)linear_extrude(h,center=true)Mklon(tx=-l/2,mz=0)polygon([[max(-5,-l/3),0],[0,-s],[0,0]]);
50 |
51 | Text(h=h+.1,text=txt,center=true,size=s/4,trueSize="size",cx=cx,cy=cy);
52 | }
53 | else if(end==2)Col(3)union(){
54 | h=is_undef(h)?1.1:max(minVal,h);
55 | rotate(in?in==2?90:in==3?-90:180:0)MKlon(tx=l/2)T(-(l-textl*2)/4,0)cube([max(l-textl*2,.01)/2,line,h],center=true);
56 | rotate(in?in==2?90:in==3?-90:180:0)MKlon(tx=l/2)cube([line,s,h],center=true);
57 | translate([(l1?l/2+s/4+1:0,0])
59 | Text(h=h+.1,text=txt,center=true,size=s/2,trueSize="size",cx=cx,cy=cy);
60 | if(l=2) translate([0,.25])square([line,l+.5],true);
62 |
63 | }
64 | else Col(1) {
65 | h=is_undef(h)?.1:h;
66 | if(h) linear_extrude(h,convexity=5) Dimensioning();
67 | else Dimensioning();
68 | }
69 | }
70 |
71 | Echo("Caliper will render",color="warning",condition=on==2);
72 | if(h&&end&&on&&end<3)
73 | Pivot(messpunkt=messpunkt,p0=translate,active=[1,1,1,1,norm(translate)]);
74 |
75 | HelpTxt("Caliper",[
76 | "l",l,
77 | "in",in,
78 | "size",size,
79 | "center",center,
80 | "messpunkt",messpunkt,
81 | "translate",translate,
82 | "end",end,
83 | "h",h,
84 | "on",on,
85 | "l2",l2,
86 | "txt",txt,
87 | "txt2",txt2]
88 | ,help);
89 |
90 |
91 | module Dimensioning (t=translate){
92 | s=s==$vpd/15?5:s;
93 | txt2=txt2?str(txt2):"";
94 | line=s/20;
95 | textS=len(txt2)?s/2*.72:s*.72;//text size
96 | l2=l2?l2:s/1.5;
97 | textl=in>1?(len(txt2)?3:1.5)*textS:1+textS*max(len(txt),len(txt2))*0.95;
98 | arrowL=min(l/6,s);
99 | textOut=ll/2&& (in==2||in==3) )||(abs(translate.x)>l/2&&in!=2&&in!=3); // is text outside l
100 | textOffset=l0)rotate(in?in==2?90:in==3?-90:180:0){
105 | if(!textOut&&l-textl - diffT*2>0) T(-l/2)T((l-textl)/4 +diffT/2,0)square([(l-textl)/2-diffT,line],center=true);
106 | if(!textOut&&l-textl + diffT*2>0) T( l/2)T(-(l-textl)/4 +diffT/2,0)square([(l-textl)/2+diffT,line],center=true);
107 | }
108 | //End lines vertical
109 | translate(in!=2&&in!=3?[-translate.x,0]:[0,-translate.y])rotate(in?in==2?90:in==3?-90:180:0){
110 | MKlon(tx=l/2){
111 | T(end?end==4?-line/2:+line/2:0) square([line,s],center=true);
112 | if(end)rotate(end==4?180:0)Pfeil([0,arrowL],b=[line,l2],center=[-1,1],name=false);
113 | }
114 | if(textOut) square([l,line],true); // Verbindung Pfeile
115 | // text pos
116 | translate(in!=2&&in!=3?[(in?1:-1) * -translate.x,0]:[(in==2?1:-1)*translate.y,0]){
117 | translate([textOffset,0])rotate(in>1?-90:180){
118 | if(len(txt2))translate([0,-textS/1.5])Text(h=0,text=txt2,center=true,size=textS,trueSize="size",name=false,cx=cx,cy=cy);
119 | translate([0,len(txt2)?textS/1.5:0])Text(h=0,text=txt,center=true,size=textS,trueSize="size",name=false,cx=cx,cy=cy);
120 | }
121 | }
122 | // verbindung text ausserhalb
123 | tOutDist=(in!=2&&in!=3)? t.x *(in ?-1:1) + textOffset :
124 | t.y *(in==3?-1:1) + textOffset ;
125 |
126 | if(textOut&&tOutDist)rotate(tOutDist<0?180:0)translate([0,-line/2])square([abs(tOutDist)-textl/2 ,line]);
127 | }
128 |
129 | // verlängerungen translate auf 0.5
130 | mkL=end?end==4?l/2-line:l/2:l/2-line/2;
131 | if(abs(translate.y)>(l2/2+.5)&&in!=2&&in!=3)translate([-translate.x,0])MKlon(tx=mkL) mirror([0,translate.y>0?1:0,0])square([line,abs(translate.y)-.5],false);
132 | if(abs(translate.x)>(l2/2+.5)&&(in==2||in==3))translate([0,-translate.y])MKlon(ty=mkL) mirror([translate.x>0?1:0,0,0])square([abs(translate.x)-.5,line],false);
133 | //if(translate.x) mirror([translate.x>0?1:0,0,0])T(l/2,-line/2)square([abs(translate.x),line],false);
134 |
135 | }// end Dimensioning
136 |
137 |
138 | }// end Caliper
--------------------------------------------------------------------------------
/src/modules/thridparty/ub_helptxt.scad:
--------------------------------------------------------------------------------
1 | // works from OpenSCAD version 2021 or higher maintained at https://github.com/UBaer21/UB.scad
2 |
3 | /** \mainpage
4 | * ##Open SCAD library www.openscad.org
5 | * **Author:** ulrich.baer+UBscad@gmail.com (mail me if you need help - i am happy to assist)
6 | */
7 |
8 | include
9 |
10 | // display the module variables in a copyable format
11 | module HelpTxt(titel,string,help){
12 | help=is_undef(help)?is_undef($helpM)?false:
13 | $helpM:
14 | help;
15 | idxON=is_undef($idxON)?false:$idxON?true:false;
16 | idx=is_undef($idx)||idxON?false:is_list($idx)?norm($idx):$idx;
17 |
18 | joinArray= function(in,out="",pos=0) pos>=len(in)?out:
19 | joinArray(in=in,out=str(out,in[pos]),pos=pos +1);
20 |
21 | helpText=[for(i=[0:2:len(string)-1])str(string[i],"=",string[i+1],",\n ")];
22 | if(version()[0]<2021){
23 | if(help)if(is_list(string))echo(
24 |
25 | str(" Help ",titel, "(",
26 | helpText[0]
27 | ,helpText[1]?helpText[1]:""
28 | ,helpText[2]?helpText[2]:""
29 | ,helpText[3]?helpText[3]:""
30 | ,helpText[4]?helpText[4]:""
31 | ,helpText[5]?helpText[5]:""
32 | ,helpText[6]?helpText[6]:""
33 | ,helpText[7]?helpText[7]:""
34 | ,helpText[8]?helpText[8]:""
35 | ,helpText[9]?helpText[9]:""
36 | ,helpText[10]?helpText[10]:""
37 | ,helpText[11]?helpText[11]:""
38 | ,helpText[12]?helpText[12]:""
39 | ,helpText[13]?helpText[13]:""
40 | ,helpText[14]?helpText[14]:""
41 | ,helpText[15]?helpText[15]:""
42 | ,helpText[16]?helpText[16]:""
43 | ,helpText[17]?helpText[17]:""
44 | ,helpText[18]?helpText[18]:""
45 | ,helpText[19]?helpText[19]:""
46 | ,helpText[20]?helpText[20]:""
47 | ,helpText[21]?helpText[21]:""
48 | ,helpText[22]?helpText[22]:""
49 | ,helpText[23]?helpText[23]:""
50 | ,helpText[24]?helpText[24]:""
51 | ,helpText[25]?helpText[25]:""
52 | ,helpText[26]?helpText[26]:""
53 | ,helpText[27]?helpText[27]:""
54 | ,helpText[28]?helpText[28]:""
55 | ,helpText[29]?helpText[29]:""
56 |
57 |
58 | // ," name=",name,
59 | ," help);"));
60 | else HelpTxt("Help",["titel",titel,"string",["string","data","help",help],"help",help],help=1);
61 | }
62 | else{ // current versions help
63 | if(help&&!idx)if(is_list(string))echo(
64 |
65 | str("🟪\nHelp ",titel, "(\n ",
66 | joinArray(helpText)
67 | ,"help=",help,"\n);\n"));
68 | else HelpTxt("Help",["titel",titel,"string",string,"help",help],help=1);
69 | }
70 | }
--------------------------------------------------------------------------------
/src/modules/thridparty/ub_hexgrid.scad:
--------------------------------------------------------------------------------
1 | // works from OpenSCAD version 2021 or higher maintained at https://github.com/UBaer21/UB.scad
2 |
3 | /** \mainpage
4 | * ##Open SCAD library www.openscad.org
5 | * **Author:** ulrich.baer+UBscad@gmail.com (mail me if you need help - i am happy to assist)
6 | */
7 |
8 | include
9 | include
10 |
11 | /** \name Grid \page Modifier
12 | Grid() children(); creates a grid of children
13 | \param e elements [x,y]
14 | \param es element spacing [x,y]
15 | \param s total space ↦ es
16 | \param center true/false
17 | */
18 | // multiply children in a given matrix (e= number es =distance)
19 |
20 | module Grid(e=[2,2,1],es=10,s,center=true,name,help){
21 |
22 | name=is_undef(name)?is_undef($info)?false:
23 | $info:
24 | name;
25 |
26 | function n0(e)=is_undef(e)?1:max(round(e),0);
27 | function n0s(e)=max(e-1,1);// e-1 must not be 0
28 | center=is_list(center)?v3(center):[center,center,center];
29 | e=is_list(e)?is_num(e[2])?[max(round(e[0]),0),max(round(e[1]),0),n0(e[2])]:
30 | [round(e.x),round(e.y),1]: // z = 1
31 | es[2]?[n0(e),n0(e),n0(e)]:
32 | [n0(e),n0(e),1];
33 |
34 | es=is_undef(s)?is_list(es)?is_num(es[2])?es:
35 | concat(es,[0]):
36 | is_undef(es)?[0:0:0]:
37 | [es,es,es]:
38 | is_list(s)?is_num(s[2])?[s[0]/n0s(e[0]),s[1]/n0s(e[1]),s[2]/n0s(e[2])]:
39 | [s[0]/n0s(e[0]),s[1]/n0s(e[1]),0]:
40 | [s/n0s(e[0]),s/n0s(e[1]),s/n0s(e[2])];
41 |
42 | MO(!$children);
43 | InfoTxt("Grid",[str("Gridsize(",e,")"),str(e[0]*e[1]*e[2]," elements= ",(e[0]-1)*es[0],"×",(e[1]-1)*es[1],"×",(e[2]-1)*es[2],"mm \n element spacing= ",es," mm",
44 |
45 | center.x?str("\n\tX ",-(e[0]-1)*es[0]/2," ⇔ ",(e[0]-1)*es[0]/2," mm"):"",
46 | center.y?str("\n\tY ",-(e[1]-1)*es[1]/2," ⇔ ",(e[1]-1)*es[1]/2," mm"):"",
47 | center.z?str("\n\tZ ",-(e[2]-1)*es[2]/2," ⇔ ",(e[2]-1)*es[2]/2," mm"):"")
48 | ],name);
49 |
50 |
51 |
52 | HelpTxt("Grid",[
53 | "e",e
54 | ,"es",es
55 | ,"s",[(e[0]-1)*es[0],(e[1]-1)*es[1],(e[2]-1)*es[2]]
56 | ,"center",center
57 | ,"name",name]
58 | ,help);
59 |
60 | centerPos=[
61 | center.x?((1-e[0])*es[0])/2:0,
62 | center.y?((1-e[1])*es[1])/2:0,
63 | center.z?((1-e[2])*es[2])/2:0];
64 |
65 | if(e.x&&e.y&&e.z) for(x=[0:e[0]-1],y=[0:e[1]-1],z=[0:e[2]-1]){
66 | $idx=[x,y,z];
67 | $idx2=[e.y*e.x*z+e.y*y+x,e.y*e.x*z+e.x*x+y];
68 | $pos=[x*es.x,y*es.y,z*es.z]+centerPos;
69 | $info=norm($idx)?false:name;
70 | $tab=is_undef($tab)?1:b($tab,false)+1;
71 | $es=es;
72 | // $helpM=norm($idx)?false:$helpM;
73 | translate([x*es[0],y*es[1],z*es[2]]+centerPos)children();
74 | }
75 | }
76 |
77 | //Grid(4)Text($pos.xy,size=3);
78 |
79 | // Grid but with alternating row offset - hex or circle packing
80 | //HexGrid()circle(d=$es.y);
81 | //HexGrid()circle(d=Umkreis(6,$d-.1),$fn=6);
82 |
83 | /** \name HexGrid \page Modifier
84 | HexGrid() children(); creates an interlaced grid of children
85 | \param e elements [x,y] e+.1 or -.1 will change the pattern
86 | \param es element spacing [x,y]
87 | \param center true/false or -7 ⇔ 7 for x shift
88 | \param $d $r $es $idx $idx2 $pos output for children
89 | \param name help name help
90 | */
91 | module HexGrid(e=[11,4],es=5,center=true,name,help){
92 |
93 | es=is_list(es)?es:[es*sin(60),es];
94 | e=is_list(e)?e:[e,e,1];
95 | $d=es.y;
96 | $r=$d/2;
97 | icenter=abs(b(center,bool=false));
98 | //shifting for center and pattern change
99 | yCor=(is_undef(useVersion)||useVersion>23.300)&&icenter?-es.y/4:0;
100 | shift=[0,e.y>round(e.y)?-es.y/2:e.y0||($idx.x)%2)children();
122 | }
123 | else children();
124 | }
125 |
126 | MO(!$children);
127 | // info of Grid will be used additional for changed pattern this:
128 | if(e.y%1)InfoTxt("HexGrid",["elements",round(e.x)*round(e.y)*(e.z?e.z:1)
129 | - (e.y