├── .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 |
Image to DXF
Openscad exampleOrca Studio example
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 | [![Watch the tutorial](https://img.youtube.com/vi/K45Y8rKlYDY/0.jpg)](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