Skip to content

Commit

Permalink
Feature/lazy loading (#39)
Browse files Browse the repository at this point in the history
* Lazy loading for parts. It also enables aliases.

* Added a test case for part aliases
  • Loading branch information
openvmp authored Jan 6, 2024
1 parent c3d736a commit 810ef7d
Show file tree
Hide file tree
Showing 10 changed files with 64 additions and 44 deletions.
1 change: 1 addition & 0 deletions examples/part_cadquery_primitive/partcad.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ parts:
cube:
type: cadquery
desc: This is a cube from examples
aliases: ["box"]
cylinder:
type: cadquery
path: cylinder.py
Expand Down
3 changes: 0 additions & 3 deletions src/partcad/assembly.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ def get_shape(self):
self.shape = shape.wrapped
return copy.copy(self.shape)

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

def _render_txt_real(self, file):
for child in self.children:
child._render_txt_real(file)
Expand Down
11 changes: 9 additions & 2 deletions src/partcad/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@


class Part(shape.Shape):
def __init__(self, name=None, config={}, shape=None):
def __init__(self, name=None, path=None, config={}, shape=None):
if name is None:
name = "part" + "".join(
random.choices(string.ascii_uppercase + string.digits, k=8)
)
if path is None:
path = name
super().__init__(name)

self.config = config
self.path = config["path"]
self.path = path
self.shape = shape

self.desc = None
Expand All @@ -49,6 +51,11 @@ def __init__(self, name=None, config={}, shape=None):
def set_shape(self, shape):
self.shape = shape

def get_shape(self):
if self.shape is None:
self.instantiate(self)
return self.shape

def ref_inc(self):
self.count += 1

Expand Down
10 changes: 8 additions & 2 deletions src/partcad/part_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class PartFactory:
def __init__(self, ctx, project, part_config, extension=""):
self.ctx = ctx
self.project = project
self.part_config = part_config
self.name = part_config["name"]

self.path = self.name + extension
Expand All @@ -34,7 +35,12 @@ def __init__(self, ctx, project, part_config, extension=""):
part_config["path"] = self.path

def _create(self, part_config):
self.part = p.Part(self.name, part_config)
self.part = p.Part(self.name, self.path, part_config)

def _save(self):
self.project.parts[self.name] = self.part
if "aliases" in self.part_config and not self.part_config["aliases"] is None:
for alias in self.part_config["aliases"]:
# TODO(clairbee): test if this a copy or a reference
self.project.parts[alias] = self.part

self.part.instantiate = lambda part_self: self.instantiate(part_self)
6 changes: 3 additions & 3 deletions src/partcad/part_factory_build123d.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, ctx, project, part_config):
# Complement the config object here if necessary
self._create(part_config)

def instantiate(self, part):
wrapper_path = wrapper.get("build123d.py")

request = {"build_parameters": {}}
Expand All @@ -45,8 +46,7 @@ def __init__(self, ctx, project, part_config):

if result["success"]:
shape = result["shape"]
self.part.shape = shape
part.set_shape(shape)
else:
logging.error(result["exception"])

self._save()
raise Exception(result["exception"])
6 changes: 3 additions & 3 deletions src/partcad/part_factory_cadquery.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(self, ctx, project, part_config):
# Complement the config object here if necessary
self._create(part_config)

def instantiate(self, part):
wrapper_path = wrapper.get("cadquery.py")

request = {"build_parameters": {}}
Expand All @@ -45,8 +46,7 @@ def __init__(self, ctx, project, part_config):

if result["success"]:
shape = result["shape"]
self.part.shape = shape
part.set_shape(shape)
else:
logging.error(result["exception"])

self._save()
raise Exception(result["exception"])
7 changes: 3 additions & 4 deletions src/partcad/part_factory_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ def __init__(self, ctx, project, part_config):
# Complement the config object here if necessary
self._create(part_config)

shape = cq.importers.importStep(self.path).val().wrapped
self.part.set_shape(shape)

self._save()
def instantiate(self, part):
shape = cq.importers.importStep(part.path).val().wrapped
part.set_shape(shape)
35 changes: 15 additions & 20 deletions src/partcad/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,16 @@ def __init__(self, ctx, path):
else:
self.desc = ""

self.init_parts()
self.init_assemblies()

def get_part_config(self, part_name):
if not part_name in self.part_configs:
return None
return self.part_configs[part_name]

def get_part(self, part_name):
if not part_name in self.parts:
def init_parts(self):
for part_name in self.part_configs:
part_config = self.get_part_config(part_name)

# Handle the case of the part being declared in the config
Expand Down Expand Up @@ -91,22 +94,19 @@ def get_part(self, part_name):
)
return None

