Skip to content

Commit

Permalink
Sandboxing of python scripts used to generate parts (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
openvmp authored Jan 3, 2024
1 parent 834c60c commit be154b7
Show file tree
Hide file tree
Showing 58 changed files with 1,180 additions and 192 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@
"python.testing.pytestEnabled": true,
"python.terminal.executeInFileDir": true,
"python.terminal.launchArgs": [
]
],
"OcpCadViewer.advanced.autostart": false
}
78 changes: 49 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
PartCAD is the first package manager for CAD models,
and a Python package to consume these packages in CAD scripts ([`cadquery`][CadQuery] and [`build123d`][build123d]).
It brings the same power to CAD scripting
as `pip` to Python, `npm` to JavaScript, `maven` to Java etc.
as [pip](https://pypi.org/) to Python, [npm](https://www.npmjs.com/) to JavaScript, [maven](https://maven.apache.org/) to Java etc.

[Join our Discord channel!](https://discord.gg/AXbP47zYw5)

Expand All @@ -19,8 +19,11 @@ all of the consumers.
- [Installation](#installation)
- [Usage](#usage)
- [Create new package](#create-new-package)
- [List dependencies](#list-dependencies)
- [List available parts and assemblies](#list-available-parts-and-assemblies)
- [Add a dependency](#add-a-dependency)
- [Create a basic assembly](#create-a-basic-assembly)
- [Troubleshooting](#troubleshooting)
- [Render your project](#render-your-project)
- [Modelling](#modelling)
- [Parts](#parts)
Expand All @@ -31,6 +34,8 @@ all of the consumers.
- [Visualization](#visualization)
- [Other modelling formats](#other-modelling-formats)
- [Purchasing / Bill of materials](#purchasing--bill-of-materials)
- [Security](#security)
- [Public repository](#public-repository)
- [Tools for mechanical engineering](#tools-for-mechanical-engineering)
- [History](#history)

Expand All @@ -54,20 +59,21 @@ python3 -m pip install -e .

### Create new package

Initialize new PartCAD package in the current
directory (create the default `partcad.yaml`):
`pc init` to initialize new PartCAD package in the current
directory (create the default `partcad.yaml`).

```shell
pc init
```
### List dependencies

### Add a dependency
`pc list` to list all (recursively) imported dependencies.

Use the following command to import parts from another package:
### List available parts and assemblies

```shell
pc add <alias> <url-or-local-path>
```
`pc list-parts -r` and `pc list-assemblies -r` to list all available parts and assemblies.

### Add a dependency

`pc add <alias> <url-or-local-path>`
to import parts from another package.

### Create a basic assembly

Expand All @@ -77,7 +83,7 @@ assembly.
```yaml
# partcad.yaml
import:
partcad-index:
partcad-index: # Standard public PartCAD repository (needs to be explicitly referenced to be used)
type: git
url: https://github.com/openvmp/partcad-index.git
assemblies:
Expand All @@ -92,17 +98,16 @@ assy = pc.Assembly() # create an empty assembly
pc.finalize(assy) # this is the object produced by this PartCAD script
```

### Render your project
### Troubleshooting

Use the following shell command to render PartCAD parts and assemblies
in the current package (the current directory):
At the moment, the best way to troubleshoot PartCAD is to use VS Code with `OCP CAD Viewer`.
Simply run an assembly script which contains a call to `pc.finalize(...)` (from within the IDE) to have the model displayed.
Alternatively, any part or assembly can be displayed in `OCP CAD Viewer` by using `pc show <part> [<package>]` or `pc show -a <assembly> [<package>]`.

```shell
pc render
```
### Render your project

If you are using VS Code then you can simply run your PartCAD script
while having `OCP CAD Viewer` view open.
Use `pc render` to render PartCAD parts and assemblies
in the current package (the current directory).

## Modelling

Expand All @@ -113,14 +118,11 @@ while having parts and assemblies often maintained by third parties.

PartCAD allows to define parts using any of the following methods:

| Method | Example | Result |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------ | ---------------------------------------------- |
| [STEP] file | parts:<br/>&nbsp;&nbsp;bolt:<br/>&nbsp;&nbsp;&nbsp;&nbsp;type:&nbsp;step | ![](examples/part_step/bolt.png) |
| [CadQuery]<br/>Gateway<br/>Interface<br/>(including [build123d]) | parts:<br/>&nbsp;&nbsp;cube: | ![](examples/part_cadquery_primitive/cube.png) |

<!--
| Extension for<br/>parametrized<br/>CadQuery scripts | TODO | TODO |
-->
| Method | Example | Result |
| ----------- | ----------------------------------------------------------------------------- | ----------------------------------------------- |
| [STEP] | parts:<br/>&nbsp;&nbsp;bolt:<br/>&nbsp;&nbsp;&nbsp;&nbsp;type:&nbsp;step | ![](examples/part_step/bolt.png) |
| [CadQuery] | parts:<br/>&nbsp;&nbsp;cube:<br/>&nbsp;&nbsp;&nbsp;&nbsp;type:&nbsp;cadquery | ![](examples/part_cadquery_primitive/cube.png) |
| [build123d] | parts:<br/>&nbsp;&nbsp;cube:<br/>&nbsp;&nbsp;&nbsp;&nbsp;type:&nbsp;build123d | ![](examples/part_build123d_primitive/cube.png) |

Other methods to define parts are coming in soon (e.g. OpenSCAD).

Expand Down Expand Up @@ -200,6 +202,7 @@ import:
relPath: <(git|tar only) relative-path-within-the-repository>
web: <(optional) package or maintainer's url>
poc: <(optional) maintainer's email>
pythonVersion: <(optional) python version for sandboxing if applicable>
```
Expand Down Expand Up @@ -228,6 +231,23 @@ formats:
- [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) (not yet / in progress)
- [Markdown](https://en.wikipedia.org/wiki/Markdown) (not yet / in progress)
## Security
PartCAD is capable of rendering scripted parts
([CadQuery] and [build123d] use Python) in sandboxed environments.
While at the moment it is only useful from dependency management perspective,
in the future PartCAD aims to achieve security isolation of the sandboxed
environments. That will fundamentally change the security implications of using
scripted models shared online.
## Public repository
PartCAD allows anyone to create their own private repositories of parts.
However it also comes with a
[public repository](https://github.com/openvmp/partcad-index)
that is used by default for all new packages.
## Tools for mechanical engineering
Here is an overview of the open source tools to maintain
Expand Down Expand Up @@ -298,7 +318,7 @@ The motivation behind this framework is to build a packaging and dependency
tracking layer on top of [CadQuery] and traditional CAD tools to enable
version control and other features required for effective collaboration.

This framework currently uses [CadQuery], and thus, [OpenCASCADE] under the hood.
This framework currently uses [CadQuery] and, thus, [OpenCASCADE] under the hood.
However this may change in the future, if the python C bindings for [OpenCASCADE]
remain a blocker for unlocking multithreaded performance.

Expand Down
Binary file modified examples/assembly_logo/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/assembly_primitive/assembly.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/part_build123d_primitive/cube.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/part_build123d_primitive/partcad.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
desc: PartCAD example project which demonstrates parts created using build123d
parts:
cube:
type: build123d
desc: Sample part created by build123d
render:
png:
Expand Down
1 change: 1 addition & 0 deletions examples/part_build123d_primitive/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build123d==0.2.0
Binary file modified examples/part_cadquery_logo/bone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/part_cadquery_logo/head_half.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/part_cadquery_logo/partcad.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
desc: PartCAD example project which implements its logo
parts:
head_half:
type: cadquery
desc: Bracket used as one side of the head on PartCAD logo
bone:
type: cadquery
desc: Plate used as one of the bones on PartCAD logo

render:
Expand Down
1 change: 1 addition & 0 deletions examples/part_cadquery_logo/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cadquery==2.3.1
Binary file modified examples/part_cadquery_primitive/cube.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 1 addition & 6 deletions examples/part_cadquery_primitive/cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@
# Licensed under Apache License, Version 2.0.
#

import partcad as pc
import cadquery as cq

if __name__ != "__cqgi__":
from cq_server.ui import ui, show_object

shape = cq.Workplane("front").box(10.0, 10.0, 10.0)

# This example demonstrates that partcad is compatible with CQGI.

# show_object(shape)
part = pc.Part(shape=shape)
pc.finalize(part, show_object)
show_object(shape)
Binary file modified examples/part_cadquery_primitive/cylinder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions examples/part_cadquery_primitive/partcad.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
desc: PartCAD example project which demonstrates parts created using CadQuery
parts:
cube:
type: cadquery
desc: This is a cube from examples
cylinder:
type: cadquery
path: cylinder.py
desc: This is a cylinder from examples
render:
Expand Down
1 change: 1 addition & 0 deletions examples/part_cadquery_primitive/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cadquery==2.3.1
Binary file modified examples/part_step/bolt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pc = "partcad:main_cli"

[tool.setuptools.package-data]
"partcad.template" = ["*.yaml"]
"partcad.wrappers" = ["*.py"]

[tool.setuptools.dynamic]
version = {attr = "partcad.__version__"}
Expand All @@ -28,7 +29,7 @@ dependencies = {file = ["requirements.txt"]}
pythonpath = "src"

[tool.bumpversion]
current_version = "0.1.4"
current_version = "0.2.0"
commit = "true"
commit_args = "--no-verify"
tag = "true"
Expand Down
5 changes: 3 additions & 2 deletions src/partcad/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from cadquery import Location
from build123d import Location

from .context import (
Context,
Expand All @@ -17,6 +17,7 @@
from .project_factory_git import ProjectFactoryGit
from .project_factory_tar import ProjectFactoryTar
from .cli import main as main_cli
from .user_config import user_config


__all__ = [
Expand All @@ -39,4 +40,4 @@
"main_cli",
]

__version__: str = "0.1.4"
__version__: str = "0.2.0"
34 changes: 24 additions & 10 deletions src/partcad/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@
#
# Licensed under Apache License, Version 2.0.

import copy
import random
import string

import cadquery as cq
import build123d as b3d

from . import shape

Expand All @@ -25,31 +26,44 @@ def __init__(self, name=None, config={}):
)
else:
self.name = name
self.shape = cq.Assembly(name=self.name)
self.shape = None

# self.children contains all child parts and assemblies
self.children = {}
self.children = []

# TODO(clairbee): add reference counter to assemblies
self.count = 0

def add(
self,
child, # pc.Part or pc.Assembly
child: shape.Shape, # pc.Part or pc.Assembly
name=None,
loc=cq.Location((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 0.0),
loc=b3d.Location((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 0.0),
):
self.shape = self.shape.add(child.shape, name=name, loc=loc)
child_item = copy.copy(child.get_build123d()).locate(loc) # Shallow copy
# child_item.label = name # TODO(clairbee): fix this
self.children.append(child_item)

# Keep part reference counter for bill-of-materials purposes
child.ref_inc()

# Destroy the previous object if any
self.shape = None

def ref_inc(self):
for child in self.children:
child.ref_inc()

def getCompound(self):
if self.compound is None:
self.compound = self.shape.toCompound()
return self.compound
def get_shape(self):
if self.shape is None:
self.shape = b3d.Compound(label=self.name, children=self.children)
return self.shape

def get_build123d(self):
return copy.copy(self.get_shape())

def get_wrapped(self):
return self.get_shape()

def _render_txt_real(self, file):
for child in self.children:
Expand Down
9 changes: 7 additions & 2 deletions src/partcad/assembly_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# Licensed under Apache License, Version 2.0.
#

import os

from . import assembly


Expand All @@ -19,8 +21,11 @@ def __init__(self, ctx, project, assembly_config, extension=""):
self.path = self.name + extension
if "path" in assembly_config:
self.path = assembly_config["path"]
if not self.path.startswith("/") and project.path != "":
self.path = project.path + "/" + self.path
if not os.path.isdir(project.path):
raise Exception("ERROR: The project path must be a directory")
self.path = os.path.join(project.path, self.path)
if not os.path.exists(self.path):
raise Exception("ERROR: The part path must exist")

# Pass the autodetected path to the 'Assembly' class
assembly_config["path"] = self.path
Expand Down
Loading

0 comments on commit be154b7

Please sign in to comment.