diff --git a/.github/workflows/prettier-check.yml b/.github/workflows/prettier-check.yml new file mode 100644 index 00000000..90dfe288 --- /dev/null +++ b/.github/workflows/prettier-check.yml @@ -0,0 +1,23 @@ +name: 'Docs Fomatting Check' +on: + push: + paths: + - './docs/**' + workflow_dispatch: + +jobs: + format-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - name: Install dependencies + run: bun install + - name: check format for docs + run: + bun docs-check diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000..96ab12b2 --- /dev/null +++ b/contributing.md @@ -0,0 +1,31 @@ + +# Contributing + +This is a community driven project, and we welcome any contributions you'd like to make. You can connect with us on [discord](https://discord.gg/suyCCgr). + + +## Running Locally + +- Ensure you have [node](https://nodejs.org/en/download/), or an equivalent installed +- Clone the [repository](https://github.com/GridSpace/grid-apps) +- Run `npm run setup` +- Run `npm run dev` + +## Running Docs Locally + +- Ensure you have [node](https://nodejs.org/en/download/), or an equivalent installed +- Clone the [repository](https://github.com/GridSpace/grid-apps) +- Run `npm run setup` +- Run `npm run docs-dev` + + +## How to add a new machine + +- Make sure you have your tested machine selected +- Open the developer console +- Run the following code: `kiri.api.conf.get().device` +- Right click on the object and select `Copy object` +- Make a new file in the `src/kiri-dev/` directory, with the name of your machine, no spaces or special characters, and a `.json` extension. +- Paste the copied object into the new file, and save it. +- Publish your changes to a git repo +- Submit a [pull request](https://github.com/GridSpace/grid-apps/compare) \ No newline at end of file diff --git a/docs/gridbot/bom.md b/docs/gridbot/bom.md index f5086b52..53947815 100644 --- a/docs/gridbot/bom.md +++ b/docs/gridbot/bom.md @@ -1,7 +1,5 @@ # Parts List - - | **Quantity** | **Name** | **Description** | **Product Link** | | ------------ | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 1 | upper xy belt | | [https://www.amazon.com/Printing-FYSETC-Fiberglass-Rostock-Printers/dp/B07CDDPCM8](https://www.amazon.com/Printing-FYSETC-Fiberglass-Rostock-Printers/dp/B07CDDPCM8) | @@ -9,7 +7,7 @@ | 1 | z belt | 1140-1160 mm | [https://www.amazon.com/gp/product/B018HN8576](https://www.amazon.com/gp/product/B018HN8576) | | 1 | 24v power supply | | [https://www.amazon.com/EAGWELL-Universal-Regulated-Switching-Computer/dp/B01IOK5FM0](https://www.amazon.com/EAGWELL-Universal-Regulated-Switching-Computer/dp/B01IOK5FM0) | | 1 | SKR V1.3 | | [https://www.amazon.com/gp/product/B07Z3FZGJ1](https://www.amazon.com/gp/product/B07Z3FZGJ1) | -| 2 | cross bar | | [https://hobbyking.com/en\_us/aluminum-square-tube-diy-multi-rotor-12-8x12-8x600mm-5inch-black.html](https://hobbyking.com/en\_us/aluminum-square-tube-diy-multi-rotor-12-8x12-8x600mm-5inch-black.html) | +| 2 | cross bar | | [https://hobbyking.com/en_us/aluminum-square-tube-diy-multi-rotor-12-8x12-8x600mm-5inch-black.html](https://hobbyking.com/en_us/aluminum-square-tube-diy-multi-rotor-12-8x12-8x600mm-5inch-black.html) | | 1 | hot end mount | 3d print solid | | | 1 | bltouch mount | 3d print solid | | | 3 | level bracket | 3d print solid | | diff --git a/docs/gridbot/index.md b/docs/gridbot/index.md index aa024ac2..14741b90 100644 --- a/docs/gridbot/index.md +++ b/docs/gridbot/index.md @@ -15,6 +15,4 @@ The [control software](https://github.com/GridSpace/grid-bot) is open source on A Beta for a [new design](https://cad.onshape.com/documents/d0514606a660a802eb16304a/w/43a797f28bcc004e3e7e7e17/e/61abf70c46004ffbc0134231) is also available. Check [forum.grid.space](https://forum.grid.space) for progress on this and other builds. - - ![](/img/GridBot-Two-Right.png) diff --git a/docs/kiri-moto/CAM/index.md b/docs/kiri-moto/CAM/index.md new file mode 100644 index 00000000..79a6344e --- /dev/null +++ b/docs/kiri-moto/CAM/index.md @@ -0,0 +1,74 @@ +--- +description: Overview of cam mode +--- + +# CAM Mode Overview + +Kiri:Moto's CAM mode is designed for subtractive manufacturing processes, primarily [CNC](https://en.wikipedia.org/wiki/Computer_numerical_control) milling. This mode provides a streamlined, browser-based workflow for generating toolpaths from 3D models and exporting G-code for a wide range of machines. + +This page provides a high-level overview of the CAM workflow. There are subpages for specific parts of the workflow. + +--- + +## Arrange + +Begin by importing your model(s) into the workspace. The **Arrange** stage allows you to: + +- Translate and rotate parts on the base platform +- Align multiple objects relative to each other +- Snap objects flat to the bed +- Apply other [transformations]() like scaling and mirroring +- Set your [profile options](processOpts) +- set your [machine](machines) settings +- set your [tool library](tools) +- set your [CAM operations](ops) + +You can set profile options, and machine and tool settings in any part of the workflow, but doing so will not automatically update other parts of the workflow. You will need to re-slice after making changes. + +--- + +## Slice + +The Slice stage generates toolpaths based on user-defined [CAM operations](ops). The software makes use of [worker threads](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to slice the model without blocking the UI. + +When working with complex models, you can toggle specific layers of the slice by toggling them at the botom of the page + +![](/img/CAM/sliceToggle.png) + +The bottom of the page also has a progress bar that is draggable and shows the order of operations. + +--- + +## Preview + +The Preview tab is quite similar to the Slice tab, but has a few key differences: + +- Shows tool speeds +- Shows tool movement types (rapid move/ milling move) +- Visually shows tool offsets and tool changes +- Allows inspection of toolpath order and direction +- Helps detect gouges, collisions, or inefficient moves + +![](/img/CAM/preview.png) + +Overall, the Preview tab gives a more in-depth view of the toolpaths that will be generated. + +--- + +## Animate + +Animate is a useful tool to get a sense of how the tool will move through the stock without the risk of actually cutting anything. +Animate can show if your gode will collide with the stock, cut too deep, or cut in a place you don't expect. + +![](/img/CAM/animate.gif) + +--- + +## Export + +Once you're confident in your setup, use the Export menu to generate machine-ready [G-code](https://en.wikipedia.org/wiki/G-code). + +![](/img/CAM/exportMenu.png) + +Most of the configuration for this step is done in the [Machine](./machines) settings, so be sure to have that configured before exporting. +The tab will give you a time estimate for the job (excluding tool changes), and an option to export G-code to a single file, or as a zipped archive of the sepate operation for each tool. diff --git a/docs/kiri-moto/CAM/machines.md b/docs/kiri-moto/CAM/machines.md new file mode 100644 index 00000000..3b49ea05 --- /dev/null +++ b/docs/kiri-moto/CAM/machines.md @@ -0,0 +1,69 @@ +--- +description: Machine setup for CAM machines +--- + +# Machine Setup + +The Machine Setup tab defines the physical capabilities of your CNC machine, its firmware behavior, and how G-code should be generated. A properly configured machine profile ensures your output matches your hardware's constraints and expectations. + +You can access it from the top-right menu: + +![](/img/CAM/machinesTab.png) + +--- + +## Workspace Dimensions + +These values define your machine’s cutting envelope. Use your _actual usable_ dimensions here, not the marketing ones. + +- **X (width)** — Maximum horizontal travel (left to right) +- **Y (depth)** — Maximum front-to-back travel +- **Z (height)** — Maximum vertical travel (top to bottom) + +The units of these values are based on the units set in your global preferences (defaults to mm). + +--- + +## Firmware Settings + +- **Max Spindle** — The highest RPM your spindle supports. This limits the `feed` speeds set in your [CAM operations](/kiri-moto/CAM/ops) to generate proper and safe `S` values in G-code. + +--- + +## Output Options + +Customize how your G-code is formatted for your controller or post-processor: + +- **Strip Comments** — Removes all G-code comments for cleaner, smaller files +- **Token Spacer** — Inserts a space between G-code tokens (e.g., `G1 X1 Y1` vs `G1X1Y1`) +- **Enable Laser** — Enables [Laser](./ops#laser-operations) operations (typically uses `M3/M5`; adjusts movement strategy) +- **File Extension** — Optional override for exported file type (e.g., `.nc`, `.ngc`, `.cnc`, `.cam`, `.gcode`, `.txt`) + + +--- + +## G-code Macros + +Macros insert custom G-code at key stages of toolpath generation. Each tab corresponds to a different context. In these boxes, you have access to [G-code macros](../gcode-macros). Not all macros are available in all contexts, so be careful! + +- **Header** — Runs once at the beginning of the job (e.g., mode override, move to safe Z) +- **Footer** — Runs at the end of the job (e.g., spindle stop, move to safe Z) +- **Tool** — Called when changing tools. Automatic tool changes and tool measurement can be put here. +- **Dwell** — Code for pausing movement/spindle at specified positions +- **Spindle** — Code to set spindle RPM + +--- + +## RML Dialect + +Kiri:Moto supports the [RML](https://downloadcenter.rolanddg.com/contents/manuals/PNC-3200_USE2_E_R5.pdf) dialect of G-code. + +When a machine's file extension is set to `.rml`, Kiri:Moto will use the RML dialect, using `PU` for non-cut moves and `Z` for cut moves. RML commands can be used in the `Header`, `Footer`, `Tool`, `Dwell`, and `Spindle` sections of the Machine Setup tab in the same way that G-code commands are used. + +--- + +## Contribute a Machine Profile + +We love to add new machines to the Kiri:Moto library. If you have a tested machine that you'd like to add, you can export your workspace as a `.kmz` file and share it on the [Discord](https://discord.gg/suyCCgr) or [forums](https://forum.grid.space). + +If you want to submit a PR, you can find instructions for how to do so [here](https://github.com/GridSpace/grid-apps/blob/master/contributing.md#how-to-add-a-new-machine). diff --git a/docs/kiri-moto/CAM/ops.mdx b/docs/kiri-moto/CAM/ops.mdx new file mode 100644 index 00000000..ed7c2cd0 --- /dev/null +++ b/docs/kiri-moto/CAM/ops.mdx @@ -0,0 +1,155 @@ +--- +description: Keyboard Shortcuts and Mouse Controls +label: ops +--- + +import { ImageCarousel } from "@site/docs/src/components/carousel.js"; + +# Operations + +Ops—not to be confused with [opps](https://knowyourmeme.com/memes/opp-opps)—are the operations +applied to a model to remove material. The Operations tab also contains meta-operations +that represent parts of the manufacturing process but do not necessarily generate toolpaths. + +Each operation has parameters which can be hovered over to reveal their description. +![](/img/CAM/paramDetails.png) + +When an operation's settings are changed, they update the defaults for that operation +when it is created in the future. + +These operations include: + +- Toolpath Operations + - Global + - [Outline](#outline) + - [Level](#level) + - [Rough](#rough) + - [Contour](#contour) + - [Register](#register) + - Specific + - [Drill](#drill) + - [Trace](#trace) + - [Pocket](#pocket) +- [Gcode](#gcode) +- Laser Operations +- Indexed Operations + +## Global Operations + +Operations that apply to all objects in the workspace. + +### Outline + + + +The Outline op generates a toolpath surrounding the edges of a part. It is highly configurable and can select both internal or external edges specifically. The Outline operation is great for: + +- Cutting out many objects at once +- Generating a rough/finishing pass on an object +- Easily avoiding specific features like pockets or holes + +### Level + + + +The Level op creates a flat-surface clearing toolpath across a selected area. It is designed to remove material to a consistent depth, making it useful for preparing stock, flattening uneven surfaces, or cleaning up residual material. The Level operation is great for: + +- Surfacing raw stock before detail cuts +- Creating uniform faces on complex objects +- Clearing large areas with consistent depth quickly + +### Rough + + + +The Rough op removes large volumes of material quickly using a stepped-down toolpath. It is typically the first pass in a multi-step workflow, clearing space for finishing tools without concern for fine detail. The Rough operation is great for: + +- Rapid bulk material removal +- Preparing many parts for finishing simultaneously +- Clearing voids or flat faces for many objects at once +- Preparing a part for finish machining +- Preserving tool life by reducing cutting load in later passes + +### Contour + + + +The Contour op generates detailed toolpaths for complex organic surface geometries. +It can trace along the X or Y axis and has configurable precision. It is useful for: + +- Carving 3D shapes with organic or curved profiles +- Finishing complex wall geometries +- Cleaning up a part after [roughing](#rough) + +### Register + + + +The Register operation drills holes in the sides of a part, +helping keep the part in the same place when it is flipped onto its opposite face. +The operation has options to drill on different sides and can even generate a puzzle-piece-like pattern for registration. + +## Specific Operations + +Specific operations require selection of individual part features to apply operations to. +These may overlap with Global operations but are generally more configurable. + +### Drill + + + +The Drill op creates pecking toolpaths for a drill tool to follow. +It can also drill holes of other diameters and even mark holes instead of drilling. +It is useful for: + +- Generating a toolpath for a drill tool +- Selecting specific holes to remove material from +- Marking holes to be drilled manually + +### Trace + + + +The Trace operation is likely the most configurable, allowing for generation of toolpaths following loops or lines. +It can trace on, inside, or outside a line and can take multiple passes to step down to the desired position. +When the type parameter is set to "clear," it can even act like a [Pocket](#pocket), +clearing the area above the shape created by the selected lines or loop. +While the Trace op can do a lot, some specific use cases include: + +- Engraving lettering or other text +- Drilling out a hole helically +- Outlining a part with a nonstandard offset +- Outlining only one part on the workspace + +### Pocket + + + +The Pocket operation takes a selection of polygon faces and generates a pocket toolpath that cuts down to them. +The operation has options to expand and smooth the pocket selection, and the contour option even allows +for an approximation of a v-bit carve. Some use cases specific to the Pocket op include: + +- Creating one pocket in a part with multiple +- Attempting a v-carve +- Clearing a specific area of a part + +### Gcode + + + +The Gcode operation does not generate a toolpath but instead adds a line or lines of G-code to the output +of the file. The Gcode operation is different from [Gcode Macros](/kiri-moto/gcode-macros), +as it is not tied to any event +but is output in the order it is included in the operations array. + +## Laser Operations + +Laser operations become available when "Enable Laser" is selected in the [Machine](./machines) tab. + +These operations are specific to certain machines, like the Makera Carvera, and can be safely ignored for other machines. + +## Indexed Operations + +Indexed operations become available when "Indexed" is selected in the [Process Ops](./processOpts) tab. + +These operations are specific to indexed stock and allow a user to create operations on different faces of a part. diff --git a/docs/kiri-moto/CAM/processOpts.mdx b/docs/kiri-moto/CAM/processOpts.mdx new file mode 100644 index 00000000..75ce5fb7 --- /dev/null +++ b/docs/kiri-moto/CAM/processOpts.mdx @@ -0,0 +1,59 @@ +--- +description: Process options for the CAM mode +--- + +import { ImageCarousel } from "@site/docs/src/components/carousel.js"; + +# Process Options + +The Process Options (on the left side of the screen) are a set of parameters that control the behavior of the CAM engine. + +Process options can be saved in a profile, so they don't need to be reset each time a material is changed. +[Operation](./ops) defaults are stored in a profile as well but are modified within that operation. + +![](/img/CAM/process/profiles.png) + +Each operation has parameters that can be hovered over to reveal their description. + +![](/img/CAM/process/hover.png) + +## Tabs + + + +Tabs can be added with the plus button, finalized with the checkmark, and removed with the trash button. +Their size is configurable. + +## Stock + +The stock defines the material that surrounds the part. As of V4.1, only rectangular stock is supported. +Setting width, depth, and height to zero will make the stock the same size as the part's bounding box. + +The stock size defaults to absolute dimensions but can be made to offset from the part's bounding box by setting the stock offset option to true. +If the stock is smaller than the part, the part will not slice. + +The "Clip To" option will delete any toolpaths that go outside the stock. + +Using indexed stock is supported (barely). If you are using a rotary indexer, you can check the Indexed option and use [Indexed Operations](./ops#indexed-operations) to create operations on different faces of the part. + +## Limits + +The Limits tab has options for setting the maximum feed rates, the Z-zero, and defaults for top and bottom offsets for operations. +Some operations have overrides for these values. + +## Output + +The Output tab configures how lines generated by the CAM ops are processed before being exported. +These mainly affect what to do before and after each operation, and which direction to traverse the toolpath (climb or conventional). + +The **First Z Max** and **Force Z Max** are safety features that move to a safe Z +before the job and before pockets, respectively. They are not checked by default for legacy compatibility. + +## Origin + +The Origin tab controls the origin of the part and allows configuration in the form of XYZ offsets. + +## Expert + +The Expert tab controls advanced options for CAM mode, which can speed up slicing but may generate tool crashes. +Use with caution. diff --git a/docs/kiri-moto/CAM/tools.md b/docs/kiri-moto/CAM/tools.md new file mode 100644 index 00000000..f1e64ec3 --- /dev/null +++ b/docs/kiri-moto/CAM/tools.md @@ -0,0 +1,21 @@ +--- +description: Tool options for the CAM mode +--- + +# Tools + +The Tool menu can be accessed from the top-right corner dropdown. + +![](/img/CAM/toolsTab.png) + +Once selected, the Tool menu provides options to create, copy, edit, and delete tools. +Additionally, tools can be imported and exported as `.km` files. The file format is binary-encoded and not human-readable, so don't try to edit it with a text editor. + +![](/img/CAM/toolMenu.png) + +Kiri:Moto currently supports four types of tools: + +- End Mills +- Ball Mills +- Taper Mills +- Drills diff --git a/docs/kiri-moto/apis.md b/docs/kiri-moto/apis.md index c28c15d4..0ef06927 100644 --- a/docs/kiri-moto/apis.md +++ b/docs/kiri-moto/apis.md @@ -40,24 +40,24 @@ JSON dictionaries can be used for reference from the [CLI package](https://githu Kiri:Moto is designed to be embedded in a page using an IFrame. The look and feel and other controls are available for the loaded page using a messaging API. An example page is [here](https://grid.space/kiri/frame.html). -| function | description | -| --------------------------- | ------------------------------------------------------------------------------------------------------- | -| setFrame(_id\_obj_) | id or object of IFrame with Kiri:Moto loaded | -| load(_url_) | load an [STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)) referred to by _url_ | -| clear() | remove all objects from workspace | -| parse(_data_) | parse the text or binary contents of an [STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)) file | -| setMode(_mode_) | where _mode_ comes from:`[ "CAM", "FDM", "LASER", "SLA" ]` | -| setDevice(_options_) | change default Device options | -| setProcess(options) | change default Process options | -| setController(options) | change default Core options | -| slice() | async slice of loaded object | -| prepare() | async path routing of slice data | -| export() | async gcode generation from routed paths | -| onmessage(_fn_) | function will receive all IFrame messages | -| onevent(_event, data_) | function will receive named messages | -| emit(_event, data_) | send named event with data payload | -| alert(_msg, time_) | show alert message with optional time in seconds | -| process(_percent, message_) | set progress bar (0.0 to 1.0) with optional message | +| function | description | +| --------------------------- | ----------------------------------------------------------------------------------------------------- | +| setFrame(_id_obj_) | id or object of IFrame with Kiri:Moto loaded | +| load(_url_) | load an [STL]() referred to by _url_ | +| clear() | remove all objects from workspace | +| parse(_data_) | parse the text or binary contents of an [STL]() file | +| setMode(_mode_) | where _mode_ comes from:`[ "CAM", "FDM", "LASER", "SLA" ]` | +| setDevice(_options_) | change default Device options | +| setProcess(options) | change default Process options | +| setController(options) | change default Core options | +| slice() | async slice of loaded object | +| prepare() | async path routing of slice data | +| export() | async gcode generation from routed paths | +| onmessage(_fn_) | function will receive all IFrame messages | +| onevent(_event, data_) | function will receive named messages | +| emit(_event, data_) | send named event with data payload | +| alert(_msg, time_) | show alert message with optional time in seconds | +| process(_percent, message_) | set progress bar (0.0 to 1.0) with optional message | ## Events diff --git a/docs/kiri-moto/engine-apis.md b/docs/kiri-moto/engine-apis.md index 30d9d8d5..58746409 100644 --- a/docs/kiri-moto/engine-apis.md +++ b/docs/kiri-moto/engine-apis.md @@ -4,38 +4,38 @@ label: Engine APIs --- # Engine APIs + The `Engine` class serves as an abstraction layer for interacting with the `kiri` 3D slicing engine. It handles loading and parsing STL files, manipulating widgets (3D models), configuring slicing parameters, and executing the toolpath generation pipeline: slice, prepare, and export. This API is designed with method chaining in mind, and provides hooks for custom event listeners. you can view some example of how to use the engine [here](https://grid.space/kiri/engine.html). - --- ## Methods -| Method | Description | Returns | -|--------|-------------|---------| -| `load(url)` | Loads an [STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)) file from a given URL and centers the model. | `Promise` | -| `clear()` | Clears all objects from workspace. | `void` | -| `parse(data)` | Parses raw [STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)) data and loads it as a centered widget. | `Promise` | -| `setListener(listener)` | Sets an event listener function to receive engine progress updates | `Engine` | -| `setRender(bool)` | Enables or disables rendering. | `Engine` | -| `setMode(mode)` | Sets the slicing mode where mode is "CAM"|"FDM"|"LASER"|"SLA" . | `Engine` | -| `setDevice(device)` | Merges a custom device profile into the current settings. | `Engine` | -| `setProcess(process)` | Merges custom slicing process parameters. | `Engine` | -| `setController(controller)` | Sets the slicing controller settings and starts/stops the worker pool accordingly. | `Engine` | -| `setTools(tools)` | Sets the tool definitions (e.g. cutters, extruders). | `Engine` | -| `setStock(stock)` | Sets the stock material dimensions. | `Engine` | -| `setOrigin(x, y, z)` | Defines the origin point for the part. | `Engine` | -| `moveTo(x, y, z)` | Moves the widget to the specified absolute coordinates. | `Engine` | -| `move(x, y, z)` | Moves the widget by the specified delta values. | `Engine` | -| `scale(x, y, z)` | Scales the widget along each axis. | `Engine` | -| `rotate(x, y, z)` | Rotates the widget in degrees along each axis. | `Engine` | -| `slice()` | Starts the slicing process and returns once complete. | `Promise` | -| `prepare()` | Prepares the sliced toolpaths for export (e.g. G-code generation). | `Promise` | -| `export()` | Exports the toolpaths as a string (e.g. G-code). | `Promise` | +| Method | Description | Returns | +| --------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------- | +| `load(url)` | Loads an [STL]() file from a given URL and centers the model. | `Promise` | +| `clear()` | Clears all objects from workspace. | `void` | +| `parse(data)` | Parses raw [STL]() data and loads it as a centered widget. | `Promise` | +| `setListener(listener)` | Sets an event listener function to receive engine progress updates | `Engine` | +| `setRender(bool)` | Enables or disables rendering. | `Engine` | +| `setMode(mode)` | Sets the slicing mode where mode is "CAM" \| "FDM" \| "LASER" \| "SLA" . | `Engine` | +| `setDevice(device)` | Merges a custom device profile into the current settings. | `Engine` | +| `setProcess(process)` | Merges custom slicing process parameters. | `Engine` | +| `setController(controller)` | Sets the slicing controller settings and starts/stops the worker pool accordingly. | `Engine` | +| `setTools(tools)` | Sets the tool definitions (e.g. cutters, extruders). | `Engine` | +| `setStock(stock)` | Sets the stock material dimensions. | `Engine` | +| `setOrigin(x, y, z)` | Defines the origin point for the part. | `Engine` | +| `moveTo(x, y, z)` | Moves the widget to the specified absolute coordinates. | `Engine` | +| `move(x, y, z)` | Moves the widget by the specified delta values. | `Engine` | +| `scale(x, y, z)` | Scales the widget along each axis. | `Engine` | +| `rotate(x, y, z)` | Rotates the widget in degrees along each axis. | `Engine` | +| `slice()` | Starts the slicing process and returns once complete. | `Promise` | +| `prepare()` | Prepares the sliced toolpaths for export (e.g. G-code generation). | `Promise` | +| `export()` | Exports the toolpaths as a string (e.g. G-code). | `Promise` | --- @@ -51,4 +51,4 @@ If a listener is set via `setListener(fn)`, it will be called with events such a - `{ export: { segment } }` — during G-code segment export - `{ export: { done } }` — when export is finished -These allow for building progress indicators or responding to processing stages. \ No newline at end of file +These allow for building progress indicators or responding to processing stages. diff --git a/docs/kiri-moto/faq.md b/docs/kiri-moto/faq.md index 67cfa1ee..d6ceceb3 100644 --- a/docs/kiri-moto/faq.md +++ b/docs/kiri-moto/faq.md @@ -8,7 +8,7 @@ description: Frequently Asked Questions Entirely. -The source code is on [GitHub](https://github.com/GridSpace/grid-apps) if you want to install and run it yourself. It is available under the [MIT](https://en.wikipedia.org/wiki/MIT\_License) license, which is one of the most liberal in terms of allowing you to do what you want with the code without restriction. +The source code is on [GitHub](https://github.com/GridSpace/grid-apps) if you want to install and run it yourself. It is available under the [MIT](https://en.wikipedia.org/wiki/MIT_License) license, which is one of the most liberal in terms of allowing you to do what you want with the code without restriction. Multiple live [versions](https://grid.space/choose) (including current development branches) are served free of charge and free of ads on [Grid.Space](https://grid.space/kiri/). @@ -18,10 +18,10 @@ It has similar capabilities to other 3D printing slicers like Cura, Simplify3D, It is very different in several important ways: -* There is no software to install or maintain. It's just a web page. -* As a web app, it runs in a security sandbox and cannot access data on your hard drive -* It offers several modes of operation for most of the common maker tools, like CNC mills -* It is updated quite frequently (several times a week) with bug fixes and new features +- There is no software to install or maintain. It's just a web page. +- As a web app, it runs in a security sandbox and cannot access data on your hard drive +- It offers several modes of operation for most of the common maker tools, like CNC mills +- It is updated quite frequently (several times a week) with bug fixes and new features ## What language is it written in? @@ -29,7 +29,7 @@ It is almost entirely Javascript. There are a few minor modules compiled into WA ## What file types are supported? -[STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)), [OBJ](https://en.wikipedia.org/wiki/Wavefront\_.obj\_file), and [3MF](https://github.com/3MFConsortium/spec\_core/blob/master/3MF%20Core%20Specification.md) files are supported for 3D part import. [SVG](https://en.wikipedia.org/wiki/Scalable\_Vector\_Graphics) files import and auto-convert into 3D models. [PNG](https://en.wikipedia.org/wiki/Portable\_Network\_Graphics) files are supported for 2D image to 3D model conversion. +[STL](), [OBJ](https://en.wikipedia.org/wiki/Wavefront_.obj_file), and [3MF](https://github.com/3MFConsortium/spec_core/blob/master/3MF%20Core%20Specification.md) files are supported for 3D part import. [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) files import and auto-convert into 3D models. [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) files are supported for 2D image to 3D model conversion. ## What if my printer isn't supported? @@ -43,3 +43,7 @@ Start with the [forum](https://forum.grid.space/) discussions. If you want to ge Kiri comes from Kiri-e, which is the Japanese art of paper-cutting. Moto is short for "modeling tool". +## What are the `.kmz` and `.km` file formats short for? + +- `kmz` stands for **Kiri Moto Zip**, and contains an entire zipped workspace +- `km` stands for **Kiri Moto**, and contains [CAM tool](./CAM/tools) paramers. diff --git a/docs/kiri-moto/gcode-macros.md b/docs/kiri-moto/gcode-macros.md index 7bb71fc4..263e98ee 100644 --- a/docs/kiri-moto/gcode-macros.md +++ b/docs/kiri-moto/gcode-macros.md @@ -19,7 +19,6 @@ description: Variable Substitutions and Expressions in GCode Macros - \{print-time\} = alias for \{time\} ... deprecated after 2.8 - \{print_time\} = alias for \{time\} ... 2.9 and beyond - ## FDM (3D Printing) Mode Only ### FDM Macros @@ -44,10 +43,12 @@ description: Variable Substitutions and Expressions in GCode Macros - \{e\} = amount of filament extruded ### "Feature" only Macros (v3.4+) + - \{feature\} = feature region of the print (brims, infill, etc) - \{minx|miny|maxx|maxy\} = position in mm of extents of the print area ### Logical Code Flow (IF / ELIF / ELSE / END) + ``` ;; IF { layer >= 10 && layer <= 20 } ;; ..... inside 10-20 layer={layer} @@ -58,7 +59,8 @@ description: Variable Substitutions and Expressions in GCode Macros ;; END ``` -### PREAMBLE control (v3.4+) +### PREAMBLE control (v3.4+) + Allows for intro comment and config list to be re-positioned after the header or disabled. This was introduced to allow GCode output to work with Ultimaker. `;; PREAMBLE OFF` @@ -96,24 +98,25 @@ Set decimal precision (n = integer) (v3.8+) `;; DECIMALS = n` ### Axis Scaling (v3.7+) + Allows for a factor to be applied to X,Y,Z coordinates. Useful for some machines like the Roland MDX-40A that uses an unusual coordinate space. Default axis scale is `1` _`;; SCALE { "X":100, "Y":100, "Z":100 }`_ - ## CAM & FDM Modes - - ### Simple Algebraic Expression Support +## CAM & FDM Modes + +### Simple Algebraic Expression Support - Text inside `{}` is evaluated algebraically with access to JS classes and methods - `{Math.min(layer/layers, 0.5) + 1}` - `{token+n} {token-n}` ### Axis Remapping (v3.5+) - Allows for over-riding the default axis names. Useful for swapping axes and changing output for specific firmware targets. The format of the map is a JSON object._ + +Allows for over-riding the default axis names. Useful for swapping axes and changing output for specific firmware targets. The format of the map is a JSON object.\_ `;; AXISMAP {"X":"Y", "Y":"Z", "E":"E1"}` - ## Laser Mode Only ### Laser Macros diff --git a/docs/kiri-moto/index.md b/docs/kiri-moto/index.md index 7bd3fc4c..5917baaa 100644 --- a/docs/kiri-moto/index.md +++ b/docs/kiri-moto/index.md @@ -12,11 +12,11 @@ Kiri:Moto is focused on privacy, performance, and ease-of-use. Kiri:Moto has been in continuous development since 2013. It typically gets new development releases a few times a week and major releases every 4-8 weeks. -Please consider supporting this project with a [donation](https://paypal.me/gridspace3d?locale.x=en\_US). +Please consider supporting this project with a [donation](https://paypal.me/gridspace3d?locale.x=en_US). ## Privacy -Kiri:Moto is served free of ads, trackers, and any collection of personal data ([PII](https://en.wikipedia.org/wiki/Personal\_data)). Don't trust that? You can also install and run it yourself from [source](https://github.com/GridSpace/grid-apps). +Kiri:Moto is served free of ads, trackers, and any collection of personal data ([PII](https://en.wikipedia.org/wiki/Personal_data)). Don't trust that? You can also install and run it yourself from [source](https://github.com/GridSpace/grid-apps). Because Kiri:Moto runs in a browser's security sandbox, it does not have access to the personal and private data on your local hard drive the way installed desktop software does. @@ -29,4 +29,3 @@ Kiri:Moto is a compact, fast-loading page free of bloat. This is achieved by min ## Ease of Use Creating an interface that is usable for new to advanced users can be a challenge. Kiri:Moto takes a strong editorial stance on complexity. Much of the software in the maker universe is riddled with parameters and controls that are duplicative and work at cross-purposes. - diff --git a/docs/kiri-moto/interface.md b/docs/kiri-moto/interface.md index efe98a16..f02b8de3 100644 --- a/docs/kiri-moto/interface.md +++ b/docs/kiri-moto/interface.md @@ -10,7 +10,7 @@ The left menus control workspace, work mode, and objects while. The right menus control the behavior and settings for slicing, path routing, and path to code exports. -[OBJ](https://www.cs.cmu.edu/\~mbz/personal/graphics/obj.html), [STL](https://en.wikipedia.org/wiki/STL\_\(file\_format\)), and [3MF](https://docs.fileformat.com/3d/3mf/) files import natively. [SVG](https://en.wikipedia.org/wiki/Scalable\_Vector\_Graphics), [JPG](https://en.wikipedia.org/wiki/JPEG\_File\_Interchange\_Format), and [PNG](https://en.wikipedia.org/wiki/Portable\_Network\_Graphics) are auto-extruded into a 3D parts. +[OBJ](https://www.cs.cmu.edu/~mbz/personal/graphics/obj.html), [STL](), and [3MF](https://docs.fileformat.com/3d/3mf/) files import natively. [SVG](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics), [JPG](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format), and [PNG](https://en.wikipedia.org/wiki/Portable_Network_Graphics) are auto-extruded into a 3D parts. ![A minimalist approach](/img/kiri-moto.png) @@ -27,4 +27,3 @@ Application preferences are mode-dependent. For example, CNC preferences show an ![CNC Mode Preferences](/img/preferences-cam.png) ![FDM Mode Preferences](/img/preferences-fdm.png) - diff --git a/docs/kiri-moto/localization.md b/docs/kiri-moto/localization.md index 53011d13..12b59aec 100644 --- a/docs/kiri-moto/localization.md +++ b/docs/kiri-moto/localization.md @@ -15,4 +15,3 @@ Replace all values with correct localized values and submit a [pull request](htt If you have the time, please consider also localizing `web/kiri/lang/en-help.html` Test your language by appending `?ln:XX` to your local URL where `XX` is the name of your language file _without_ the `.js` extension. - diff --git a/docs/kiri-moto/octoprint.md b/docs/kiri-moto/octoprint.md index dc4b2ae1..85e7a1f8 100644 --- a/docs/kiri-moto/octoprint.md +++ b/docs/kiri-moto/octoprint.md @@ -14,8 +14,6 @@ The `send` button will spool a print directly to OctoPrint where it will sit und ![](/img/gridsend.png) - - ## Local Sending OctoPrint has a web api endpoint that can be configured on `Export` when enabled. diff --git a/docs/kiri-moto/shared-profiles.md b/docs/kiri-moto/shared-profiles.md index bd2970aa..427ecbdf 100644 --- a/docs/kiri-moto/shared-profiles.md +++ b/docs/kiri-moto/shared-profiles.md @@ -6,9 +6,9 @@ description: For Classrooms and Shared Workstations, Maintain Consistent Setups [Kiri:Moto](https://grid.space/kiri/) stores all settings in our browser's local cache. There are times when you want to share settings between computers or setup a master config that is shared in classroom settings. In order to facilitate this, Kiri:Moto provides two hot keys and a URL short-cut for loading a default configuration at the time the application page loads. -* `U` key (uppercase) to get a unique settings key -* `u` key (lowercase) to restore a setting with a given key -* append `#key` to Kiri's URL to load the app with a given key +- `U` key (uppercase) to get a unique settings key +- `u` key (lowercase) to restore a setting with a given key +- append `#key` to Kiri's URL to load the app with a given key Using the last method, you can create a master settings configuration then give our or link to Kiri with an url that looks like: diff --git a/docs/mesh-tool.md b/docs/mesh-tool.md index 0aeb299f..fba30fb9 100644 --- a/docs/mesh-tool.md +++ b/docs/mesh-tool.md @@ -10,7 +10,7 @@ id: mesh-tool [Mesh:Tool](https://grid.space/mesh) is an intentionally simplified tool designed to address the most common cases of mesh editing and repair. It is under active development with new features daily or weekly. This means the docs can get out of date quickly. -Mesh:Tool is part of the [Grid.Apps GitHub Repo](https://github.com/GridSpace/grid-apps) and is Open Source under the [MIT License](https://en.wikipedia.org/wiki/MIT\_License). It runs entirely in browser, is not connected to the cloud, and does not collect or share any model data. [Desktop builds](https://github.com/GridSpace/grid-apps/releases) are available whiel start in the Slicer view. Use the central drop-menu to switch to Mesh:Tool. +Mesh:Tool is part of the [Grid.Apps GitHub Repo](https://github.com/GridSpace/grid-apps) and is Open Source under the [MIT License](https://en.wikipedia.org/wiki/MIT_License). It runs entirely in browser, is not connected to the cloud, and does not collect or share any model data. [Desktop builds](https://github.com/GridSpace/grid-apps/releases) are available whiel start in the Slicer view. Use the central drop-menu to switch to Mesh:Tool. Browse the Mesh:Tool [video playlist](https://www.youtube.com/playlist?list=PLRoVgyRoWZps84Scj5wQ2LYK-wMu-7r0L) on YouTube diff --git a/docs/sidebars.js b/docs/sidebars.js index 4631ea07..429f8036 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -3,7 +3,6 @@ const sidebars = { { type: "doc", id: "index", - }, { type: "category", @@ -14,8 +13,8 @@ const sidebars = { type: "category", label: "Kiri Moto", link: { - type: 'doc', - id: 'kiri-moto/index', + type: "doc", + id: "kiri-moto/index", }, items: [ "kiri-moto/interface", @@ -28,6 +27,20 @@ const sidebars = { "kiri-moto/localization", "kiri-moto/octoprint", "kiri-moto/faq", + { + type: "category", + label: "CAM Mode", + link: { + type: "doc", + id: "kiri-moto/CAM/index", + }, + items: [ + "kiri-moto/CAM/processOpts", + "kiri-moto/CAM/machines", + "kiri-moto/CAM/tools", + "kiri-moto/CAM/ops", + ], + }, ], }, { @@ -38,39 +51,37 @@ const sidebars = { type: "category", label: "GridBot", link: { - type: 'doc', - id: 'gridbot/index', + type: "doc", + id: "gridbot/index", }, - items: [ - "gridbot/bom", - ] - } + items: ["gridbot/bom"], + }, ], }, { - type: 'category', - label: 'Community', + type: "category", + label: "Community", collapsed: false, items: [ { - type: 'link', - label: 'Forums', - href: 'https://forum.grid.space', + type: "link", + label: "Forums", + href: "https://forum.grid.space", }, { - type: 'link', - label: 'Discord', - href: 'https://discord.gg/suyCCgr', + type: "link", + label: "Discord", + href: "https://discord.gg/suyCCgr", }, { - type: 'link', - label: 'GitHub', - href: 'https://github.com/GridSpace/grid-apps', + type: "link", + label: "GitHub", + href: "https://github.com/GridSpace/grid-apps", }, { - type: 'link', - label: 'BlueSky', - href: 'https://bsky.app/profile/grid.space', + type: "link", + label: "BlueSky", + href: "https://bsky.app/profile/grid.space", }, ], }, @@ -90,4 +101,3 @@ const sidebars = { }; module.exports = sidebars; - diff --git a/docs/src/components/carousel.js b/docs/src/components/carousel.js new file mode 100644 index 00000000..49634d04 --- /dev/null +++ b/docs/src/components/carousel.js @@ -0,0 +1,80 @@ +import React from "react"; +import { Carousel } from "react-responsive-carousel"; +import "react-responsive-carousel/lib/styles/carousel.min.css"; + +const imageDescripts = { + outline: [ + ["/1.png", "outline of a complex part"], + ["/2.png", "outline of a non-flat part with inside holes"], + ["/3.png", "outside-only outline"], + ["/4.png", "inside-only outline"], + ], + level: [ + ["/1.png", "level of a large part"], + ["/2.png", `level of a square part's stock`], + ["/3.png", `level of a part with a non-uniform top surface`], + ], + rough: [ + ["/1.png", "rough cut of two parts"], + ["/2.png", "side-view of a rough cut"], + ["/3.png", "rough cut of a part with a complex top surface"], + ["/4.gif", "animate of complex rough cut"], + ], + contour: [ + ["/1.png", "close-up of a precise contour"], + ["/2.png", "contour of multiple parts at once"], + ["/3.png", "low precision contour"], + ], + register: [ + ["/1.png", "2-drill register"], + ["/2.png", "3-drill register"], + ["/3.png", "jigsaw register"], + ], + drill: [ + ["/1.png", "drills for a robot baseplate"], + ["/2.png", "drills for a pegboard"], + ["/3.png", "selecting only matching-size holes"], + ], + trace: [ + ["/1.png", "toolpath tracing a design into a part"], + ["/2.png", "selecting loops to trace"], + ["/3.png", "tracing edges for deburring"], + ["/4.png", "trace to clear a pocket"], + ["/5.png", "arbitrary offset outline"], + ], + pocket: [ + ["/1.png", "selecting areas to pocket"], + ["/2.png", "toolpath for pocket"], + ["/3.gif", "pocket animation"], + ["/4.png", "toolpath for v-bit carve"], + ["/5.png", "many-pocketed part"], + ], + gcode: [["/1.png", "the gcode editor popup"]], + laserOn: [], + laserOff: [], + index: [], + lathe: [], + tabs: [ + ["/1.png", "tabs added to part"], + ["/2.png", "sliced part with tabs"], + ], +}; + +/** + * A carousel of images. + * + * @param {keyof typeof imageDescripts} images - The key to an array of images and descriptions. + * @returns {ReactElement} - A carousel of the given images. + */ +export function ImageCarousel({ base, images }) { + return ( + + {imageDescripts[images].map(([image, caption], index) => ( +
+ +

