Skip to content

Commit 8224059

Browse files
committed
JScadNode, and some work on OpenScadNode
1 parent 8e64b23 commit 8224059

File tree

7 files changed

+154
-27
lines changed

7 files changed

+154
-27
lines changed

docs/status-and-roadmap.rst

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@ Roadmap
1414
* Improve the web viewer with workplanes, rulers, camera angles, animation control, a test runner
1515
* A command to create a static website with docstrings, the viewer, source and build downloads
1616
* A FlexibleNode class to create objects that change shape over time, with keyframes for animation
17-
* A OpenJScadNode class to support OpenJScad.
18-
* Separate the watcher and builder internal processes to recover from errors
17+
* Improve the builder to recover from errors

docs/using-solid-node.rst

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ At this point, you should be able to view your project at the viewer
1010
- either Openscad or the web viewer - and have a source code to edit.
1111

1212
In Solid Node, project is organized in a tree structure, with leaf nodes
13-
and internal nodes. **Leaf nodes** use uderlying modelling libraries, like
14-
**SolidPython** and **CadQuery**, to generate solid models. **Internal Nodes**
15-
combine children nodes in some way, like an **Assembly** or **Fusion**
13+
and internal nodes. **Leaf nodes** use uderlying modelling libraries, namely
14+
**SolidPython**, **CadQuery**, **OpenScad** and **JScad", to generate solid
15+
models. **Internal Nodes** combine children nodes in some way, like an
16+
**Assembly** or **Fusion**
1617

1718
Each node implements the `render()` method. Leaf nodes return an object of the
1819
underlying library. Internal nodes `render()` should return a list of child
@@ -58,6 +59,74 @@ The same model can be obtained using **CadQuery**:
5859
hole = wp.workplane(offset=-50).circle(10).extrude(100)
5960
return cube.cut(hole)
6061
62+
**TIP**: if you want to use CQ-editor, you can add `show_object` without
63+
conflicting with Solid Node:
64+
65+
.. code-block:: python
66+
67+
if __name__ == '__cq_main__':
68+
show_object(DemoProject().render())
69+
70+
OpenScadNode
71+
------------
72+
73+
The same model can also be obtained using an **OpenScadNode**, which is a small
74+
python wrapper around an OpenScad module.
75+
76+
.. code-block:: python
77+
78+
from solid_node.node import OpenScadNode
79+
80+
class DemoProject(OpenScadNode):
81+
82+
scad_source = 'demo.scad'
83+
84+
Create a file `root/demo.scad` with a module to create the model:
85+
86+
.. code-block:: openscad
87+
88+
module demo() {
89+
difference() {
90+
translate([-25, -25, 0]) {
91+
cube([50, 50, 50]);
92+
}
93+
cylinder(r=10, h=100);
94+
}
95+
}
96+
97+
98+
JScadNode
99+
---------
100+
101+
Finally, the model can also be obtained using an **JScadNode**, which similarly
102+
to OpenScadNode, it's a python wrapper around an JScad function.
103+
104+
.. code-block:: python
105+
106+
from solid_node.node import OpenScadNode
107+
108+
class DemoProject(OpenScadNode):
109+
110+
jscad_source = 'demo.js'
111+
112+
Create a file `root/demo.js` with a module to create the model:
113+
114+
.. code-block:: javascript
115+
116+
const { square, circle } = require('@jscad/modeling').primitives
117+
const { subtract } = require('@jscad/modeling').booleans
118+
const { extrudeLinear } = require('@jscad/modeling').extrusions
119+
120+
function main() {
121+
let outerSquare = square({size: 50 });
122+
let innerCircle = circle({radius: 10 });
123+
124+
let shape = subtract(outerSquare, innerCircle);
125+
return extrudeLinear({ height: 50 }, shape);
126+
}
127+
128+
module.exports = { main }
129+
61130
Internal Nodes
62131
==============
63132

