{"id":31853682,"url":"https://github.com/mmiscool/breppluginexample","last_synced_at":"2025-10-12T13:56:19.100Z","repository":{"id":316327258,"uuid":"1062919536","full_name":"mmiscool/BREPpluginExample","owner":"mmiscool","description":"Example plugin for BREP CAD","archived":false,"fork":false,"pushed_at":"2025-09-25T00:08:27.000Z","size":61,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-10-10T03:37:20.161Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mmiscool.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-23T23:20:59.000Z","updated_at":"2025-10-09T06:02:53.000Z","dependencies_parsed_at":"2025-09-24T01:18:40.977Z","dependency_job_id":"cfdd9469-ca5a-4588-a306-455da73c222f","html_url":"https://github.com/mmiscool/BREPpluginExample","commit_stats":null,"previous_names":["mmiscool/examplebrepplugin"],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/mmiscool/BREPpluginExample","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmiscool%2FBREPpluginExample","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmiscool%2FBREPpluginExample/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmiscool%2FBREPpluginExample/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmiscool%2FBREPpluginExample/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mmiscool","download_url":"https://codeload.github.com/mmiscool/BREPpluginExample/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mmiscool%2FBREPpluginExample/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279011458,"owners_count":26084947,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-12T13:56:17.958Z","updated_at":"2025-10-12T13:56:19.092Z","avatar_url":"https://github.com/mmiscool.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Plugin Development Guide\n\nThis document explains how to build third‑party plugins for the BREP CAD application. It covers the plugin entrypoint, the `app` object you receive (including `app.BREP`), how to register new features, the structure of a feature class, the supported parameter schema types, and the small UI hooks you can use.\n\nIf you just want the TL;DR: your repo must contain a `plugin.js` ES module that exports a function. In that function, call `app.registerFeature(YourFeatureClass)` and optionally add UI via `app.addToolbarButton(...)` or `app.addSidePanel(...)`.\n\n\nYou can fork this repo as a starting point for your plugin. After you have forked it simply edit `plugin.js`.\n\nSee also: the BREP application README: https://github.com/mmiscool/BREP/blob/master/README.md\n\n## Overview\n\n- Plugins are ES modules fetched directly from GitHub.\n- The loader expects an entry file named `plugin.js` in the repo (or subdirectory when the URL includes it).\n- The entry is executed with a single argument: `app`, an object that provides:\n  - `BREP`: access to BREP modeling classes/utilities (solids/primitives/CSG helpers/THREE instance).\n  - `viewer`: the live viewer instance (scene, part history, toolbar, etc.).\n  - `registerFeature(FeatureClass)`: register a new modeling feature.\n  - `addToolbarButton(label, title, onClick)`: add a toolbar button.\n  - `addSidePanel(title, content)`: add a side panel section to the sidebar.\n\nWhere these come from in the app: see [src/plugins/pluginManager.js](https://github.com/mmiscool/BREP/blob/master/src/plugins/pluginManager.js) (builds the `app` object) and [src/UI/viewer.js](https://github.com/mmiscool/BREP/blob/master/src/UI/viewer.js) (viewer API).\n\n\n## How Plugins Are Loaded\n\n- Add your GitHub repo URL in the in‑app Plugins panel. Supported forms:\n  - `https://github.com/USER/REPO`\n  - `https://github.com/USER/REPO/tree/BRANCH`\n  - `https://github.com/USER/REPO/tree/BRANCH/sub/dir`\n- The loader looks for `plugin.js` at that path, tries GitHub Raw on `ref` (or `main`/`master`), and falls back to jsDelivr CDN.\n- Relative imports inside `plugin.js` are rewritten to absolute URLs against your repo base, so you can structure your plugin across multiple files.\n- Bare specifiers (e.g., `import x from 'some-npm-package'`) are NOT resolved. Use relative imports within your repo, or explicit URL imports if you need an external dependency.\n\n\n## Entrypoint Contract\n\nPlace a `plugin.js` at the repo path. Export either a default function or an `install` function. Both receive `app`.\n\nExample minimal entrypoint (from this repo):\n\n```js\n// plugin.js\nimport { PrimitiveSphereFeaturePlugin } from './exampleFeature.js';\n\nexport default function install(app) {\n  // Make the app (and BREP) available to the feature class\n  PrimitiveSphereFeaturePlugin.setup(app);\n\n  // Optional: small UI hooks\n  app.addToolbarButton('🧩', 'Hello', () =\u003e {\n    console.log('Hello from plugin');\n    console.log('This is the context passed in to the plugin.', app);\n    alert('Yay');\n  });\n\n  app.addSidePanel('Plugin Panel', () =\u003e {\n    const div = document.createElement('div');\n    div.textContent = 'This was added by a plugin.';\n    return div;\n  });\n\n  // Register your feature so users can add it\n  app.registerFeature(PrimitiveSphereFeaturePlugin);\n}\n```\n\n\n## The `app` Object\n\n`app` is constructed by the application when your plugin loads:\n\n- `app.BREP` — The modeling toolkit and a shared THREE instance. This includes:\n  - `THREE`, `Solid`, `Face`, `Edge`, `Vertex`\n  - primitives: `Cube`, `Sphere`, `Cylinder`, `Cone`, `Torus`, `Pyramid`\n  - operations: `Sweep`, `ExtrudeSolid`, `ChamferSolid`, `FilletSolid`\n  - utilities: `applyBooleanOperation(partHistory, baseSolid, booleanParam, featureID)`, `MeshToBrep`, `MeshRepairer`\n- `app.viewer` — The live viewer. Useful bits:\n  - `viewer.scene` — A Three.js scene containing the model objects.\n  - `viewer.partHistory` — The history and feature pipeline (see below).\n  - `viewer.addToolbarButton(label, title, onClick)` — Add a custom button.\n  - `viewer.addPluginSidePanel(title, content)` — Add a side panel; `content` can be an element, a function that returns an element, or a string.\n- `app.registerFeature(FeatureClass)` — Register a feature so users can add it from the UI/history. The app marks it as plugin‑provided and prefixes names with a plug icon.\n\n\n## Registering a Feature\n\nCall `app.registerFeature(YourFeatureClass)`. The app will:\n- Flag the class as plugin‑provided (`fromPlugin = true`).\n- Prefix `featureShortName` and `featureName` with a plug icon in UI.\n- Make it available to the Feature Registry so it can be created via the History UI or programmatically with `viewer.partHistory.newFeature('Your Name or ShortName')`.\n\n\n## Feature Class Anatomy\n\nEvery feature is a small class with three static fields and two instance fields, plus a `run()` method.\n\n- Static fields:\n  - `featureShortName` — Short code shown in menus (e.g., `E`, `P.CU`).\n  - `featureName` — Human friendly name.\n  - `inputParamsSchema` — Describes inputs; the UI is auto‑generated from this.\n- Instance fields:\n  - `this.inputParams` — Filled with sanitized values before `run()` is called.\n  - `this.persistentData` — Your scratchpad; survives across runs of this feature.\n- Method:\n  - `async run(partHistory)` — Build/update geometry. Return either:\n    - An array of objects to add to the scene, OR\n    - `{ added: [...], removed: [...] }` for fine‑grained control.\n\nTypical flow inside `run()` for geometry‑creating features:\n1) Construct a BREP solid/face using `app.BREP` classes.\n2) Apply any transform (`bakeTRS`) and call `visualize()` to create display geometry and helper edges.\n3) Apply optional boolean with `BREP.applyBooleanOperation(partHistory, base, this.inputParams.boolean, this.inputParams.featureID)`.\n4) Return the result from step 3 (additions/removals). The app removes flagged items and adds new ones.\n\nImportant conventions:\n- Name your output objects with `this.inputParams.featureID` (e.g., pass `name: featureID` to constructors). This enables referencing them in later features.\n- You can persist heavy intermediate data in `this.persistentData` to speed up subsequent runs.\n\n\n## Parameter Schema Cookbook\n\nDefine `static inputParamsSchema = { ... }`. The app uses this to:\n- Provide default values when the feature is created.\n- Render an editable UI.\n- Sanitize/resolve values before calling `run()`.\n\nSupported field types and options:\n\n- `string`\n  - `default_value: string`\n  - `hint?: string`\n\n- `number`\n  - `default_value: number | string` — Users can type math expressions. Numbers are evaluated against the global Expressions manager, so `x * 2` is allowed if `x` is defined.\n  - `min?`, `max?`, `step?`: number or string\n  - `hint?: string`\n\n- `boolean`\n  - `default_value: boolean`\n\n- `options`\n  - `options: string[]` — Fixed list of values\n  - `default_value: string`\n\n- `button`\n  - `label?: string`\n  - `actionFunction?: (ctx) =\u003e void | Promise\u003cvoid\u003e` — Called on click. `ctx` includes `{ featureID, key, viewer, partHistory, feature, params, schemaDef }`.\n\n- `file`\n  - `accept?: string` — e.g., `.png,image/png`\n  - Value is a Data URL string of the selected file after UI selection.\n\n- `transform`\n  - `default_value: { position: [x,y,z], rotationEuler: [rx,ry,rz], scale?: [sx,sy,sz] }`\n  - UI provides interactive 3D gizmos; commit updates into `inputParams`.\n\n- `reference_selection`\n  - Lets users pick scene objects.\n  - `selectionFilter: [\"SOLID\" | \"FACE\" | \"EDGE\" | \"SKETCH\" | \"PLANE\", ...]`\n  - `multiple: boolean` — single value (string) or array of strings.\n  - `default_value: string | string[] | null`\n  - Before `run()`, the app resolves the names into actual objects where possible; `this.inputParams[key]` will be an array of objects for `multiple: true` or a single‑element array for single select.\n\n- `boolean_operation`\n  - Object with shape `{ operation: 'NONE'|'UNION'|'SUBTRACT'|'INTERSECT', targets: (string|object)[] }`.\n  - Optional: `biasDistance`, `offsetCoplanarCap`, `offsetDistance` for advanced subtract behavior.\n  - Pass directly to `BREP.applyBooleanOperation(...)` for robust, normalized handling.\n\n\n## Using `app.BREP`\n\n`app.BREP` is already imported from the application, so you must NOT import your own copy of Three.js or the BREP library. Using the shared instance avoids duplicate constructors and broken `instanceof` checks.\n\nCommon patterns:\n\n```js\nconst { BREP } = app;\n\n// Primitives\nconst sphere = new BREP.Sphere({ r: 5, resolution: 32, name: 'MySphere' });\nsphere.visualize();\n\n// Sweeps/Extrudes\nconst sweep = new BREP.Sweep({ face: someFace, distance: 10, name: 'MyExtrude' });\nsweep.visualize();\n\n// CSG helper\nconst result = await BREP.applyBooleanOperation(partHistory, sphere, { operation: 'UNION', targets: [otherSolid] }, 'Feat123');\n// → { added: [Solid], removed: [Solid, ...] }\n```\n\n\n### Accessing `app.BREP` exactly\n\nThis repo’s example passes the `app` object into the feature class via a static `setup(app)` method, and the feature reads the shared BREP instance from there:\n\n```js\n// exampleFeature.js\nexport class PrimitiveSphereFeaturePlugin {\n  static app = null;\n  static setup(app) { this.app = app; }\n\n  async run(partHistory) {\n    const BREP = this.constructor.app.BREP; // shared instance from the host app\n    const sphere = new BREP.Sphere({ r: 5, resolution: 32, name: 'FeatureID' });\n    sphere.visualize();\n    return await BREP.applyBooleanOperation(partHistory, sphere, this.inputParams.boolean, this.inputParams.featureID);\n  }\n}\n```\n\nAlternative patterns also work, such as capturing once at module scope (`let BREP; export default (app) =\u003e { BREP = app.BREP; }`) or destructuring on demand (`const { BREP } = app`). Always use the provided `app.BREP` rather than importing your own copy.\n\n\n## Complete Example: Toolbar + Side Panel + Feature\n\nThis section shows the actual files in this repo and how the feature gets access to `app.BREP`.\n\n```js\n// plugin.js\nimport { PrimitiveSphereFeaturePlugin } from './exampleFeature.js';\n\nexport default (app) =\u003e {\n  // Provide the app (which contains BREP) to the feature class\n  PrimitiveSphereFeaturePlugin.setup(app);\n\n  // Optional UI examples\n  app.addToolbarButton('🧩', 'Hello', () =\u003e {\n    console.log('Hello from plugin');\n    console.log('This is the context passed in to the plugin.', app);\n    alert('Yay');\n  });\n  app.addSidePanel('Plugin Panel', () =\u003e {\n    const div = document.createElement('div');\n    div.textContent = 'This was added by a plugin.';\n    return div;\n  });\n\n  // Register the feature\n  app.registerFeature(PrimitiveSphereFeaturePlugin);\n};\n```\n\n```js\n// exampleFeature.js\nconst inputParamsSchema = {\n  featureID: { type: 'string', default_value: null, hint: 'Unique identifier for the feature' },\n  radius: { type: 'number', default_value: 5, hint: 'Radius of the sphere' },\n  resolution: { type: 'number', default_value: 32, hint: 'Base segment count (longitude). Latitude segments are derived from this.' },\n  transform: { type: 'transform', default_value: { position: [0, 0, 0], rotationEuler: [0, 0, 0], scale: [1, 1, 1] }, hint: 'Position, rotation, and scale' },\n  boolean: { type: 'boolean_operation', default_value: { targets: [], operation: 'NONE' }, hint: 'Optional boolean operation with selected solids' },\n};\n\nexport class PrimitiveSphereFeaturePlugin {\n  static featureShortName = 'S.p';\n  static featureName = 'Primitive Sphere';\n  static inputParamsSchema = inputParamsSchema;\n  static app = null;\n\n  static setup(app) { this.app = app; }\n\n  constructor() {\n    this.inputParams = {};\n    this.persistentData = {};\n  }\n\n  async run(partHistory) {\n    const { radius, resolution, featureID } = this.inputParams;\n    const BREP = this.constructor.app.BREP; // Access BREP from the app provided during setup\n\n    const sphere = await new BREP.Sphere({ r: radius, resolution, name: featureID });\n    try {\n      if (this.inputParams.transform) {\n        sphere.bakeTRS(this.inputParams.transform);\n      }\n    } catch (_) {}\n    sphere.visualize();\n\n    return await BREP.applyBooleanOperation(partHistory || {}, sphere, this.inputParams.boolean, featureID);\n  }\n}\n```\n\nNote: the loader will prefix plugin‑provided names in the UI with a plug icon. To avoid confusion with built‑in features, pick a unique `featureShortName`.\n\n\n## Programmatic Feature Creation\n\nFrom toolbar buttons or side panels you can add features directly:\n\n```js\nconst ph = app.viewer.partHistory;\n// Either the long name or short name works; match your class statics.\nconst feature = await ph.newFeature('Primitive Sphere'); // or 'S.p'\nfeature.inputParams.radius = 20;\nawait ph.runHistory();\n```\n\nInternally, this uses the Feature Registry ([src/FeatureRegistry.js](https://github.com/mmiscool/BREP/blob/master/src/FeatureRegistry.js)) to find your class and apply default values from `inputParamsSchema`.\n\n\n## Side Panels and Toolbar\n\n- `app.addToolbarButton(label, title, onClick)` — Adds a small labeled button to the main toolbar.\n- `app.addSidePanel(title, content)` — Inserts a collapsible section in the sidebar. `content` may be:\n  - An `HTMLElement`\n  - A function returning an `HTMLElement` (async allowed)\n  - A string (rendered in a `\u003cpre\u003e`) \n\nPlugin side panels appear before the Display Settings panel.\n\n\n## Recommended Repo Layout\n\n```\nyour-plugin-repo/\n  plugin.js            # entrypoint (required)\n  exampleFeature.js    # a feature class\n```\n\n`plugin.js` can import your own files via relative paths:\n\n```js\nimport { PrimitiveSphereFeaturePlugin } from './exampleFeature.js';\nexport default (app) =\u003e {\n  PrimitiveSphereFeaturePlugin.setup(app);\n  app.registerFeature(PrimitiveSphereFeaturePlugin);\n};\n```\n\n\n## Best Practices\n\n- Always call `.visualize()` on solids you generate before returning them.\n- Name your outputs with the feature’s `featureID` for easy referencing.\n- Use `this.persistentData` for caches/intermediate results to speed reruns.\n- Prefer `app.BREP.THREE` over importing your own Three.js.\n- Keep imports relative to your repo, or use absolute URL imports when needed.\n- For boolean operations, prefer the provided helper: `BREP.applyBooleanOperation(...)`.\n\n\n## Troubleshooting\n\n- “Cannot find plugin.js” — Ensure the file exists at the path your URL points to. If you use a subdirectory, include it in the GitHub URL (see above).\n- “Import failed” — Avoid bare specifiers. Use relative paths or absolute URLs.\n- “TypeError or geometry not visible” — Did you call `.visualize()` on your BREP solids/faces? Are you naming outputs correctly?\n- “Selections don’t resolve” — For `reference_selection` fields, the app resolves names to objects at run time. Ensure the referenced objects exist and are named.\n\n\n## References (source)\n\n- App plugin surface and loader: [src/plugins/pluginManager.js](https://github.com/mmiscool/BREP/blob/master/src/plugins/pluginManager.js)\n- Feature registry and history execution: [src/FeatureRegistry.js](https://github.com/mmiscool/BREP/blob/master/src/FeatureRegistry.js), [src/PartHistory.js](https://github.com/mmiscool/BREP/blob/master/src/PartHistory.js)\n- Example features (good templates): [src/features](https://github.com/mmiscool/BREP/tree/master/src/features)\n- Boolean helper: [src/BREP/applyBooleanOperation.js](https://github.com/mmiscool/BREP/blob/master/src/BREP/applyBooleanOperation.js)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmiscool%2Fbreppluginexample","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmmiscool%2Fbreppluginexample","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmmiscool%2Fbreppluginexample/lists"}