Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 75 additions & 1 deletion solid/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"""
from .solidpython import OpenSCADObject
from .solidpython import IncludedOpenSCADObject
import inspect
import uuid
from collections import OrderedDict

class polygon(OpenSCADObject):
'''
Expand Down Expand Up @@ -193,10 +196,81 @@ def __init__(self):


class part(OpenSCADObject):
def __init__(self):
"""
Extended part object.
Intended to be called from within a function dedicated to assembling the part.
When the object assembly is completed, part is called on it. This creates a solid python "part"
- in effect holes created in the assembly can be filled by CSG operations with other objects.
The part stores extra information about parameters used to create the part. This allows
the object to be queried for the creation of, eg, mating parts. For example, specifying an internal
screw thread might permit the on-the-fly calculation of a matching external thread.

[Once a part allows data storage and introspection, it will facilitate other uses - such as nominating
part connectors, re-orienting parts to mate them together at specfied connectors, splitting out
separate parts from an object and arranging them on a print bed for printing, in place rotation around
a nominated centre or rotation etc]

Brendan Barnacle Duck, March 17
"""

def __init__(self, part_id=None, dims_dict=None, function_name=None):
"""
param part_id: unique identifier for this part
type part_id: string
param dims_dict: dictionary of measurements that define the part
type dims_dict: dictionary of float
param function_name: name of function used to assemble this part
type function_name: string

Note on dimension guessing code:
Code to determine the arg values, function name is apparently implementation (CPython) dependent, so
pass express values if that fails.
The code also depnds on the function doing the assembly not accepting * or ** args
The dimension values are taken from the function's arguments and their values as at the time of the call
to part. So, better to not change their values before the call to part.

"""

OpenSCADObject.__init__(self, 'part', {})
self.set_part_root(True)

if function_name is None:
function_name = (inspect.stack()[1][3])

if part_id is None:
part_id = str(uuid.uuid4()) # random uuid

# if dimesions are not provided explicitly, try to deduce them from the calling function
if dims_dict is None:
# then try to grab them from what was provided to the calling function
# - ie the function that assembled this part.
args, varargs, keywords, _locals = inspect.getargvalues(inspect.stack()[1][0])
# filter out non-parameter locals
dims_dict = OrderedDict() # preserve the order of mandatory/optional args for __repr__
for k in args:
dims_dict[k]=_locals[k]

self.part_id = part_id
self.part_dims = dims_dict
self.function_name = function_name

@property
def part_signature(self):
return (self.function_name, self.part_dims)

def __repr__(self):
args = []
for k,v in self.part_dims.items():
try:
# it's a number
spam = float(v)
args.append((k,v))
except (ValueError, TypeError):
# its a string
args.append((k,"""'%s'"""%v))

return "%s(%s)"%(self.function_name, ", ".join(["{d[0]}={d[1]}".format(d=a) for a in args]))


class translate(OpenSCADObject):
'''
Expand Down
19 changes: 18 additions & 1 deletion solid/solidpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
# License: LGPL 2.1 or later
#


import os, sys, re
import inspect
import subprocess
Expand Down Expand Up @@ -571,6 +570,24 @@ def _repr_png_(self):

return png_data

def get_part(self, part_id):
""" find child with id = part_id"""
if self.is_part_root:
if self.part_id == part_id:
return self
else:
for c in self.children:
p = c.get_part(part_id)
if p is not None:
return p
return None

def get_part_dim(self, part_id, dim_name):
"""Find the value of var_name for part with id = part_id
"""
p = self.get_part(part_id)
return p.part_dims[dim_name]


class IncludedOpenSCADObject(OpenSCADObject):
# Identical to OpenSCADObject, but each subclass of IncludedOpenSCADObject
Expand Down
63 changes: 63 additions & 0 deletions solid/test/test_solidpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import unittest
import tempfile
from solid.test.ExpandedTestCase import DiffOutput

import os,sys,inspect

from solid import *

scad_test_case_templates = [
Expand Down Expand Up @@ -267,6 +270,66 @@ def test_scad_render_to_file(self):
# be done from a separate file, or include everything in this one



def test_extended_part(self):
# create part, does not interfere with rendering
part1 = make_a_part("part1")
actual = scad_render(part1)
expected = """\n\ncube(size = [20, 40, 10]);"""
self.assertEqual(actual, expected)

# __repr__ works
actual = part1.__repr__()
expected = """make_a_part(partid='part1', width=20, length=40, depth=10)"""
self.assertEqual(expected, actual)

# get part signature
actual = part1.part_signature
expected = ('make_a_part', OrderedDict([('partid', 'part1'), ('width', 20), ('length', 40), ('depth', 10)]))
self.assertEqual(actual, expected)

# get part correctly traverses object tree to find part1
test_sphere = sphere(1)
obj = union()(part1, test_sphere)
obj = translate([10,10,10])(obj)
obj = rotate([90,0,0])(obj)

part2 = obj.get_part("part1")
self.assertEqual(part1, part2)

# correctly get part dimension
width = obj.get_part_dim("part1", "width")
self.assertEqual(width, 20)

#creation of a part without arguments
# will create default dimensions etc, which can't be relied on
test_sphere = part()(test_sphere) # no failure on creation
actual = scad_render(test_sphere)
expected = "\n\nsphere(r = 1);"
self.assertEqual(actual, expected)

actual = test_sphere.__repr__()
expected = """test_extended_part(self='test_extended_part (__main__.TestSolidPython)')"""
self.assertEqual(actual, expected)

# this can't be tested from __main__ in this test rig, but manual testing doesn't fail
# when parts are declared in __main__ rather than an explicit funciton as the class anticipates



def make_a_part(partid, width=20, length=40, depth=10):
obj = cube([width, length, depth])

# additional locals that need to be not recorded in the part
half_width = width/2
half_length = length/2
half_depth = depth/2
obj = part(part_id="part1")(obj)

return obj



def single_test(test_dict):
name, args, kwargs, expected = test_dict['name'], test_dict['args'], test_dict['kwargs'], test_dict['expected']

Expand Down