@@ -85,6 +154,9 @@ Create a new file `root/clock_base.py` and create a `CadQueryNode`:
85154
wp = cq.Workplane("XY")
86155
return wp.circle(100).extrude(2)
87156
157+
if __name__ == '__cq_main__':
158+
show_object(ClockBase().render())
159+
88160
Now, a file `root/pointer.py` with a `Solid2Node`:
89161

90162
.. code-block:: python

docs/why-solid-node.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Why Solid Node
22
==============
33

4-
When designing a mechanical project with Free Software, there are some technologies available that allow parametric design, using source code to create solid structures. There's OpenScad, which uses it's own scripting language, and on top of it there is Solid Python, which allows modelling for OpenScad using Python language. Python also has CadQuery, which is more powerful and flexible for complex designs, but has a steeper learning curve. When picking which technology to use, one should consider the libraries available, and a complex project might need to assemble pieces that come from different libraries. Openscad has a native animation system that allows developing projects with moving parts, but it soon gets very slow as project grows.
4+
When designing a mechanical project with Free Software, there are some technologies available that allow parametric design, using source code to create solid structures. There's OpenScad, which uses it's own scripting language, and on top of it there is Solid Python, which allows modelling for OpenScad using Python language. Python also has CadQuery, which is more powerful and flexible for complex designs, but has a steeper learning curve. There's also JScad, which uses Javascript. When picking which technology to use, one should consider the libraries available, and a complex project might need to assemble pieces that come from different libraries. Openscad has a native animation system that allows developing projects with moving parts, but it soon gets very slow as project grows.
55

6-
Solid Node come as a framework to join all these underlying technologies together and solve performance bottlenecks. It's inspired by a web development culture, which uses frameworks like Django, React and Angular, that monitors filesystem for changes and shows results automatically. Solid Node proposes an architecture that allows building of pieces as they change, being able to handle a lot of moving parts.
6+
Solid Node is a framework to join all these underlying technologies together and solve performance bottlenecks. It's inspired by a web development culture, which uses frameworks like Django, React and Angular, that monitors filesystem for changes and shows results automatically. Solid Node proposes an architecture that allows building of pieces as they change, being able to handle a lot of moving parts.
77

88
Solid Node also provides testing capabilities. Prototyping can take a lot of time and generate a lot of garbage, and this can be substantially reduced by being able to logically test connections between components before producing anything.
99

solid_node/node/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
from .adapters.cadquery import CadQueryNode
1111
from .adapters.solid2 import Solid2Node
1212
from .adapters.openscad import OpenScadNode
13+
from .adapters.jscad import JScadNode
1314
from .decorators import property_as_number

solid_node/node/adapters/jscad.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import os
2+
import sys
3+
import time
4+
from solid2 import import_stl
5+
from subprocess import Popen
6+
from solid_node.node.leaf import LeafNode
7+
8+
9+
class JScadNode(LeafNode):
10+
"""
11+
A JScad node. You just need to declare the property "jscad_source" with
12+
the path of your JScad source code. It must be placed in the same directory
13+
of the python file containing this node.
14+
15+
You need to have jscad cli tool installed in $PATH, and node dependencies
16+
installed in the directory you are running solid from.
17+
"""
18+
19+
jscad_source = None
20+
21+
def __init__(self, name=None):
22+
if not self.jscad_source:
23+
raise Exception('OpenJScadNode subclass must declare "jscad_source" '
24+
'property with path with a valid OpenJScad js file')
25+
module = sys.modules[self.__class__.__module__]
26+
basedir = os.path.dirname(module.__file__)
27+
source_path = os.path.join(basedir, self.jscad_source)
28+
self.jscad_source = os.path.realpath(source_path)
29+
30+
super().__init__(name=name)
31+
32+
def get_source_file(self):
33+
return self.jscad_source
34+
35+
def render(self):
36+
return self
37+
38+
def as_scad(self, _):
39+
40+
cmd = [
41+
'jscad',
42+
self.jscad_source,
43+
'-o', self.stl_file,
44+
]
45+
print('\n' + ' '.join(cmd))
46+
proc = Popen(cmd)
47+
proc.communicate()
48+
try:
49+
os.utime(self.stl_file, (time.time(), self.mtime))
50+
except FileNotFoundError:
51+
pass
52+
return import_stl(self.local_stl)