# Since factories do not return status codes, we need to verify
# whether they have produced the expected product or not
# TODO(clairbee): reconsider returning status from the factories
if not part_name in self.parts:
logging.error("Failed to instantiate the part: %s" % part_config)
return None

def get_part(self, part_name):
if not part_name in self.parts:
logging.error("Part not found: %s" % part_name)
return None
return self.parts[part_name]

def get_assembly_config(self, assembly_name):
if not assembly_name in self.assembly_configs:
return None
return self.assembly_configs[assembly_name]

def get_assembly(self, assembly_name):
if not assembly_name in self.assemblies:
def init_assemblies(self):
for assembly_name in self.assembly_configs:
assembly_config = self.get_assembly_config(assembly_name)

# Handle the case of the part being declared in the config
Expand All @@ -133,15 +133,10 @@ def get_assembly(self, assembly_name):
)
return None

# Since factories do not return status codes, we need to verify
# whether they have produced the expected product or not
# TODO(clairbee): reconsider returning status from the factories
if not assembly_name in self.assemblies:
logging.error(
"Failed to instantiate the assembly: %s" % assembly_config
)
return None

def get_assembly(self, assembly_name):
if not assembly_name in self.assemblies:
logging.error("Assembly not found: %s" % assembly_name)
return None
return self.assemblies[assembly_name]

def render(self, parts=None, assemblies=None):
Expand Down
2 changes: 1 addition & 1 deletion src/partcad/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __init__(self, name):
self.svg_url = None

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

def get_cadquery(self) -> cq.Shape:
cq_solid = cq.Solid.makeBox(1, 1, 1)
Expand Down
27 changes: 21 additions & 6 deletions tests/unit/test_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@ def test_part_get_1():
repo1 = ctx.get_project("this")
bolt = repo1.get_part("bolt")
assert bolt is not None
assert bolt.get_wrapped() is not None


def test_part_get_2():
"""Load part from the context by the project and part names"""
ctx = pc.Context("examples/part_step")
bolt = ctx.get_part("bolt", "this")
assert bolt is not None
assert bolt.get_wrapped() is not None


def test_part_get_3():
Expand All @@ -45,38 +47,49 @@ def test_part_get_3():
_ = pc.ProjectFactoryLocal(ctx, None, test_config_local)
cylinder = ctx.get_part("cylinder", "primitive_local")
assert cylinder is not None
assert cylinder.get_wrapped() is not None


# Note: The below test fails if there are braking changes in the way parts are
# declared. Keep it this way so that the braking changes are conciously
# force pushed.
def test_part_get_4():
"""Instantiate a project by a git import config and load a part"""
ctx = pc.Context() # Emplty config
ctx = pc.Context() # Empty config
factory = pc.ProjectFactoryGit(ctx, None, test_config_git)
assert factory.project.path.endswith(test_config_git["relPath"])
cube = factory.project.get_part("cube")
assert cube is not None
assert cube.get_wrapped() is not None


def test_part_lazy_loading_1():
"""Future test for lazy loading of geometry data"""
def test_part_lazy_loading():
"""Test for lazy loading of geometry data"""
ctx = pc.Context() # Empty config
_ = pc.ProjectFactoryLocal(ctx, None, test_config_local)
cylinder = ctx.get_part("cylinder", "primitive_local")
# TODO(clairbee): implement lazy loading
# assert cylinder.shape is None
# logo.build()
assert cylinder.shape is None
assert cylinder.get_wrapped() is not None


def test_part_aliases():
"""Test for part aliases"""
ctx = pc.Context() # Empty config
_ = pc.ProjectFactoryLocal(ctx, None, test_config_local)
# "box" is an alias for "cube"
box = ctx.get_part("box", "primitive_local")
assert box.shape is None
assert box.get_wrapped() is not None


def test_part_example_cadquery_primitive():
"""Instantiate all parts from the example: part_cadquery_primitive"""
ctx = pc.init("tests/partcad-examples.yaml")
cube = ctx.get_part("cube", "example_part_cadquery_primitive")
assert cube is not None
cylinder = ctx.get_part("cylinder", "example_part_cadquery_primitive")
assert cylinder is not None
assert cylinder.get_wrapped() is not None


def test_part_example_cadquery_logo():
Expand All @@ -86,10 +99,12 @@ def test_part_example_cadquery_logo():
assert bone is not None
head_half = ctx.get_part("head_half", "example_part_cadquery_logo")
assert head_half is not None
assert head_half.get_wrapped() is not None


def test_part_example_build123d_primitive():
"""Instantiate all parts from the example: part_build123d_primitive"""
ctx = pc.init("tests/partcad-examples.yaml")
cube = ctx.get_part("cube", "example_part_build123d_primitive")
assert cube is not None
assert cube.get_wrapped() is not None

0 comments on commit 810ef7d

Please sign in to comment.