{caption}

+
+ ))} +
+ ); +} diff --git a/docs/src/custom.css b/docs/src/custom.css index abe3290e..e51d1d87 100644 --- a/docs/src/custom.css +++ b/docs/src/custom.css @@ -1,9 +1,7 @@ - - -.navbar{ - height:5rem +.navbar { + height: 5rem; } -.navbar .navbar__items{ - height:4.5rem -} \ No newline at end of file +.navbar .navbar__items { + height: 4.5rem; +} diff --git a/docs/static/favicon.ico b/docs/static/favicon.ico new file mode 100644 index 00000000..43bbf428 Binary files /dev/null and b/docs/static/favicon.ico differ diff --git a/docs/static/img/CAM/animate.gif b/docs/static/img/CAM/animate.gif new file mode 100644 index 00000000..b4e5c4fe Binary files /dev/null and b/docs/static/img/CAM/animate.gif differ diff --git a/docs/static/img/CAM/example/contour/1.png b/docs/static/img/CAM/example/contour/1.png new file mode 100644 index 00000000..ff974f78 Binary files /dev/null and b/docs/static/img/CAM/example/contour/1.png differ diff --git a/docs/static/img/CAM/example/contour/2.png b/docs/static/img/CAM/example/contour/2.png new file mode 100644 index 00000000..1137be95 Binary files /dev/null and b/docs/static/img/CAM/example/contour/2.png differ diff --git a/docs/static/img/CAM/example/contour/3.png b/docs/static/img/CAM/example/contour/3.png new file mode 100644 index 00000000..7b6dc509 Binary files /dev/null and b/docs/static/img/CAM/example/contour/3.png differ diff --git a/docs/static/img/CAM/example/drill/1.png b/docs/static/img/CAM/example/drill/1.png new file mode 100644 index 00000000..16ed0931 Binary files /dev/null and b/docs/static/img/CAM/example/drill/1.png differ diff --git a/docs/static/img/CAM/example/drill/2.png b/docs/static/img/CAM/example/drill/2.png new file mode 100644 index 00000000..013d7cab Binary files /dev/null and b/docs/static/img/CAM/example/drill/2.png differ diff --git a/docs/static/img/CAM/example/drill/3.png b/docs/static/img/CAM/example/drill/3.png new file mode 100644 index 00000000..b42c3f73 Binary files /dev/null and b/docs/static/img/CAM/example/drill/3.png differ diff --git a/docs/static/img/CAM/example/gcode/1.png b/docs/static/img/CAM/example/gcode/1.png new file mode 100644 index 00000000..6812659e Binary files /dev/null and b/docs/static/img/CAM/example/gcode/1.png differ diff --git a/docs/static/img/CAM/example/level/1.png b/docs/static/img/CAM/example/level/1.png new file mode 100644 index 00000000..508aef72 Binary files /dev/null and b/docs/static/img/CAM/example/level/1.png differ diff --git a/docs/static/img/CAM/example/level/2.png b/docs/static/img/CAM/example/level/2.png new file mode 100644 index 00000000..f27d12f7 Binary files /dev/null and b/docs/static/img/CAM/example/level/2.png differ diff --git a/docs/static/img/CAM/example/level/3.png b/docs/static/img/CAM/example/level/3.png new file mode 100644 index 00000000..1fcb0133 Binary files /dev/null and b/docs/static/img/CAM/example/level/3.png differ diff --git a/docs/static/img/CAM/example/outline/1.png b/docs/static/img/CAM/example/outline/1.png new file mode 100644 index 00000000..500db255 Binary files /dev/null and b/docs/static/img/CAM/example/outline/1.png differ diff --git a/docs/static/img/CAM/example/outline/2.png b/docs/static/img/CAM/example/outline/2.png new file mode 100644 index 00000000..9ff160d8 Binary files /dev/null and b/docs/static/img/CAM/example/outline/2.png differ diff --git a/docs/static/img/CAM/example/outline/3.png b/docs/static/img/CAM/example/outline/3.png new file mode 100644 index 00000000..bc5f1dff Binary files /dev/null and b/docs/static/img/CAM/example/outline/3.png differ diff --git a/docs/static/img/CAM/example/outline/4.png b/docs/static/img/CAM/example/outline/4.png new file mode 100644 index 00000000..ec75ea93 Binary files /dev/null and b/docs/static/img/CAM/example/outline/4.png differ diff --git a/docs/static/img/CAM/example/pocket/1.png b/docs/static/img/CAM/example/pocket/1.png new file mode 100644 index 00000000..9230ca81 Binary files /dev/null and b/docs/static/img/CAM/example/pocket/1.png differ diff --git a/docs/static/img/CAM/example/pocket/2.png b/docs/static/img/CAM/example/pocket/2.png new file mode 100644 index 00000000..adba2f58 Binary files /dev/null and b/docs/static/img/CAM/example/pocket/2.png differ diff --git a/docs/static/img/CAM/example/pocket/3.gif b/docs/static/img/CAM/example/pocket/3.gif new file mode 100644 index 00000000..66e28c27 Binary files /dev/null and b/docs/static/img/CAM/example/pocket/3.gif differ diff --git a/docs/static/img/CAM/example/pocket/4.png b/docs/static/img/CAM/example/pocket/4.png new file mode 100644 index 00000000..117bef7e Binary files /dev/null and b/docs/static/img/CAM/example/pocket/4.png differ diff --git a/docs/static/img/CAM/example/pocket/5.png b/docs/static/img/CAM/example/pocket/5.png new file mode 100644 index 00000000..59042626 Binary files /dev/null and b/docs/static/img/CAM/example/pocket/5.png differ diff --git a/docs/static/img/CAM/example/register/1.png b/docs/static/img/CAM/example/register/1.png new file mode 100644 index 00000000..172bc214 Binary files /dev/null and b/docs/static/img/CAM/example/register/1.png differ diff --git a/docs/static/img/CAM/example/register/2.png b/docs/static/img/CAM/example/register/2.png new file mode 100644 index 00000000..67277096 Binary files /dev/null and b/docs/static/img/CAM/example/register/2.png differ diff --git a/docs/static/img/CAM/example/register/3.png b/docs/static/img/CAM/example/register/3.png new file mode 100644 index 00000000..75e97699 Binary files /dev/null and b/docs/static/img/CAM/example/register/3.png differ diff --git a/docs/static/img/CAM/example/rough/1.png b/docs/static/img/CAM/example/rough/1.png new file mode 100644 index 00000000..237075a5 Binary files /dev/null and b/docs/static/img/CAM/example/rough/1.png differ diff --git a/docs/static/img/CAM/example/rough/2.png b/docs/static/img/CAM/example/rough/2.png new file mode 100644 index 00000000..840b3f11 Binary files /dev/null and b/docs/static/img/CAM/example/rough/2.png differ diff --git a/docs/static/img/CAM/example/rough/3.png b/docs/static/img/CAM/example/rough/3.png new file mode 100644 index 00000000..4fc2500e Binary files /dev/null and b/docs/static/img/CAM/example/rough/3.png differ diff --git a/docs/static/img/CAM/example/rough/4.gif b/docs/static/img/CAM/example/rough/4.gif new file mode 100644 index 00000000..1b84b624 Binary files /dev/null and b/docs/static/img/CAM/example/rough/4.gif differ diff --git a/docs/static/img/CAM/example/trace/1.png b/docs/static/img/CAM/example/trace/1.png new file mode 100644 index 00000000..de6d452b Binary files /dev/null and b/docs/static/img/CAM/example/trace/1.png differ diff --git a/docs/static/img/CAM/example/trace/2.png b/docs/static/img/CAM/example/trace/2.png new file mode 100644 index 00000000..dac710a2 Binary files /dev/null and b/docs/static/img/CAM/example/trace/2.png differ diff --git a/docs/static/img/CAM/example/trace/3.png b/docs/static/img/CAM/example/trace/3.png new file mode 100644 index 00000000..e2324fce Binary files /dev/null and b/docs/static/img/CAM/example/trace/3.png differ diff --git a/docs/static/img/CAM/example/trace/4.png b/docs/static/img/CAM/example/trace/4.png new file mode 100644 index 00000000..24898665 Binary files /dev/null and b/docs/static/img/CAM/example/trace/4.png differ diff --git a/docs/static/img/CAM/example/trace/5.png b/docs/static/img/CAM/example/trace/5.png new file mode 100644 index 00000000..910cf226 Binary files /dev/null and b/docs/static/img/CAM/example/trace/5.png differ diff --git a/docs/static/img/CAM/exportMenu.png b/docs/static/img/CAM/exportMenu.png new file mode 100644 index 00000000..a2aea613 Binary files /dev/null and b/docs/static/img/CAM/exportMenu.png differ diff --git a/docs/static/img/CAM/machinesTab.png b/docs/static/img/CAM/machinesTab.png new file mode 100644 index 00000000..7e1fd2c9 Binary files /dev/null and b/docs/static/img/CAM/machinesTab.png differ diff --git a/docs/static/img/CAM/paramDetails.png b/docs/static/img/CAM/paramDetails.png new file mode 100644 index 00000000..023a456b Binary files /dev/null and b/docs/static/img/CAM/paramDetails.png differ diff --git a/docs/static/img/CAM/preview.png b/docs/static/img/CAM/preview.png new file mode 100644 index 00000000..029614ff Binary files /dev/null and b/docs/static/img/CAM/preview.png differ diff --git a/docs/static/img/CAM/process/hover.png b/docs/static/img/CAM/process/hover.png new file mode 100644 index 00000000..24d0301f Binary files /dev/null and b/docs/static/img/CAM/process/hover.png differ diff --git a/docs/static/img/CAM/process/profiles.png b/docs/static/img/CAM/process/profiles.png new file mode 100644 index 00000000..aa86810e Binary files /dev/null and b/docs/static/img/CAM/process/profiles.png differ diff --git a/docs/static/img/CAM/process/tabs/1.png b/docs/static/img/CAM/process/tabs/1.png new file mode 100644 index 00000000..71deff0a Binary files /dev/null and b/docs/static/img/CAM/process/tabs/1.png differ diff --git a/docs/static/img/CAM/process/tabs/2.png b/docs/static/img/CAM/process/tabs/2.png new file mode 100644 index 00000000..6d5c17a0 Binary files /dev/null and b/docs/static/img/CAM/process/tabs/2.png differ diff --git a/docs/static/img/CAM/sliceToggle.png b/docs/static/img/CAM/sliceToggle.png new file mode 100644 index 00000000..bf429125 Binary files /dev/null and b/docs/static/img/CAM/sliceToggle.png differ diff --git a/docs/static/img/CAM/toolMenu.png b/docs/static/img/CAM/toolMenu.png new file mode 100644 index 00000000..db7ea748 Binary files /dev/null and b/docs/static/img/CAM/toolMenu.png differ diff --git a/docs/static/img/CAM/toolsTab.png b/docs/static/img/CAM/toolsTab.png new file mode 100644 index 00000000..53bf1853 Binary files /dev/null and b/docs/static/img/CAM/toolsTab.png differ diff --git a/docusaurus.config.js b/docusaurus.config.js index 26d63e8a..316a7ece 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -4,6 +4,7 @@ module.exports = { url: 'https://docs.grid.space', title: 'grid.space docs', baseUrl : '/', + favicon: 'img/gs-logo.png', staticDirectories: ['./docs/static/'], // ... plugins: [require.resolve('docusaurus-lunr-search')], diff --git a/mods/bambu/bambu.js b/mods/bambu/bambu.js index 5d182528..2eb1ac49 100644 --- a/mods/bambu/bambu.js +++ b/mods/bambu/bambu.js @@ -34,6 +34,8 @@ self.kiri.load(api => { "White": "#FFFFFF" }).map(v => `${v.substring(1)}FF`); + let local_port = 5309; + let local_url = `http://localhost:${local_port}`; let sequence_id = (Math.random() * 0xfff) | 0; let user_id = ((Math.random() * 0xfffffff) | 0).toString(); let init = false; @@ -52,7 +54,14 @@ self.kiri.load(api => { if (socket.ws) { return; } - let ws = socket.ws = new WebSocket("/bambu"); + let ws_url = api.onshape ? `${local_url}/bambu` : "/bambu"; + let ws = socket.ws = new WebSocket(ws_url); + ws.onerror = (error) => { + socket.open = false; + socket.ws = undefined; + console.log({ bambu_ws_error: error }); + ws_url = "/bambu"; + }; ws.onopen = () => { socket.open = true; socket.drain(); @@ -357,6 +366,7 @@ self.kiri.load(api => { total_layer_num, upload } = print || {}; + let print_busy = print_type !== 'idle'; let { tray_pre, tray_now, tray_tar } = ams || {}; let trays = ams?.ams?.map((ams, unit) => { return ams.tray.map(tray => { @@ -426,7 +436,7 @@ self.kiri.load(api => { $('bbl_ams_tray').innerHTML = ''; $('bbl_file_spool').innerHTML = ''; $('print-bambu-spool').innerHTML = ''; - print_ams_select = 'auto'; + // print_ams_select = 'auto'; } let state = (gcode_state || 'unknown').toLowerCase(); $('bbl_noz').value = nozzle_diameter || ''; @@ -438,8 +448,8 @@ self.kiri.load(api => { $('bbl_bed_on').checked = bed_target_temper > 0; $('bbl_pause').disabled = (gcode_state !== 'RUNNING'); $('bbl_resume').disabled = (gcode_state !== 'PAUSE' || gcode_state === 'FAILED');// || print_error); - $('bbl_stop').disabled = gcode_file ? false : true; - $('bbl_file_print').disabled = gcode_file ? true : false; + $('bbl_stop').disabled = print_busy ? false : true; + $('bbl_file_print').disabled = print_busy ? true : false; $('bbl_fan_part').value = cooling_fan_speed || 0; $('bbl_fan_part_on').checked = cooling_fan_speed > 0 ? true : false; $('bbl_fan_1').value = big_fan1_speed || 0; @@ -1279,7 +1289,7 @@ self.kiri.load(api => { function send(filename, gcode, start) { const spool = print_ams_select; - const baseUrl = '/api/bambu_send'; + const baseUrl = api.onshape ? `${local_url}/api/bambu_send` : "/api/bambu_send"; const url = new URL(baseUrl, window.location.origin); url.searchParams.append('host', host); url.searchParams.append('code', password); @@ -1288,7 +1298,7 @@ self.kiri.load(api => { url.searchParams.append('start', start ?? false); url.searchParams.append('ams', spool === 'auto' && amsmap ? amsmap : spool); - const alert = api.alerts.show('Sending to Bambu Printer'); + const alert = api.alerts.show('Sending to Bambu Printer', 120); fetch(url.toString(), { headers: { 'Content-Type': 'text/plain' }, diff --git a/mods/bambu/init.js b/mods/bambu/init.js index 370554c6..e0b98bb7 100644 --- a/mods/bambu/init.js +++ b/mods/bambu/init.js @@ -309,7 +309,9 @@ module.exports = async (server) => { } if (!found[rec.DevName]) { found[rec.DevName] = nurec; - util.log(`found Bambu ${nurec.name} ${nurec.srno} @ ${nurec.host}`); + let privsn = nurec.srno; + privsn = privsn.slice(0,8) + privsn.slice(8).replace(/./g, 'X'); + util.log(`found Bambu ${nurec.name} ${privsn} @ ${nurec.host}`); } wsend({ found }); } diff --git a/package.json b/package.json index dfb869be..b2659a6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "grid-apps", - "version": "4.1.8", + "version": "4.2.0", "description": "grid.space 3d slicing & modeling tools", "author": "Stewart Allen ", "license": "MIT", @@ -36,7 +36,7 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.1.1", - "@gridspace/app-server": "^0.0.16", + "@gridspace/app-server": "^0.0.17", "@gridspace/net-level-client": "^0.2.3", "@tracespace/parser": "^5.0.0-next.0", "@tweenjs/tween.js": "^16.6.0", @@ -50,6 +50,8 @@ "jszip": "^3.7.1", "manifold-3d": "^2.5.1", "moment": "^2.29.4", + "prettier": "^3.5.3", + "react-responsive-carousel": "^3.2.23", "serve-static": "^1.14.1", "three": "^0.174.0", "three-mesh-bvh": "^0.7.6", @@ -97,7 +99,8 @@ "preinstall": "node bin/install-pre.js && npx webpack --config bin/webpack-three.js", "docs-dev": "docusaurus start", "docs-build": "docusaurus build", - "docs-serve": "docusaurus serve" + "docs-serve": "docusaurus serve", + "docs-check": "prettier ./docs --check" }, "main": "app-el.js", "build": { diff --git a/src/geo/polygon.js b/src/geo/polygon.js index 4e1982be..4f9d0501 100644 --- a/src/geo/polygon.js +++ b/src/geo/polygon.js @@ -1002,6 +1002,23 @@ class Polygon { return this.z !== undefined ? this.z : this.points[i || 0]?.z || 0; } + minZ() { + return Math.min(...this.points.map(p => p.z)); + } + + avgZ() { + return [...this.points.map(p => p.z)].reduce((a,v) => a+v) / this.points.length; + } + + /** + * @param {*} z value target + * @param {*} epsilon allowed Z variance + * @returns {boolean} true if all points Z within epsilon of value + */ + onZ(z, epsilon = 10e-4) { + return Math.max(...this.points.map(p => Math.abs(z - p.z))) < epsilon; + } + /** */ render(layer, color, recursive, open) { diff --git a/src/kiri-mode/cam/animate2.js b/src/kiri-mode/cam/animate2.js index 9cbc4ddd..c2d2618b 100644 --- a/src/kiri-mode/cam/animate2.js +++ b/src/kiri-mode/cam/animate2.js @@ -540,7 +540,7 @@ kiri.load(() => { const sd = Math.sqrt(mx*mx + my*my + Math.min(1,mz*mz) + dr*dr); const moves = []; for (let i=0, x=lp.x, y=lp.y, z=lp.z; i { if (!pos) { throw `no pos @ ${index} of ${moves.length}`; } + const { dx, dy, dz } = pos; toolMove(pos); // console.log('renderMoves', {id, moves, seed}); let subs = 0; + if (dx || dy || dz < 0) for (let slice of stockSlices) { if (slice.bounds.intersectsBox(toolMesh.bounds)) { slice.subtractTool(toolMesh); diff --git a/src/kiri-mode/cam/client.js b/src/kiri-mode/cam/client.js index 94b44438..8edaee87 100644 --- a/src/kiri-mode/cam/client.js +++ b/src/kiri-mode/cam/client.js @@ -474,13 +474,11 @@ CAM.init = function(kiri, api) { return; } let index = 0; - let indexing = false; for (let op of oplist) { if (op.type === '|') { break; } if (op.type === 'index' && !op.disabled) { - indexing = true; if (op.absolute) { index = op.degrees } else { @@ -1008,7 +1006,6 @@ CAM.init = function(kiri, api) { CAM.traces((ids) => { api.hide.alert(alert); alert = api.show.alert("[esc] cancels trace editing"); - kiri.api.widgets.opacity(0.8); kiri.api.widgets.for(widget => { if (ids.indexOf(widget.id) >= 0) { unselectTraces(widget, true); @@ -1042,6 +1039,7 @@ CAM.init = function(kiri, api) { }); // ensure appropriate traces are toggled matching current record kiri.api.widgets.for(widget => { + widget.setVisualState({ opacity: 0.25 }); let areas = (poppedRec.areas[widget.id] || []); let stack = widget.trace_stack; stack.meshes.forEach(mesh => { @@ -1070,6 +1068,7 @@ CAM.init = function(kiri, api) { api.feature.hover = false; api.feature.hoverAdds = false; kiri.api.widgets.for(widget => { + widget.restoreVisualState(); if (widget.trace_stack) { widget.trace_stack.hide(); widget.adds.removeAll(widget.trace_stack.meshes); @@ -1096,17 +1095,15 @@ CAM.init = function(kiri, api) { color.r = colorSave.r; color.g = colorSave.g; color.b = colorSave.b; - lastTrace.position.z -= 0.01; } + lastTrace = null; if (data.type === 'platform') { - lastTrace = null; return; } if (!data.int.object.trace) { return; } lastTrace = data.int.object; - lastTrace.position.z += 0.01; if (lastTrace.selected) { let event = data.event; let target = event.target; @@ -1115,11 +1112,8 @@ CAM.init = function(kiri, api) { } let material = lastTrace.material[0] || lastTrace.material; let color = material.color; - let {r, g, b} = color; - material.colorSave = {r, g, b}; - color.r = 0; - color.g = 0; - color.b = 1; + material.colorSave = color.clone(); + color.setHex(isDark() ? 0x0066ff : 0x0000ff); }; func.traceHoverUp = function(int, ev) { if (!int) return; @@ -1128,8 +1122,9 @@ CAM.init = function(kiri, api) { if (ev.metaKey || ev.ctrlKey) { let { selected } = object; let { widget, poly } = object.trace; + let avgZ = poly.avgZ(); for (let add of widget.adds) { - if (add.trace && add.selected !== selected && add.trace.poly.getZ() === poly.getZ()) { + if (add.trace && add.selected !== selected && add.trace.poly.onZ(avgZ)) { func.traceToggle(add); } } @@ -1147,23 +1142,17 @@ CAM.init = function(kiri, api) { let wlist = areas[widget.id] = areas[widget.id] || []; obj.selected = !obj.selected; if (!colorSave) { - colorSave = material.colorSave = { - r: color.r, - g: color.g, - b: color.b - }; + colorSave = material.colorSave = color.clone(); } if (obj.selected) { - obj.position.z += 0.01; - color.r = colorSave.r = 0.9; - color.g = colorSave.g = 0; - color.b = colorSave.b = 0.1; + color.setHex(isDark() ? 0xdd0011 : 0xff0033); + colorSave.r = color.r; + colorSave.g = color.g; + colorSave.b = color.b; if (!skip) wlist.push(poly._trace); } else { - obj.position.z -= 0.01; - color.r = colorSave.r = 0xaa/255; - color.g = colorSave.g = 0xaa/255; - color.b = colorSave.b = 0x55/255; + color.setHex(0xaaaa55); + colorSave.setHex(0xaaaa55); if (!skip) wlist.remove(poly._trace); } API.conf.save(); @@ -1192,9 +1181,6 @@ CAM.init = function(kiri, api) { func.hover = func.selectHolesHover; func.hoverUp = func.selectHolesHoverUp; - - - const widgets = kiri.api.widgets.all() /** * creates a mesh for a hole and adds it to a widget @@ -1266,7 +1252,7 @@ CAM.init = function(kiri, api) { centers = centers ?? [] // list of all hole centers and if they are selected kiri.api.widgets.for(widget => { - const {holes} = centers.find(center=>center.id = widget.id) + const {holes} = centers.find(center=>center.id == widget.id) // console.log(holes) if (!holes.length) unselectHoles(holes); holes.forEach(hole => { @@ -1712,13 +1698,11 @@ CAM.init = function(kiri, api) { dogbone: 'camTraceDogbone', revbone: 'camTraceDogbone', merge: 'camTraceMerge', - select: 'camTraceMode', ov_topz: 0, ov_botz: 0, ov_conv: '~camConventional', }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), - select: UC.newSelect(LANG.cc_sele_s, {title:LANG.cc_sele_l}, "select"), mode: UC.newSelect(LANG.cu_type_s, {title:LANG.cu_type_l}, "trace"), offset: UC.newSelect(LANG.cc_offs_s, {title: LANG.cc_offs_l, show:() => (poppedRec.mode === 'follow')}, "traceoff"), sep: UC.newBlank({class:"pop-sep"}), @@ -1791,7 +1775,7 @@ CAM.init = function(kiri, api) { menu: UC.newRow([ UC.newButton("select", func.surfaceAdd) ], {class:"ext-buttons f-row"}), }; - const drillOp =createPopOp('drill', { + createPopOp('drill', { tool: 'camDrillTool', spindle: 'camDrillSpindle', down: 'camDrillDown', @@ -1801,7 +1785,7 @@ CAM.init = function(kiri, api) { mark: 'camDrillMark', precision:'camDrillPrecision', thru: 'camDrillThru', - + fromTop: 'camDrillFromStockTop', }).inputs = { tool: UC.newSelect(LANG.cc_tool, {}, "tools"), sep: UC.newBlank({class:"pop-sep"}), @@ -1810,7 +1794,8 @@ CAM.init = function(kiri, api) { down: UC.newInput(LANG.cc_sdwn_s, {title:LANG.cc_sdwn_l, convert:UC.toFloat, units:true}), dwell: UC.newInput(LANG.cd_dwll_s, {title:LANG.cd_dwll_l, convert:UC.toFloat}), lift: UC.newInput(LANG.cd_lift_s, {title:LANG.cd_lift_l, convert:UC.toFloat, units:true, show:() => !poppedRec.mark}), - mark: UC.newBoolean(LANG.cd_mark_s, undefined, {title:LANG.cd_mark_l}), + mark: UC.newBoolean(LANG.cd_mark_s,undefined, {title:LANG.cd_mark_l, show:() => !poppedRec.fromTop}), + fromTop: UC.newBoolean(LANG.cd_ftop_s,undefined, {title:LANG.cd_ftop_l, show:() => !poppedRec.mark}), sep: UC.newBlank({class:"pop-sep"}), thru: UC.newInput(LANG.cd_dtru_s, {title:LANG.cd_dtru_l, convert:UC.toFloat, units:true,show:() => !poppedRec.mark}), precision:UC.newInput(LANG.cd_prcn_s, {title:LANG.cd_prcn_l, convert:UC.toFloat, units:true,show:() => !poppedRec.mark}), @@ -1968,10 +1953,12 @@ function createPopOp(type, map) { const settings = conf.get(); const {tool} = new CAM.Tool(settings,op.rec.tool); //get tool by id const opType = op.rec.type - if( opType != "drill" && tool.type == "drill"){ + const drillOrRegister = opType == "drill" || opType == "register" + + if ( !drillOrRegister && tool.type == "drill"){ alerts.show(`Warning: Drills should not be used for ${opType} operations.`) } - else if( opType == "drill" && tool.type != "drill"){ + else if ( drillOrRegister && tool.type != "drill"){ alerts.show(`Warning: Only drills should be used for drilling operations.`) } @@ -2159,16 +2146,18 @@ function validateTools(tools) { } } +function isDark() { return API.space.is_dark() }; + function addbox() { return FDM.addbox(...arguments)}; function delbox() { return FDM.delbox(...arguments)}; function boxColor() { - return API.space.is_dark() ? 0x00ddff : 0x0000dd; + return isDark() ? 0x00ddff : 0x0000dd; } function boxOpacity() { - return API.space.is_dark() ? 0.75 : 0.6; + return isDark() ? 0.75 : 0.6; } function animate() { @@ -2199,6 +2188,8 @@ function updateStock() { return; } + api.platform.update_bounds(); + const settings = API.conf.get(); const widgets = API.widgets.all(); @@ -2206,9 +2197,13 @@ function updateStock() { const { x, y, z, center } = stock; UI.func.animate.classList.add('disabled'); + if (camStock) { + SPACE.world.remove(camStock); + camStock = null; + } if (x && y && z) { UI.func.animate.classList.remove('disabled'); - if (!camStock) { + { let geo = new THREE.BoxGeometry(1, 1, 1); let mat = new THREE.MeshBasicMaterial({ color: 0x777777, @@ -2240,21 +2235,17 @@ function updateStock() { let lines = new THREE.LineSegments(ligeo, limat); camStock.lines = lines; camStock.add(lines); - SPACE.world.add(camStock); } + // fight z fighting in threejs camStock.scale.x = x + 0.005; camStock.scale.y = y + 0.005; camStock.scale.z = z + 0.005; camStock.position.x = center.x; camStock.position.y = center.y; camStock.position.z = center.z; - camStock.rotation.x = currentIndex || 0; camStock.lines.material.color = - new THREE.Color(API.space.is_dark() ? 0x555555 : 0xaaaaaa); - } else if (camStock) { - SPACE.world.remove(camStock); - camStock = null; + new THREE.Color(isDark() ? 0x555555 : 0xaaaaaa); } SPACE.world.remove(camZTop); diff --git a/src/kiri-mode/cam/export.js b/src/kiri-mode/cam/export.js index 2a9e4b83..3ac5c2eb 100644 --- a/src/kiri-mode/cam/export.js +++ b/src/kiri-mode/cam/export.js @@ -391,7 +391,7 @@ CAM.export = function(print, online) { append(`; Generated by Kiri:Moto ${kiri.version}`); append(`; ${new Date().toString()}`); filterEmit(["; Bed left:{left} right:{right} top:{top} bottom:{bottom}"], consts); - append(`; Stock X:${stock.x.round(2)} Y:${stock.y.round(2)} Z:${stock.z.round(2)}`); + append(`; Stock X:${stock.x?.round(2)} Y:${stock.y?.round(2)} Z:${stock.z?.round(2)}`); append(`; Target: ${settings.filter[settings.mode]}`); append("; --- process ---"); for (let pk in spro) { diff --git a/src/kiri-mode/cam/ops.js b/src/kiri-mode/cam/ops.js index eb22e26f..74200790 100644 --- a/src/kiri-mode/cam/ops.js +++ b/src/kiri-mode/cam/ops.js @@ -18,6 +18,7 @@ const { poly2polyEmit, tip2tipEmit, segmentNormal, vertexNormal } = paths; const { driver, newSlice } = kiri; const { CAM } = driver; +const DEG2RAG = Math.PI / 180; const POLY = polygons; class CamOp { @@ -44,12 +45,17 @@ class OpIndex extends CamOp { super(state, op); } - slice() { + async slice() { let { op, state } = this; if (!state.isIndexed) { throw 'index op requires indexed stock'; } - this.degrees = state.setAxisIndex(op.degrees, op.absolute); + let { widget, updateSlicer, computeShadows, setAxisIndex } = state; + this.degrees = setAxisIndex(op.degrees, op.absolute); + // force recompute of topo + widget.topo = undefined; + updateSlicer(); + await computeShadows(); } prepare(ops, progress) { @@ -199,9 +205,6 @@ class OpRough extends CamOp { POLY.offset([ newPolygon().centerRectangle(stock.center, stock.x, stock.y) ], step) : POLY.offset(shadow, roughIn ? step : step + roughLeave + toolDiam / 2); let facing = POLY.offset(inset, -step, { count: 999, flat: true }); - if (isIndexed) { - ztOff = (stock.z / 2) - zMax; - } let zdiv = ztOff / roughDown; let zstep = (zdiv % 1 > 0) ? ztOff / (Math.floor(zdiv) + 1) : roughDown; if (ztOff === 0) { @@ -482,8 +485,7 @@ class OpOutline extends CamOp { async slice(progress) { let { op, state } = this; let { settings, widget, slicer, addSlices, tshadow, thruHoles, unsafe, color } = state; - let { updateToolDiams, tabs, cutTabs, cutPolys, workarea } = state; - let { zMax } = state; + let { updateToolDiams, tabs, cutTabs, cutPolys, workarea, zMax } = state; let { process, stock } = settings; if (op.down <= 0) { @@ -975,11 +977,12 @@ class OpTrace extends CamOp { } async slice(progress) { + const debug = false; let { op, state } = this; let { tool, rate, down, plunge, offset, offover, thru } = op; let { ov_conv } = op; let { settings, widget, addSlices, zThru, tabs, workarea } = state; - let { updateToolDiams, cutTabs, cutPolys, healPolys, color } = state; + let { updateToolDiams, cutTabs, cutPolys, healPolys, color, shadowAt } = state; let { process, stock } = settings; let { camStockClipTo } = process; if (state.isIndexed) { @@ -1041,17 +1044,39 @@ class OpTrace extends CamOp { .setLayer("trace follow", {line: color}, false) .addPolys(slice.camLines) } - function clearZ(polys, z, down) { + function clearZnew(polys, z, down) { + if (down) { + // adjust step down to a value <= down that + // ends on the lowest z specified + let diff = zTop - z; + down = diff / Math.ceil(diff / down); + } let zs = down ? base.util.lerp(zTop, z, down) : [ z ]; - let nested = POLY.nest(polys); - for (let poly of nested) { + let zpro = 0, zinc = 1 / (polys.length * zs.length); + for (let poly of polys) { + // newPocket(); for (let z of zs) { + let clip = [], shadow; + shadow = shadowAt(z); + POLY.subtract([ poly ], shadow, clip, undefined, undefined, 0); + if (op.outline) { + POLY.clearInner(clip); + } + if (clip.length === 0) { + continue; + } let slice = newSliceOut(z); + let count = 999; slice.camTrace = { tool, rate, plunge }; - POLY.offset([ poly ], [ -toolDiam/2, -toolOver ], { - count:999, outs: slice.camLines = [], flat:true, z, - minArea: 0 - }); + if (toolDiam) { + const offs = [ -toolDiam / 2, -toolOver ]; + POLY.offset(clip, offs, { + count, outs: slice.camLines = [], flat:true, z, minArea: 0 + }); + } else { + // when engraving with a 0 width tip + slice.camLines = clip; + } if (tabs) { slice.camLines = cutTabs(tabs, POLY.flatten(slice.camLines, null, true), z); } else { @@ -1059,8 +1084,14 @@ class OpTrace extends CamOp { } POLY.setWinding(slice.camLines, cutdir, false); slice.output() - .setLayer("trace clear", {line: color}, false) + .setLayer("trace", {line: color}, false) .addPolys(slice.camLines) + if (debug && shadow) slice.output() + .setLayer("trace shadow", {line: 0xff8811}, false) + .addPolys(shadow) + progress(zpro, "trace"); + zpro += zinc; + addSlices(slice); } } } @@ -1175,7 +1206,7 @@ class OpTrace extends CamOp { const zbo = widget.track.top - widget.track.box.d; let zmap = {}; for (let poly of polys) { - let z = minZ(poly.getZ()); + let z = minZ(poly.minZ()); if (offover) { let pnew = POLY.offset([poly], -offover, { minArea: 0, open: true }); if (pnew) { @@ -1189,7 +1220,7 @@ class OpTrace extends CamOp { (zmap[z] = zmap[z] || []).appendAll(poly); } for (let [zv, polys] of Object.entries(zmap)) { - clearZ(polys, parseFloat(zv), down); + clearZnew(polys, parseFloat(zv), down); } } } @@ -1347,7 +1378,7 @@ class OpPocket extends CamOp { let vert = widget.getGeoVertices({ unroll: true, translate: true }).map(v => v.round(4)); // let vert = widget.getVertices().array.map(v => v.round(4)); let outline = []; - let faces = CAM.surface_find(widget, surfaces, 0.1); + let faces = CAM.surface_find(widget, surfaces, (op.follow || 5) * DEG2RAG); let zmin = Infinity; let j=0, k=faces.length; for (let face of faces) { diff --git a/src/kiri-mode/cam/prepare.js b/src/kiri-mode/cam/prepare.js index 8971655b..eb13ec12 100644 --- a/src/kiri-mode/cam/prepare.js +++ b/src/kiri-mode/cam/prepare.js @@ -85,12 +85,12 @@ function prepEach(widget, settings, print, firstPoint, update) { bounds = widget.getBoundingBox(), boundsz = isIndexed ? stock.z / 2 : bounds.max.z + ztOff, zadd = !isIndexed ? stock.z - boundsz : alignTop ? outerz - boundsz : 0, - zmax = outerz + zclear + process.camOriginOffZ, + zmax = outerz + zclear + (process.camOriginOffZ || 0), wmpos = widget.track.pos, wmx = wmpos.x, wmy = wmpos.y, - originx = (startCenter ? 0 : -stock.x / 2) + process.camOriginOffX, - originy = (startCenter ? 0 : -stock.y / 2) + process.camOriginOffY, + originx = (startCenter ? 0 : -stock.x / 2) + (process.camOriginOffX || 0), + originy = (startCenter ? 0 : -stock.y / 2) + (process.camOriginOffY || 0), origin = newPoint(originx + wmx, originy + wmy, zmax), output = print.output, easeDown = process.camEaseDown, diff --git a/src/kiri-mode/cam/slice.js b/src/kiri-mode/cam/slice.js index 9f0db31a..5843c98c 100644 --- a/src/kiri-mode/cam/slice.js +++ b/src/kiri-mode/cam/slice.js @@ -2,6 +2,7 @@ "use strict"; +// dep: geo.line // dep: geo.point // dep: geo.polygons // dep: kiri.slice @@ -14,7 +15,7 @@ gapp.register("kiri-mode.cam.slice", (root, exports) => { const { base, kiri } = root; const { util } = base; const { driver, newSlice, setSliceTracker } = kiri; -const { polygons, newPoint, newPolygon } = base; +const { polygons, newLine, newPoint, newPolygon } = base; const { CAM } = driver; const { OPS } = CAM; @@ -31,7 +32,6 @@ const POLY = polygons; CAM.slice = async function(settings, widget, onupdate, ondone) { let proc = settings.process, sliceAll = widget.slices = [], - // slices = widget.slices = [], camOps = widget.camops = [], isIndexed = proc.camStockIndexed; @@ -47,13 +47,22 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { // allow recomputing later if widget or settings changes const var_compute = () => { - stock = settings.stock || {}; + let { camStockX, camStockY, camStockZ, camStockOffset } = proc; bounds = widget.getBoundingBox(); + stock = camStockOffset ? { + x: bounds.dim.x + camStockX, + y: bounds.dim.y + camStockY, + z: bounds.dim.z + camStockZ, + } : { + x: camStockX, + y: camStockY, + z: camStockZ + } track = widget.track; ({ camZTop, camZBottom, camZThru } = proc); wztop = track.top; - ztOff = isIndexed ? 0 : (stock.z - wztop); - zbOff = isIndexed ? 0 : (wztop - track.box.d); + ztOff = isIndexed ? (stock.z - bounds.dim.z) / 2 : (stock.z - wztop); + zbOff = isIndexed ? (stock.z - bounds.dim.z) / 2 : (wztop - track.box.d); zBottom = isIndexed ? camZBottom : camZBottom - zbOff; zMin = isIndexed ? bounds.min.z : Math.max(bounds.min.z, zBottom); zMax = bounds.max.z; @@ -72,7 +81,7 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { bottom_part = 0; bottom_stock = -bottom_gap; bottom_thru = zThru; - bottom_z = Math.max( + bottom_z = isIndexed ? zBottom : Math.max( (camZBottom ? bottom_stock + camZBottom : bottom_part) - bottom_thru, (camZBottom ? bottom_stock + camZBottom : bottom_stock - bottom_thru) ); @@ -125,7 +134,7 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { return error('no processes specified'); } - if (stock.x && stock.y && stock.z) { + if (stock.x && stock.y && stock.z && !isIndexed) { if (stock.x + 0.00001 < bounds.max.x - bounds.min.x) { return error('stock X too small for part. resize stock or use offset stock'); } @@ -144,15 +153,55 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } let mark = Date.now(); - let slicer = new kiri.cam_slicer(widget); + let opList = []; + let opSum = 0; + let opTot = 0; + let shadows = {}; + let slicer; + let state = { + settings, + widget, + bounds, + tabs, + cutTabs, + cutPolys, + healPolys, + shadowAt, + slicer, + addSlices, + isIndexed, + setAxisIndex, + updateToolDiams, + updateSlicer, + computeShadows, + zBottom, + zThru, + ztOff, + zMax, + zTop, + unsafe, + color, + dark, + ops: opList + }; + let tracker = setSliceTracker({ rotation: 0 }); + + function updateSlicer() { + slicer = state.slicer = new kiri.cam_slicer(widget); + } + + async function computeShadows() { + shadows = {}; + await new CAM.OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => { + // console.log('reshadow', progress.round(3)); + }); + } function updateToolDiams(toolDiam) { minToolDiam = Math.min(minToolDiam, toolDiam); maxToolDiam = Math.max(maxToolDiam, toolDiam); } - let shadows = {}; - function shadowAt(z) { let cached = shadows[z]; if (cached) { @@ -215,44 +264,22 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } } - let state = { - settings, - widget, - bounds, - tabs, - cutTabs, - cutPolys, - healPolys, - shadowAt, - slicer, - addSlices, - isIndexed, - setAxisIndex, - updateToolDiams, - zBottom, - zThru, - ztOff, - zMax, - zTop, - unsafe, - color, - dark, - }; - - let opList = [ - // silently preface op list with OpShadow - new CAM.OPS.shadow(state, { type: "shadow", silent: true }) - ]; - if (false) { opList.push(new CAM.OPS.xray(state, { type: "xray" })); } - let opSum = 0; - let opTot = opList.length ? opList.map(op => op.weight()).reduce((a,v) => a + v) : 0; + let activeOps = proc.ops.filter(op => !op.disabled); + + // silently preface op list with OpShadow + if (isIndexed) { + if (activeOps.length === 0 || activeOps[0].type !== 'index') + opList.push(new CAM.OPS.index(state, { type: "index", index: 0 })); + } else { + opList.push(new CAM.OPS.shadow(state, { type: "shadow", silent: true })); + } // determing # of steps and step weighting for progress bar - for (let op of proc.ops.filter(op => !op.disabled)) { + for (let op of activeOps) { if (op.type === '|') { break; } @@ -264,39 +291,41 @@ CAM.slice = async function(settings, widget, onupdate, ondone) { } } - // give ops access to entire sequence - state.ops = opList; - // call slice() function on all ops in order - let tracker = setSliceTracker({ rotation: 0 }); setAxisIndex(); + updateSlicer(); for (let op of opList) { let weight = op.weight(); // apply operation override vars let workover = var_compute(); let valz = op.op; if (valz.ov_topz) { - workover.top_z = bottom_stock + valz.ov_topz; + workover.top_z = isIndexed ? valz.ov_topz : bottom_stock + valz.ov_topz; } if (valz.ov_botz) { - workover.bottom_z = bottom_stock + valz.ov_botz; + workover.bottom_z = isIndexed ? valz.ov_botz : bottom_stock + valz.ov_botz; workover.bottom_cut = Math.max(workover.bottom_z, -zThru); } - state.workarea = workover; + // state.workarea = workover; + Object.assign(state, { + zBottom, + zThru, + ztOff, + zMax, + zTop, + workarea: workover + }); + // console.log({ + // op, + // workover, + // bounds: structuredClone(bounds), + // stock: structuredClone(stock) + // }); await op.slice((progress, message) => { onupdate((opSum + (progress * weight)) / opTot, message || op.type()); }); + // update tracker rotation for next slice output() visualization tracker.rotation = isIndexed ? axisRotation : 0; - // setup new state when indexing the workspace - if (op.op.type === "index") { - widget.topo = undefined; - // let points = base.verticesToPoints(); - state.slicer = new kiri.cam_slicer(widget); - shadows = {}; - await new CAM.OPS.shadow(state, { type: "shadow", silent: true }).slice(progress => { - // console.log('reshadow', progress.round(3)); - }); - } camOps.push(op); opSum += weight; } @@ -379,60 +408,107 @@ CAM.addDogbones = function(poly, dist, reverse) { }; CAM.traces = async function(settings, widget, single) { - if (widget.traces && widget.trace_single === single) { - // do no work if cached + if (widget.traces) { return false; } - let slicer = new kiri.cam_slicer(widget); - let zees = Object.assign({}, slicer.zFlat, slicer.zLine); - let indices = [...new Set(Object.keys(zees) - .map(kv => parseFloat(kv).round(5)) - .appendAll(Object.entries(single ? slicer.zLine : {}).map(ze => { - let [ zk, zv ] = ze; - return zv > 1 ? parseFloat(zk).round(5) : null; - }) - .filter(v => v !== null)) - )] - .sort((a,b) => b - a); - let traces = []; - // find and trim polys (including open) to shadow - let oneach = data => { - if (single) { - for (let line of data.lines) { - if (line.p1.distTo2D(line.p2) > 1) { - traces.push(newPolygon().append(line.p1).append(line.p2).setOpen()); - } - } - } else - base.polygons.flatten(data.tops).forEach(poly => { - poly.inner = null; - poly.parent = null; - let z = poly.getZ(); - for (let i=0, il=traces.length; i 0.01) { - continue; - } - // do not add duplicates - if (traces[i].isEquivalent(poly) && dz < 0.1) { - // console.log({ dup: poly }); - return; - } - } - traces.push(poly); - }); - }; + // --- points → line segments --- + let edges = new THREE.EdgesGeometry(widget.mesh.geometry, 20); + let array = edges.attributes.position.array; + let pcache = {}; + let points = new Array(2); + let lines = new Array(points.length / 2); + for (let i=0, j=0, k=0, l=array.length; i (v * 10000) | 0).join(','); + let point = pcache[key]; + if (!point) { + point = newPoint(ps[0], ps[1], ps[2], key); + point.lines = []; + pcache[key] = point; + } + points[j++ % 2] = point; + if (j % 2 === 0) { + let [ p0, p1 ] = points; + let line = lines[k++] = newLine(p0, p1); + p0.lines.push(line); + p1.lines.push(line); + } + } + + // --- segments → chains (bidirectional walk) --- + let chains = []; + { + const segs = lines.slice(); // shallow copy + segs.forEach(s => (s.visited = false)); + + function step(dirPoint, prevSeg, pushFront, chain) { + let curr = dirPoint; + let prev = prevSeg; + + while (curr.lines.length === 2) { // stay inside the arc + const next = curr.lines.find(l => !l.visited && l !== prev); + if (!next) break; // should not happen + + next.visited = true; + if (pushFront) chain.unshift(next); // prepend or append? + else chain.push(next); + + curr = next.p1 === curr ? next.p2 : next.p1; + prev = next; + } + } + + for (const seed of segs) { + if (seed.visited) continue; + + seed.visited = true; // always include the seed + const chain = [seed]; + + // grow backwards from p1 (prepend) + step(seed.p1, seed, true, chain); + + // grow forwards from p2 (append) + step(seed.p2, seed, false, chain); + + chains.push(chain); + } + } + + // --- chains → polylines --- + const polylines = []; + + for (const chain of chains) { + const poly = newPolygon().setOpen(); + + // choose any node whose degree !== 2; if none, it’s a closed loop + let start = null; + for (const s of chain) { + if (s.p1.lines.length !== 2) { start = s.p1; break; } + if (s.p2.lines.length !== 2) { start = s.p2; break; } + } + if (!start) start = chain[0].p1; // closed loop + + let curr = start, prevSeg = null; + poly.push(curr); // first point + + while (true) { + const nextSeg = curr.lines.find( + l => l !== prevSeg && chain.includes(l) + ); + if (!nextSeg) break; // open end reached + + curr = nextSeg.p1 === curr ? nextSeg.p2 : nextSeg.p1; + poly.push(curr); // << push *before* test + if (curr === start) break; // loop closed + + prevSeg = nextSeg; + } + polylines.push(poly); + } + + widget.traces = polylines; - let opts = { each: oneach, over: false, flatoff: 0, edges: true, openok: true, lines: true }; - await slicer.slice(indices, opts); - // pick up bottom features - opts.over = true; - await slicer.slice(indices, opts); - widget.traces = traces; - widget.trace_single = single; return true; }; @@ -471,7 +547,7 @@ CAM.holes = async function(settings, widget, individual, rec,onProgress) { let slicerOpts = {flatoff: 0.001} let slicer = new kiri.cam_slicer(widget,slicerOpts); let zFlats = Object.keys(slicer.zFlat).map(Number).map(z=>[z,z-0.002]).flat() - + precision = Math.max( 0, precision ) let intervals = (precision == 0) ? [] : slicer.interval( precision, @@ -564,6 +640,11 @@ CAM.holes = async function(settings, widget, individual, rec,onProgress) { onProgress(0.75+(i/circles.length*0.25),"assemble holes") } drills.forEach( h=>{ + if(rec.fromTop){ + // set z top if selected + h.depth += wztop - h.z + h.z = wztop + } delete h.overlapping //for encoding h.diam = toolDiam // for mesh generation h.selected = (!individual && Math.abs(h.area - area) <= area * 0.05 ); //for same size selection diff --git a/src/kiri-mode/cam/tool.js b/src/kiri-mode/cam/tool.js index 4ef46085..a7a8d9fa 100644 --- a/src/kiri-mode/cam/tool.js +++ b/src/kiri-mode/cam/tool.js @@ -137,7 +137,7 @@ class Tool { drill_tip_length = this.drillTipLength(), shaft_offset = this.fluteLength(), flute_diameter = this.fluteDiameter(), - shaft_diameter = this.shaftDiameter(), + shaft_diameter = Math.max(flute_diameter, this.shaftDiameter()), max_diameter = Math.max(flute_diameter, shaft_diameter), shaft_pix_float = max_diameter / resolution, shaft_pix_int = Math.round(shaft_pix_float), diff --git a/src/kiri-run/engine.js b/src/kiri-run/engine.js index 17e9ed07..d0350a17 100644 --- a/src/kiri-run/engine.js +++ b/src/kiri-run/engine.js @@ -8,6 +8,7 @@ // dep: kiri.client // dep: kiri.widget // use: add.three +// use: add.array gapp.register("kiri-run.engine", [], (root, exports) => { const { kiri } = root; @@ -105,10 +106,19 @@ class Engine { } setStock(stock) { - this.settings.stock = stock; + let { settings } = this; + let { process } = settings; + settings.stock = stock; + process.camStockX = stock.x; + process.camStockY = stock.y; + process.camStockZ = stock.z; return this; } + setOrigin(x, y, z) { + this.settings.origin = { x, y, z }; + return this; + } moveTo(x, y, z) { this.widget.move(x, y, z, true); diff --git a/src/kiri/conf.js b/src/kiri/conf.js index ee6f7354..36400e98 100644 --- a/src/kiri/conf.js +++ b/src/kiri/conf.js @@ -520,6 +520,7 @@ const conf = exports({ camDrillDwell: 250, camDrillLift: 2, camDrillMark: false, + camDrillFromStockTop: false, camDrillThru: 5, camDrillPrecision: 1, camDrillingOn: false, diff --git a/src/kiri/slice.js b/src/kiri/slice.js index 612bcde6..c858c082 100644 --- a/src/kiri/slice.js +++ b/src/kiri/slice.js @@ -7,11 +7,8 @@ gapp.register("kiri.slice", [], (root, exports) => { const { base, kiri } = root; -const { key, polygons } = base; -const { util } = base; const POLY = base.polygons; -const NOKEY = key.NONE; let tracker; diff --git a/src/kiri/widget.js b/src/kiri/widget.js index 61eb8ddf..c55df903 100644 --- a/src/kiri/widget.js +++ b/src/kiri/widget.js @@ -355,6 +355,27 @@ class Widget { } } + getVisualState() { + return { + edges: this.outline ? true : false, + wires: this.wire ? true : false, + opacity: this.getMaterial().opacity + }; + } + + setVisualState({ edges, wires, opacity}) { + this.cache.vizstate = this.getVisualState(); + this.setEdges(edges ?? false); + this.setWireframe(wires ?? false); + this.setOpacity(opacity ?? 1); + } + + restoreVisualState() { + if (this.cache.vizstate) { + this.setVisualState(this.cache.vizstate); + } + } + /** * @param {number} value */ @@ -451,6 +472,7 @@ class Widget { let rad = deg * (Math.PI / 180); if (rad !== this.track.indexRad) { this.track.indexRad = rad; + this.setModified(); this._updateMeshPosition(); } } @@ -815,7 +837,8 @@ class Widget { let drv = driver[settings.mode.toUpperCase()]; if (drv) { - drv.slice(settings, widget, catchupdate, catchdone); + let promise = drv.slice(settings, widget, catchupdate, catchdone); + if (promise) promise.catch(error => ondone(error)); } else { console.log('invalid mode: '+settings.mode); ondone('invalid mode: '+settings.mode); diff --git a/src/moto/license.js b/src/moto/license.js index d2cf6bd7..768c94d4 100644 --- a/src/moto/license.js +++ b/src/moto/license.js @@ -5,7 +5,7 @@ let terms = { COPYRIGHT: "Copyright (C) Stewart Allen - All Rights Reserved", LICENSE: "See the license.md file included with the source distribution", - VERSION: (is_self ? self : this).debug_version || "4.1.8" + VERSION: (is_self ? self : this).debug_version || "4.2.0" }; if (typeof(module) === 'object') { diff --git a/web/kiri/lang/en.js b/web/kiri/lang/en.js index eefe5848..0e90a13a 100644 --- a/web/kiri/lang/en.js +++ b/web/kiri/lang/en.js @@ -599,6 +599,8 @@ self.kiri.lang['en-us'] = { cd_dtru_l: ["additional drill depth below part bottom","overrides global z thru","0 to disable"], cd_mark_s: "marking", cd_mark_l: ["only mark holes, do not drill out","the step down setting is used","to determine how deep to mark"], + cd_ftop_s: "from stock top", + cd_ftop_l: ["drill from stock top","instead of part surface"], cd_prcn_s: "precision", cd_prcn_l: ["distance between slices","to search for holes","in workspace units","lower is slower and","uses more memory","0 = only flats"], diff --git a/web/kiri/manifest.json b/web/kiri/manifest.json index eee7cb8c..661bb6d5 100644 --- a/web/kiri/manifest.json +++ b/web/kiri/manifest.json @@ -1,5 +1,5 @@ { - "name": "Kiri:Moto 4.1.8", + "name": "Kiri:Moto 4.2.0", "short_name": "Kiri:Moto", "description": "Slicer for 3D printers, CNC mills, laser cutters and more", "start_url": "/kiri/",