Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/lazy loading #39

Merged
merged 2 commits into from
Jan 6, 2024
Merged
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
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