solid_node/node/adapters/openscad.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import os
2+
import sys
23
import tempfile
3-
import inspect
44
from solid2 import scad_render, import_scad
55
from solid2.core.parse_scad import get_scad_file_as_dict
66
from solid2.core.utils import resolve_scad_filename
@@ -9,31 +9,37 @@
99

1010
class OpenScadNode(LeafNode):
1111
"""
12-
Python wrapper around a pure scad module
12+
A pure OpenScad node. You just need to declare the property "scad_source" with
13+
the path of your OpenScad source code. It must be placed in the same directory
14+
of the python file containing this node.
15+
16+
The scad file must contain a module with the same name of the file, or you
17+
may specify the property "module_name" with the module name.
1318
"""
1419

1520
namespace = 'solid2.core.object_factory'
21+
scad_source = None
22+
module_name = None
1623

17-
def __init__(self, source, *args, name=None, **kwargs):
18-
"""Receives a source code and arguments, imports an OpenScad module from
19-
the source and renders it using *args and **kwargs
20-
The OpenScad code must implement a module with same name as file
24+
def __init__(self, *args, name=None, **kwargs):
25+
"""Receives args, an optional name keyword argument and a list of keyword
26+
arguments. The list of arguments and keyword arguments will be passed as
27+
parameter to the module.
2128
2229
Args:
23-
source (str): The .scad source code, in the same folder as the python file
2430
*args: will be passed as arguments to the OpenScad module
2531
name keyword argument: the name of this node, defaul to name of the class
2632
**kwargs: will be passed as keyword arguments to the openscad module
2733
"""
28-
frame = inspect.currentframe().f_back
29-
caller = frame.f_globals.get('__file__')
30-
basedir = os.path.dirname(caller)
31-
source_path = os.path.join(basedir, source)
34+
module = sys.modules[self.__class__.__module__]
35+
basedir = os.path.dirname(module.__file__)
36+
source_path = os.path.join(basedir, self.scad_source)
3237
self.openscad_source = os.path.realpath(source_path)
3338
self.openscad_code = open(self.openscad_source).read()
3439
self.args = args
3540
self.kwargs = kwargs
36-
self.module_name = source.split('/')[-1].split('.')[0]
41+
if self.module_name is None:
42+
self.module_name = self.openscad_source.split('/')[-1].split('.')[0]
3743

3844
super().__init__(*args, name=name, **kwargs)
3945

solid_node/node/leaf.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ class LeafNode(AbstractBaseNode):
1111

1212
_type = 'LeafNode'
1313

14+
# Each LeafNode subclass can declare a namespace, and objects
15+
# returned by render() must belong to that namespace
16+
namespace = None
17+
1418
@property
1519
def time(self):
1620
"""Raise an exception, as leaf nodes cannot rely on time.
@@ -30,19 +34,12 @@ def as_scad(self, rendered):
3034
raise NotImplementedError(f"LeafNode subclass {self.__class__} must "
3135
"be able to output scad")
3236

33-
@property
34-
def namespace(self):
35-
"""Each LeafNode subclass must declare a namespace, and objects
36-
returned by render() must belong to that namespace"""
37-
raise NotImplementedError(f"LeafNode needs to belong to a namespace-"
38-
"constrained class.")
39-
4037
def validate(self, rendered):
4138
"""Check if rendered result is an object of proper namespace"""
4239
if type(rendered) in (list, tuple):
4340
raise Exception(f"{self.__class__} is a LeafNode and should return "
4441
f"a {self.namespace} object, not a list")
4542

46-
if not type(rendered).__module__.startswith(self.namespace):
43+
if self.namespace and not type(rendered).__module__.startswith(self.namespace):
4744
raise Exception(f"{self.__class__} is a LeafNode and should render "
4845
f"as {self.namespace} child, not {type(rendered)}")

0 commit comments

Comments
 (0)