diff --git a/readme.md b/readme.md
index e0ceb16..725201f 100644
--- a/readme.md
+++ b/readme.md
@@ -24,24 +24,24 @@ A list of changes classified as breaking, additions or fixes is maintained in [C
Axials | Jack | Rails | Box | Annotation | BOM |
Ball_bearings | KP_pillow_blocks | Ring_terminals | Butt_box | Bezier | Clip |
Batteries | LDRs | Rockers | Cable_grommets | Catenary | Global |
- Belts | LED_meters | Rod | Camera_housing | Dogbones | Polyholes |
- Blowers | LEDs | SCS_bearing_blocks | Carriers | Fillet | Rounded_rectangle |
- Bulldogs | Leadnuts | SK_brackets | Corner_block | Gears | Sphere |
- Buttons | Light_strips | SMDs | Door_hinge | Hanging_hole | Teardrops |
- Cable_strips | Linear_bearings | SSRs | Door_latch | Horiholes | |
- Cameras | Magnets | Screws | Drag_chain | Layout | |
- Circlips | Mains_sockets | Sealing_strip | Fan_guard | Maths | |
- Components | Microswitches | Shaft_couplings | Fixing_block | Offset | |
- DIP | Microview | Sheets | Flat_hinge | Quadrant | |
- D_connectors | Modules | Spades | Foot | Round | |
- Displays | Nuts | Spools | Handle | Rounded_cylinder | |
- Extrusion_brackets | O_ring | Springs | PCB_mount | Rounded_polygon | |
- Extrusions | Opengrab | Stepper_motors | PSU_shroud | Rounded_right_triangle | |
- Fans | PCB | Swiss_clips | Pocket_handle | Sector | |
- Fuseholder | PCBs | Toggles | Press_fit | Sweep | |
- Geared_steppers | PSUs | Transformers | Printed_box | Thread | |
- Green_terminals | Panel_meters | Tubings | Printed_pulleys | Tube | |
- Hot_ends | Pillars | Variacs | Ribbon_clamp | | |
+ Belts | LED_meters | Rod | Camera_housing | Core_xy | Polyholes |
+ Blowers | LEDs | SCS_bearing_blocks | Carriers | Dogbones | Rounded_rectangle |
+ Bulldogs | Leadnuts | SK_brackets | Corner_block | Fillet | Sphere |
+ Buttons | Light_strips | SMDs | Door_hinge | Gears | Teardrops |
+ Cable_strips | Linear_bearings | SSRs | Door_latch | Hanging_hole | |
+ Cameras | Magnets | Screws | Drag_chain | Horiholes | |
+ Circlips | Mains_sockets | Sealing_strip | Fan_guard | Layout | |
+ Components | Microswitches | Shaft_couplings | Fixing_block | Maths | |
+ DIP | Microview | Sheets | Flat_hinge | Offset | |
+ D_connectors | Modules | Spades | Foot | Quadrant | |
+ Displays | Nuts | Spools | Handle | Round | |
+ Extrusion_brackets | O_ring | Springs | PCB_mount | Rounded_cylinder | |
+ Extrusions | Opengrab | Stepper_motors | PSU_shroud | Rounded_polygon | |
+ Fans | PCB | Swiss_clips | Pocket_handle | Rounded_right_triangle | |
+ Fuseholder | PCBs | Toggles | Press_fit | Sector | |
+ Geared_steppers | PSUs | Transformers | Printed_box | Sweep | |
+ Green_terminals | Panel_meters | Tubings | Printed_pulleys | Thread | |
+ Hot_ends | Pillars | Variacs | Ribbon_clamp | Tube | |
Hygrometer | Pin_headers | Veroboard | SSR_shroud | | |
IECs | Pulleys | Washers | Screw_knob | | |
Inserts | | Wire | Socket_box | | |
@@ -5574,6 +5574,69 @@ The coordinates of the lowest point on the curve can be retrieved by calling `ca
![catenary](tests/png/catenary.png)
+Top
+
+---
+
+## Core_xy
+Parameterised Core XY implementation. Draws the belts and provides utilities for positioning the pulleys.
+
+The belts are positioned according the bottom left "anchor" pulley and the top right drive pulley.
+Implementation has the following features:
+1. The drive and idler pulleys may be different sizes.
+2. The belt separation is parameterised.
+3. The separation of the plain and toothed pulleys on the Y carriages is parameterised, in both the X and the Y direction.
+4. The drive pulleys may be offset in the X and Y directions. If this is done, extra idler pulleys are added. This
+allows flexible positioning of the motors.
+
+[utils/core_xy.scad](utils/core_xy.scad) Implementation.
+
+[tests/core_xy.scad](tests/core_xy.scad) Code for this example.
+
+### Properties
+| Function | Description |
+|:--- |:--- |
+| `coreXY_belt(type)` | Belt type |
+| `coreXY_drive_pulley(type)` | Drive pulley type |
+| `coreXY_lower_belt_colour(type)` | Colour of the lower belt |
+| `coreXY_lower_tooth_colour(type)` | Colour of the lower belt's teeth |
+| `coreXY_plain_idler(type)` | Plain idler type |
+| `coreXY_toothed_idler(type)` | Toothed idler type |
+| `coreXY_upper_belt_colour(type)` | Colour of the upper belt |
+| `coreXY_upper_tooth_colour(type)` | Colour of the upper belt's teeth |
+
+### Functions
+| Function | Description |
+|:--- |:--- |
+| `coreXY_coincident_separation(type)` | Value of x, y separation to make y-carriage pulleys coincident |
+| `coreXY_drive_plain_idler_offset(type)` | Offset of plain drive idler pulley |
+| `coreXY_drive_pulley_x_alignment(type)` | Belt alignment offset of the drive pulley relative to the anchor pulley |
+| `coreXY_drive_toothed_idler_offset(type)` | Offset of toothed drive idler pulley |
+| `coreXY_plain_idler_offset(type)` | Offset of y-carriage plain idler |
+| `coreXY_toothed_idler_offset(type)` | offset of y-carriage toothed idler |
+
+### Modules
+| Module | Description |
+|:--- |:--- |
+| `coreXY(type, size, pos, separation, x_gap, plain_idler_offset = 0, upper_drive_pulley_offset, lower_drive_pulley_offset, show_pulleys = false)` | Wrapper module to draw both belts of a coreXY setup |
+| `coreXY_belts(type, carriagePosition, coreXYPosBL, coreXYPosTR, separation, x_gap = 20, upper_drive_pulley_offset = [0, 0], lower_drive_pulley_offset = [0, 0], show_pulleys = false)` | Draw the coreXY belts |
+| `coreXY_half(type, size, pos, separation_y = 0, x_gap = 0, plain_idler_offset = 0, drive_pulley_offset = [0, 0], show_pulleys = false, lower_belt = false, hflip = false)` | Draw one belt of a coreXY setup |
+
+![core_xy](tests/png/core_xy.png)
+
+### Vitamins
+| Qty | Module call | BOM entry |
+| ---:|:--- |:---|
+| 1 | `belt(GT2x6, [ ... ], [10.0078, 11.69], [0, -24.686])` | Belt GT2 x 6mm x 742mm |
+| 1 | `belt(GT2x6, [ ... ], [10.0078, 11.69], [0, -24.686])` | Belt GT2 x 6mm x 852mm |
+| 7 | `pulley(GT2x16_toothed_idler)` | Pulley GT2 idler 16 teeth |
+| 3 | `pulley(GT2x16_plain_idler)` | Pulley GT2 idler smooth 9.63mm |
+| 2 | `pulley(GT2x20ob_pulley)` | Pulley GT2OB 20 teeth |
+| 8 | `screw(M3_cap_screw, 20)` | Screw M3 cap x 20mm |
+| 4 | `screw(M3_grub_screw, 6)` | Screw M3 grub x 6mm |
+| 2 | `NEMA(NEMA17M)` | Stepper motor NEMA17 x 40mm |
+
+
Top
---
diff --git a/tests/core_xy.scad b/tests/core_xy.scad
new file mode 100644
index 0000000..ca549e0
--- /dev/null
+++ b/tests/core_xy.scad
@@ -0,0 +1,134 @@
+//
+// NopSCADlib Copyright Chris Palmer 2020
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+include <../core.scad>
+include <../vitamins/pulleys.scad>
+include <../vitamins/screws.scad>
+include <../vitamins/stepper_motors.scad>
+include <../vitamins/washers.scad>
+
+include <../utils/core_xy.scad>
+
+
+module coreXY_belts_test() {
+ coreXY_type = coreXY_GT2_20_16;
+ plain_idler = coreXY_plain_idler(coreXY_type);
+ toothed_idler = coreXY_toothed_idler(coreXY_type);
+
+ coreXYPosBL = [0, 0, 0];
+ coreXYPosTR = [200, 150, 0];
+ separation = [0, coreXY_coincident_separation(coreXY_type).y, pulley_height(plain_idler) + washer_thickness(M3_washer)];
+ pos = [100, 50];
+
+ upper_drive_pulley_offset = [40, 10];
+ lower_drive_pulley_offset = [0, 0];
+
+ coreXY_belts(coreXY_type,
+ carriagePosition = pos,
+ coreXYPosBL = coreXYPosBL,
+ coreXYPosTR = coreXYPosTR,
+ separation = separation,
+ x_gap = 10,
+ upper_drive_pulley_offset = upper_drive_pulley_offset,
+ lower_drive_pulley_offset = lower_drive_pulley_offset,
+ show_pulleys = true);
+
+
+ translate([coreXYPosBL.x + separation.x/2, coreXYPosTR.y + upper_drive_pulley_offset.y, separation.z/2]) {
+ // add the upper drive pulley stepper motor
+ translate([coreXY_drive_pulley_x_alignment(coreXY_type) + upper_drive_pulley_offset.x, 0, -pulley_height(coreXY_drive_pulley(coreXY_type))])
+ NEMA(NEMA17M);
+
+ // add the screws for the upper drive offset idler pulleys if required
+ if (upper_drive_pulley_offset.x > 0) {
+ translate(coreXY_drive_plain_idler_offset(coreXY_type))
+ translate_z(-pulley_offset(plain_idler))
+ screw(M3_cap_screw, 20);
+ translate(coreXY_drive_toothed_idler_offset(coreXY_type))
+ translate_z(-pulley_offset(toothed_idler))
+ screw(M3_cap_screw, 20);
+ } else if (upper_drive_pulley_offset.x < 0) {
+ translate([-pulley_od(plain_idler), coreXY_drive_plain_idler_offset(coreXY_type).y])
+ translate_z(-pulley_offset(plain_idler))
+ screw(M3_cap_screw, 20);
+ translate([2*coreXY_drive_pulley_x_alignment(coreXY_type), coreXY_drive_toothed_idler_offset(coreXY_type).y])
+ translate_z(-pulley_offset(toothed_idler))
+ screw(M3_cap_screw, 20);
+ }
+ }
+
+ translate([coreXYPosTR.x - separation.x/2, coreXYPosTR.y + lower_drive_pulley_offset.y, -separation.z/2]) {
+ // add the lower drive pulley stepper motor
+ translate([-coreXY_drive_pulley_x_alignment(coreXY_type) + lower_drive_pulley_offset.x, 0, -pulley_height(coreXY_drive_pulley(coreXY_type))])
+ NEMA(NEMA17M);
+
+ // add the screws for the lower drive offset idler pulleys if required
+ if (lower_drive_pulley_offset.x < 0) {
+ translate([-coreXY_drive_plain_idler_offset(coreXY_type).x, coreXY_drive_plain_idler_offset(coreXY_type).y])
+ translate_z(-pulley_offset(plain_idler))
+ screw(M3_cap_screw, 20);
+ translate(coreXY_drive_toothed_idler_offset(coreXY_type))
+ translate_z(-pulley_offset(toothed_idler))
+ screw(M3_cap_screw, 20);
+ } else if (lower_drive_pulley_offset.x > 0) {
+ translate([pulley_od(plain_idler), coreXY_drive_plain_idler_offset(coreXY_type).y])
+ translate_z(-pulley_offset(plain_idler))
+ screw(M3_cap_screw, 20);
+ translate([-2*coreXY_drive_pulley_x_alignment(coreXY_type), coreXY_drive_toothed_idler_offset(coreXY_type).y])
+ translate_z(-pulley_offset(toothed_idler))
+ screw(M3_cap_screw, 20);
+ }
+ }
+
+ // add the screw for the left upper idler pulley
+ translate([coreXYPosBL.x + separation.x/2, coreXYPosBL.y, separation.z])
+ screw(M3_cap_screw, 20);
+
+ // add the screw for the right upper idler pulley
+ translate([coreXYPosTR.x + separation.x/2, coreXYPosBL.y, separation.z])
+ screw(M3_cap_screw, 20);
+
+ if (separation.x != 0) {
+ // add the screw for the left lower idler pulley
+ translate([coreXYPosBL.x - separation.x/2, coreXYPosBL.y, 0])
+ screw(M3_cap_screw, 20);
+
+ // add the screw for the right lower idler pulley
+ translate([coreXYPosTR.x - separation.x/2, coreXYPosBL.y, 0])
+ screw(M3_cap_screw, 20);
+ }
+
+ translate([-separation.x/2, pos.y + coreXYPosBL.y -separation.y/2, -separation.z/2 + pulley_height(plain_idler)/2]) {
+ // add the screw for the left Y carriage toothed idler
+ translate([coreXYPosBL.x, coreXY_toothed_idler_offset(coreXY_type).y, 0])
+ screw(M3_cap_screw, 20);
+ // add the screw for the left Y carriage plain idler
+ translate([coreXYPosBL.x + separation.x + coreXY_plain_idler_offset(coreXY_type).x, separation.y + coreXY_plain_idler_offset(coreXY_type).y, separation.z])
+ screw(M3_cap_screw, 20);
+ // add the screw for the right Y carriage toothed idler
+ translate([coreXYPosTR.x + separation.x, coreXY_toothed_idler_offset(coreXY_type).y, separation.z])
+ screw(M3_cap_screw, 20);
+ // add the screw for the right Y carriage plain idler
+ translate([coreXYPosTR.x - coreXY_plain_idler_offset(coreXY_type).x, separation.y + coreXY_plain_idler_offset(coreXY_type).y, 0])
+ screw(M3_cap_screw, 20);
+ }
+}
+
+if ($preview)
+ coreXY_belts_test();
diff --git a/tests/png/core_xy.png b/tests/png/core_xy.png
new file mode 100644
index 0000000..17dd067
Binary files /dev/null and b/tests/png/core_xy.png differ
diff --git a/utils/core_xy.scad b/utils/core_xy.scad
new file mode 100644
index 0000000..e9efb72
--- /dev/null
+++ b/utils/core_xy.scad
@@ -0,0 +1,196 @@
+//
+// NopSCADlib Copyright Chris Palmer 2020
+// nop.head@gmail.com
+// hydraraptor.blogspot.com
+//
+// This file is part of NopSCADlib.
+//
+// NopSCADlib is free software: you can redistribute it and/or modify it under the terms of the
+// GNU General Public License as published by the Free Software Foundation, either version 3 of
+// the License, or (at your option) any later version.
+//
+// NopSCADlib is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along with NopSCADlib.
+// If not, see .
+//
+
+//
+//! Parameterised Core XY implementation. Draws the belts and provides utilities for positioning the pulleys.
+//!
+//! The belts are positioned according the bottom left "anchor" pulley and the top right drive pulley.
+//! Implementation has the following features:
+//! 1. The drive and idler pulleys may be different sizes.
+//! 2. The belt separation is parameterised.
+//! 3. The separation of the plain and toothed pulleys on the Y carriages is parameterised, in both the X and the Y direction.
+//! 4. The drive pulleys may be offset in the X and Y directions. If this is done, extra idler pulleys are added. This
+//! allows flexible positioning of the motors.
+//
+include <../core.scad>
+include <../vitamins/belts.scad>
+include <../vitamins/pulleys.scad>
+
+
+coreXY_GT2_20_20 = ["coreXY_20_20", GT2x6, GT2x20ob_pulley, GT2x20_toothed_idler, GT2x20_plain_idler, [0, 0, 1], [0, 0, 0.5, 1], [0, 1, 0], [0, 0.5, 0, 1] ];
+coreXY_GT2_20_16 = ["coreXY_20_16", GT2x6, GT2x20ob_pulley, GT2x16_toothed_idler, GT2x16_plain_idler, [0, 0, 1], [0, 0, 0.5, 1], [0, 1, 0], [0, 0.5, 0, 1] ];
+coreXY_GT2_16_16 = ["coreXY_16_16", GT2x6, GT2x16_pulley, GT2x16_toothed_idler, GT2x16_plain_idler, [0, 0, 1], [0, 0, 0.5, 1], [0, 1, 0], [0, 0.5, 0, 1] ];
+
+function coreXY_belt(type) = type[1]; //! Belt type
+function coreXY_drive_pulley(type) = type[2]; //! Drive pulley type
+function coreXY_toothed_idler(type) = type[3]; //! Toothed idler type
+function coreXY_plain_idler(type) = type[4]; //! Plain idler type
+function coreXY_upper_belt_colour(type) = type[5]; //! Colour of the upper belt
+function coreXY_upper_tooth_colour(type) = type[6]; //! Colour of the upper belt's teeth
+function coreXY_lower_belt_colour(type) = type[7]; //! Colour of the lower belt
+function coreXY_lower_tooth_colour(type) = type[8]; //! Colour of the lower belt's teeth
+
+// used to offset the position of the drive pulley and the y-carriage plain idler pulley
+// relative to the anchor pulley so that the belts align properly
+function coreXY_drive_pulley_x_alignment(type) = //! Belt alignment offset of the drive pulley relative to the anchor pulley
+ (pulley_od(coreXY_drive_pulley(type)) - pulley_od(coreXY_toothed_idler(type))) / 2;
+function coreXY_coincident_separation(type) = //! Value of x, y separation to make y-carriage pulleys coincident
+ [ -coreXY_plain_idler_offset(type).x, -(pulley_od(coreXY_plain_idler(type)) + pulley_od(coreXY_toothed_idler(type)))/2, 0 ];
+function coreXY_plain_idler_offset(type) = //! Offset of y-carriage plain idler
+ [ (pulley_od(coreXY_plain_idler(type)) + pulley_od(coreXY_drive_pulley(type))) / 2 + coreXY_drive_pulley_x_alignment(type), pulley_od(coreXY_plain_idler(type))/2, 0 ];
+function coreXY_toothed_idler_offset(type) = //! offset of y-carriage toothed idler
+ [ 0, -pulley_pr(coreXY_toothed_idler(type)), 0 ];
+// helper functions for positioning idlers when the stepper motor drive pulley is offset
+function coreXY_drive_toothed_idler_offset(type) = //! Offset of toothed drive idler pulley
+ [ 0, coreXY_drive_pulley_x_alignment(type), 0 ];
+function coreXY_drive_plain_idler_offset(type) = //! Offset of plain drive idler pulley
+ [ coreXY_plain_idler_offset(type).x, -(pulley_od(coreXY_plain_idler(type)) + pulley_od(coreXY_drive_pulley(type))) / 2, 0 ];
+
+
+module coreXY_half(type, size, pos, separation_y = 0, x_gap = 0, plain_idler_offset = 0, drive_pulley_offset = [0, 0], show_pulleys = false, lower_belt = false, hflip = false) { //! Draw one belt of a coreXY setup
+
+ // y-carriage toothed pulley
+ p0_type = coreXY_toothed_idler(type);
+ p0 = [ size.x / 2, -size.y / 2 - pulley_od(p0_type) / 2 + pos.y - separation_y / 2 ];
+
+ // bottom right toothed idler pulley
+ p1_type = p0_type;
+ p1 = [ size.x / 2, -size.y / 2 ];
+
+ // bottom left anchor toothed idler pulley
+ p2_type = p0_type;
+ p2 = [ -size.x / 2, -size.y / 2 ];
+
+ // stepper motor drive pulley
+ p3d_type = coreXY_drive_pulley(type);
+ p3d = [ -size.x / 2 + coreXY_drive_pulley_x_alignment(type) + drive_pulley_offset.x,
+ size.y / 2 + drive_pulley_offset.y
+ ];
+
+ // toothed idler for offset stepper motor drive pulley
+ p3t_type = coreXY_toothed_idler(type);
+ p3t = [ -size.x / 2 + (drive_pulley_offset.x > 0 ? 0 : 2*coreXY_drive_pulley_x_alignment(type)),
+ size.y / 2 + coreXY_drive_pulley_x_alignment(type) + drive_pulley_offset.y
+ ];
+
+ // y-carriage plain pulley
+ p4_type = coreXY_plain_idler(type);
+ p4 = [ -size.x / 2 + pulley_od(p4_type) / 2 + pulley_od(p3d_type) / 2 + coreXY_drive_pulley_x_alignment(type) + plain_idler_offset,
+ -size.y / 2 + pulley_od(p4_type) / 2 + pos.y + separation_y / 2
+ ];
+
+ // plain idler for offset stepper motor drive pulley
+ p3p_type = p4_type;
+ p3p = [ drive_pulley_offset.x > 0 ? p4.x : -p0.x - pulley_od(p0_type),
+ size.y / 2 - pulley_od(p3p_type) / 2 - pulley_od(p3d_type) / 2 + drive_pulley_offset.y
+ ];
+
+ // dummy pulleys for y separation
+ p5_type = p4_type;
+ p5 = [ pos.x - size.x / 2, -size.y / 2 + pos.y + separation_y / 2 ];
+ p6_type = p0_type;
+ p6 = [ pos.x - size.x / 2, -size.y / 2 + pos.y - separation_y / 2 ];
+
+ module show_pulleys(show_pulleys) {// Allows the pulley colour to be set for debugging
+ if (is_list(show_pulleys))
+ color(show_pulleys)
+ children();
+ else if (show_pulleys)
+ children();
+ }
+
+ show_pulleys(show_pulleys) {
+ translate(p0)
+ pulley_assembly(p0_type); // y-carriage toothed pulley
+ translate(p1)
+ pulley_assembly(p1_type); // bottom right toothed idler pulley
+ translate(p2)
+ pulley_assembly(p2_type); // bottom left anchor toothed idler pulley
+ translate(p3d)
+ hflip(hflip)
+ pulley_assembly(p3d_type); // top left stepper motor drive pulley
+ if (drive_pulley_offset.x) { // idler pulleys for offset stepper motor drive pulley
+ translate(p3t)
+ pulley_assembly(p3t_type); // toothed idler
+ translate(p3p)
+ pulley_assembly(p3p_type); // plain idler
+ }
+ translate(p4)
+ pulley_assembly(p4_type); // y-carriage plain pulley
+ }
+
+ path0a = [
+ [ p0.x, p0.y, pulley_od(p0_type) / 2 ],
+ [ p1.x, p1.y, pulley_od(p1_type) / 2 ],
+ [ p2.x, p2.y, pulley_od(p2_type) / 2 ]
+ ];
+ path0b = [
+ [ p3d.x, p3d.y, pulley_od(p3d_type) / 2 ],
+ [ p4.x, p4.y, -pulley_od(p4_type) / 2 ]
+ ];
+ path0c = [
+ [ p3t.x, p3t.y, pulley_od(p3t_type) / 2 ],
+ [ p3d.x, p3d.y, pulley_od(p3d_type) / 2 ],
+ [ p3p.x, p3p.y, -pulley_od(p3p_type) / 2 ],
+ [ p4.x, p4.y, -pulley_od(p4_type) / 2 ]
+ ];
+ path0d = [
+ [ p3p.x, p3p.y, -pulley_od(p3p_type) / 2 ],
+ [ p3d.x, p3d.y, pulley_od(p3d_type) / 2 ],
+ [ p3t.x, p3t.y, pulley_od(p3t_type) / 2 ],
+ [ p4.x, p4.y, -pulley_od(p4_type) / 2 ]
+ ];
+ path1 = [ // use eps for corner radius to get sharp corners so this part of the belt is deleted by the gap
+ [ p5.x, p5.y, eps ],
+ [ p6.x, p6.y, eps ]
+ ];
+
+ belt = coreXY_belt(type);
+
+ path0 = drive_pulley_offset.x == 0 ? concat(path0a, path0b) : drive_pulley_offset.x > 0 ? concat(path0a, path0c) : concat(path0a, path0d);
+ path = separation_y == 0 ? path0 : concat(path0, path1);
+
+ belt(type = belt,
+ points = path,
+ gap = [ x_gap + eps, abs(separation_y) + 2 ],
+ gap_pos = [ pos.x - size.x / 2, pos.y - size.y / 2 + belt_pitch_height(belt) - belt_thickness(belt) / 2 ],
+ belt_colour = lower_belt ? coreXY_lower_belt_colour(type) : coreXY_upper_belt_colour(type),
+ tooth_colour = lower_belt ? coreXY_lower_tooth_colour(type) : coreXY_upper_tooth_colour(type));
+}
+
+module coreXY(type, size, pos, separation, x_gap, plain_idler_offset = 0, upper_drive_pulley_offset, lower_drive_pulley_offset, show_pulleys = false) { //! Wrapper module to draw both belts of a coreXY setup
+ translate([size.x / 2 - separation.x / 2, size.y / 2, -separation.z / 2]) {
+ // lower belt
+ hflip()
+ explode(25)
+ coreXY_half(type, size, [size.x - pos.x - separation.x, pos.y], separation.y, x_gap, plain_idler_offset, [-lower_drive_pulley_offset.x, lower_drive_pulley_offset.y], show_pulleys, lower_belt = true, hflip = true);
+ // upper belt
+ translate([separation.x, 0, separation.z])
+ explode(25)
+ coreXY_half(type, size, [pos.x, pos.y], separation.y, x_gap, plain_idler_offset, upper_drive_pulley_offset, show_pulleys, lower_belt = false, hflip=false);
+ }
+}
+
+module coreXY_belts(type, carriagePosition, coreXYPosBL, coreXYPosTR, separation, x_gap = 20, upper_drive_pulley_offset = [0, 0], lower_drive_pulley_offset = [0, 0], show_pulleys = false) { //! Draw the coreXY belts
+ assert(coreXYPosBL.z == coreXYPosTR.z);
+
+ coreXYSize = coreXYPosTR - coreXYPosBL;
+ translate(coreXYPosBL)
+ coreXY(type, coreXYSize, [carriagePosition.x - coreXYPosBL.x + separation.x / 2, carriagePosition.y - coreXYPosBL.y], separation = separation, x_gap = x_gap, plain_idler_offset = 0, upper_drive_pulley_offset = upper_drive_pulley_offset, lower_drive_pulley_offset = lower_drive_pulley_offset, show_pulleys = show_pulleys);
+}