From 713ab1cf6bfe0b01c539a6b83d32fa52d2d1b0ca Mon Sep 17 00:00:00 2001 From: obucklin Date: Wed, 24 Apr 2024 16:31:40 +0200 Subject: [PATCH 1/6] added nesting module, class, gh component, format and lint --- .../components/CT_Beam_Nesting/code.py | 40 ++++++ .../components/CT_Beam_Nesting/icon.png | Bin 0 -> 818 bytes .../components/CT_Beam_Nesting/metadata.json | 48 +++++++ src/compas_timber/planning/__init__.py | 8 +- src/compas_timber/planning/beam_nesting.py | 129 ++++++++++++++++++ 5 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py create mode 100644 src/compas_timber/ghpython/components/CT_Beam_Nesting/icon.png create mode 100644 src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json create mode 100644 src/compas_timber/planning/beam_nesting.py diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py new file mode 100644 index 000000000..2c0ba7edf --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py @@ -0,0 +1,40 @@ +from ghpythonlib.componentbase import executingcomponent as component +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error +from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning +from Grasshopper import DataTree +from Grasshopper.Kernel.Data import GH_Path +from compas_timber.planning import Nester + + +class NestingComponent(component): + + def RunScript(self, assembly, stock_length, tolerance=1, iterations=10): + + if not assembly: + self.AddRuntimeMessage(Warning, "input Assembly failed to collect data") + return + if not stock_length: + self.AddRuntimeMessage(Warning, "input Stock Length failed to collect data") + return + + nester = Nester() + nesting_dict = nester.get_bins(assembly.beams, stock_length, tolerance, iterations) + + data_tree = DataTree[object]() + for key, value in nesting_dict.items(): + path = GH_Path(int(key)) + data_tree.AddRange(value, path) + + info = [] + info.append("total length: {:.2f}".format(nester.total_length)) + info.append("bin count = {0}".format(len(nesting_dict.items()))) + info.append("cutoff waste = {:.2f}".format(nester.total_space(nesting_dict))) + + beam_list_out = [] + for val in nesting_dict.values(): + beam_list_out.extend(val) + + if set(beam_list_out) != set(assembly.beams): + self.AddRuntimeMessage(Error, "beams inputs and outputs dont match") + + return info, data_tree diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/icon.png b/src/compas_timber/ghpython/components/CT_Beam_Nesting/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..6e0a15da4329a81dfbd0744a9a58868b0e3a9132 GIT binary patch literal 818 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR01_q`l zo-U3d9-T>l{{OdU-qHY;0}(0X#-5Vm=QZFm@`Qv`#R6{=XjNdY}UirB*PRNI2*j zI|#K$Gdz`cxY}UBDUk+u$f2|!Oe@-$UnKVAF$T#rUTL!64mk7s|9nQUZl~}H#_3Km zVhM#3CwMvz|AVDKBNodZ%^{{J_ISmV^MQ&BNFnQ>CG;xj`77Kk7d=Yd20zh*Q( zgm9A@+h+1IGanYpuux*2Fx^p4tAWeQ;iY&$6;p`4S*;-t505xhV>RAxM5nfbM)vGE}@P{aTKNljhNJgk~*J3E3HjF@u_ znSlT-(Gd7Ax|t0{~t;_6pK0&U>ofmqQL&G7Y^BUdfsrD_4W6u7X{H^wvKzkp%C z!9!Kb;cEkz>kd%ZLu1ovBZJhz1woq|Cr%WcvbmEfVCw>#OC3R*fH4Cx)#*Fqnb`^q zmmG8&R=V=Wv9Up8fk`!3nPn80zV zTj}tD0}yi#ZD7bc$prFwM3$qrXiOnfk)a2#M$$!NX^`I_o;#GqDAKTJ?|fh|2+!+b zyQF{mzyX2wt1NI^n<5hq04+H?K_BS9EQ==&4!o<_rf6Jv$^-NfAH*i6$Odm?j)owX rHS-RzB`j6jX7!T+7)PM!2L>!NL#LH&r0?zj`XIiitDnm{r-UW|=gl=k literal 0 HcmV?d00001 diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json b/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json new file mode 100644 index 000000000..da84f15f7 --- /dev/null +++ b/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json @@ -0,0 +1,48 @@ +{ + "name": "Assembly", + "nickname": "Assembly", + "category": "COMPAS Timber", + "subcategory": "Fabrication", + "description": "Creates an Assembly", + "exposure": 2, + "ghpython": { + "isAdvancedMode": true, + "iconDisplay": 0, + "inputParameters": [ + { + "name": "Assembly", + "description": "Compas_Timber Assembly", + "typeHintID": "none", + "scriptParamAccess": 0 + }, + { + "name": "Stock Length", + "description": "length of timber stock the beams are packed into.", + "typeHintID": "float", + "scriptParamAccess": 0 + }, + { + "name": "Tolerance", + "description": "amount of cutoff per stock piece that is considered complete.", + "typeHintID": "float", + "scriptParamAccess": 0 + }, + { + "name": "Iterations", + "description": "number of cycles the nester will run.", + "typeHintID": "int", + "scriptParamAccess":0 + } + ], + "outputParameters": [ + { + "name": "Packing", + "description": "Packing info as a tree." + }, + { + "name": "Info", + "description": "General info about packing." + } + ] + } +} diff --git a/src/compas_timber/planning/__init__.py b/src/compas_timber/planning/__init__.py index a2a3346c7..5e384a56f 100644 --- a/src/compas_timber/planning/__init__.py +++ b/src/compas_timber/planning/__init__.py @@ -2,10 +2,6 @@ from .sequencer import BuildingPlan from .sequencer import SimpleSequenceGenerator from .sequencer import Step +from .beam_nesting import Nester -__all__ = [ - "Actor", - "BuildingPlan", - "Step", - "SimpleSequenceGenerator", -] +__all__ = ["Actor", "BuildingPlan", "Step", "SimpleSequenceGenerator", "Nester"] diff --git a/src/compas_timber/planning/beam_nesting.py b/src/compas_timber/planning/beam_nesting.py new file mode 100644 index 000000000..ba881af47 --- /dev/null +++ b/src/compas_timber/planning/beam_nesting.py @@ -0,0 +1,129 @@ +from collections import OrderedDict +import random + + +class Nester(object): + def __init__(self): + pass + + def shuffle(self, lst): + random.shuffle(lst) + + def space_remaining(self, bin): + if len(bin) == 0: + return self.stock_length + return self.stock_length - (sum([beam.blank_length for beam in bin])) + + def sorted_dict(self, bin_dict): + if len(bin_dict.items()) < 2: + return bin_dict + bin_list = sorted(bin_dict.values(), key=lambda x: self.space_remaining(x)) + dict = OrderedDict() + for i in range(len(bin_list)): + dict[i] = bin_list[i] + return dict + + def total_space(self, bin_dict): + return sum([self.space_remaining(bin) for bin in bin_dict.values()]) + + def get_initial_bins(self, beams): + bins = OrderedDict([(0, [])]) + for beam in beams: + fits = False + bins = self.sorted_dict(bins) + for bin in bins.values(): + if self.space_remaining(bin) >= beam.blank_length: + bin.append(beam) + fits = True + break + if not fits: + bins[str(len(bins))] = [beam] + return bins + + def fill_bins(self, bins, beams): + for beam in beams: + fits = False + bins = self.sorted_dict(bins) + for bin in bins.values(): + if self.space_remaining(bin) >= beam.blank_length: + bin.append(beam) + fits = True + break + if not fits: + bins[str(len(bins))] = [beam] + return bins + + def parse_bins(self, bin_dict): + dict_out = {"done": False} + dict_out["finished_bins"] = bin_dict + if self.total_space(bin_dict) < self.stock_length: + dict_out["done"] = True + return dict_out + + else: + recycled_beams = [] + temporary_bins = OrderedDict() + for bin in bin_dict.values(): + if self.space_remaining(bin) > self.tolerance: + recycled_beams.extend(bin) + else: + temporary_bins[str(len(temporary_bins))] = bin + dict_out["temporary_bins"] = temporary_bins + dict_out["recycled_beams"] = recycled_beams + return dict_out + + def get_bins(self, beams, stock_length, tolerance=1, iterations=10): + self.stock_length = stock_length + self.tolerance = tolerance + if tolerance is None: + self.tolerance = 1 + if iterations is None: + iterations = 10 + + self.total_length = sum([beam.blank_length for beam in beams]) + sort = False + bins_out = None + all_bins = [] + solved = False + + for i in range(iterations): + if solved: + break + these_beams = beams + these_bins = self.get_initial_bins(these_beams) + results_dict = self.parse_bins(these_bins) + if results_dict["done"]: + bins_out = results_dict["finished_bins"] + else: + for x in range(iterations): + if sort: + beams_sorted = sorted(results_dict["recycled_beams"], key=lambda z: z.length, reverse=True) + else: + beams_sorted = results_dict["recycled_beams"] + self.shuffle(beams_sorted) + + temp_bins = self.fill_bins(results_dict["temporary_bins"], beams_sorted) + + results_dict = self.parse_bins(temp_bins) + + if results_dict["done"]: + bins_out = results_dict["finished_bins"] + print("success after {0} iterations.".format(x)) + solved = True + break + elif x == iterations - 1: + all_bins.append(results_dict["finished_bins"]) + else: + sort = not sort + + if not bins_out: + bins_out = min(all_bins, key=lambda x: len(x)) + + beam_list_out = [] + for val in bins_out.values(): + beam_list_out.extend(val) + + if set(beam_list_out) != set(beams): + raise Exception("beams inputs and outputs dont match") + + return bins_out From 3e7e9927eefd88efec0ed1ecbb04a0a0d9e38e46 Mon Sep 17 00:00:00 2001 From: obucklin Date: Wed, 24 Apr 2024 17:07:47 +0200 Subject: [PATCH 2/6] amended changelog --- CHANGELOG.md | 2 ++ .../ghpython/components/CT_Beam_Nesting/metadata.json | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b83e297bd..08d632d5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added +* Added `Nester` class for one-dimensional bin packing. this is to fit the beams of an assembly into material stock of a given length +* Added `Beam Nester` GH component for `Nester` class ### Changed diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json b/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json index da84f15f7..463606738 100644 --- a/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json +++ b/src/compas_timber/ghpython/components/CT_Beam_Nesting/metadata.json @@ -1,9 +1,9 @@ { - "name": "Assembly", - "nickname": "Assembly", + "name": "Beam Nester", + "nickname": "Nester", "category": "COMPAS Timber", "subcategory": "Fabrication", - "description": "Creates an Assembly", + "description": "Nests beams into stock lengths.", "exposure": 2, "ghpython": { "isAdvancedMode": true, From 5371cc697b50a2af9345783d3e8a651b6055a98d Mon Sep 17 00:00:00 2001 From: obucklin Date: Wed, 24 Apr 2024 17:19:04 +0200 Subject: [PATCH 3/6] added docstrings --- src/compas_timber/planning/beam_nesting.py | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/compas_timber/planning/beam_nesting.py b/src/compas_timber/planning/beam_nesting.py index ba881af47..08b5fdecd 100644 --- a/src/compas_timber/planning/beam_nesting.py +++ b/src/compas_timber/planning/beam_nesting.py @@ -3,6 +3,20 @@ class Nester(object): + """Class for nesting beams into a stock beam + + + Attributes + ---------- + stock_length : float + length of the stock beam in which to be nested + tolerance : float + tolerance for the nesting algorithm, if the remaining space in a bin is less than the tolerance, the bin is considered full + total_length : float + total length of all beams to be nested + + """ + def __init__(self): pass @@ -10,11 +24,13 @@ def shuffle(self, lst): random.shuffle(lst) def space_remaining(self, bin): + """returns the space remaining in a bin""" if len(bin) == 0: return self.stock_length return self.stock_length - (sum([beam.blank_length for beam in bin])) def sorted_dict(self, bin_dict): + """sorts a dictionary of bins by space remaining in each bin""" if len(bin_dict.items()) < 2: return bin_dict bin_list = sorted(bin_dict.values(), key=lambda x: self.space_remaining(x)) @@ -24,9 +40,11 @@ def sorted_dict(self, bin_dict): return dict def total_space(self, bin_dict): + """returns the total space remaining in all bins, AKA total waste""" return sum([self.space_remaining(bin) for bin in bin_dict.values()]) def get_initial_bins(self, beams): + """returns a dictionary of bins with beams nested in them""" bins = OrderedDict([(0, [])]) for beam in beams: fits = False @@ -41,6 +59,7 @@ def get_initial_bins(self, beams): return bins def fill_bins(self, bins, beams): + """fills a partial bins dictionary with beams, returns a dictionary of bins with beams nested in them""" for beam in beams: fits = False bins = self.sorted_dict(bins) @@ -54,6 +73,7 @@ def fill_bins(self, bins, beams): return bins def parse_bins(self, bin_dict): + """evaluates the success of the nesting, returns a dictionary with the results of the nesting process""" dict_out = {"done": False} dict_out["finished_bins"] = bin_dict if self.total_space(bin_dict) < self.stock_length: @@ -73,6 +93,21 @@ def parse_bins(self, bin_dict): return dict_out def get_bins(self, beams, stock_length, tolerance=1, iterations=10): + """returns a dictionary of bins with beams nested in them + + Parameters + ---------- + beams : list(:class:`compas_timber.parts.Beam`) + list of beams to be nested + stock_length : float + length of the stock beam in which to be nested + tolerance : float + tolerance for the nesting algorithm, if the remaining space in a bin is less than the tolerance, the bin is considered full + iterations : int + number of iterations to run the nesting algorithm, the algorithm will stop when the total waste is less than the stock length + + """ + self.stock_length = stock_length self.tolerance = tolerance if tolerance is None: From 78cb6dfc1bb0c5b7c8ac20db4a6ab66c8ba607dc Mon Sep 17 00:00:00 2001 From: oliver bucklin Date: Fri, 26 Apr 2024 12:13:39 +0200 Subject: [PATCH 4/6] added 0 iteration case --- .../components/CT_Beam_Nesting/code.py | 2 +- src/compas_timber/planning/beam_nesting.py | 58 ++++++++++++------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py index 2c0ba7edf..07b23eaec 100644 --- a/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py +++ b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py @@ -37,4 +37,4 @@ def RunScript(self, assembly, stock_length, tolerance=1, iterations=10): if set(beam_list_out) != set(assembly.beams): self.AddRuntimeMessage(Error, "beams inputs and outputs dont match") - return info, data_tree + return data_tree, info diff --git a/src/compas_timber/planning/beam_nesting.py b/src/compas_timber/planning/beam_nesting.py index 08b5fdecd..79fde2000 100644 --- a/src/compas_timber/planning/beam_nesting.py +++ b/src/compas_timber/planning/beam_nesting.py @@ -43,10 +43,17 @@ def total_space(self, bin_dict): """returns the total space remaining in all bins, AKA total waste""" return sum([self.space_remaining(bin) for bin in bin_dict.values()]) - def get_initial_bins(self, beams): + def get_initial_bins(self, beams, sort=True, shuffle=False): """returns a dictionary of bins with beams nested in them""" + if sort: + beams_sorted = sorted(beams, key=lambda z: z.length, reverse=True) + elif shuffle: + beams_sorted = beams + self.shuffle(beams_sorted) + else: + beams_sorted = beams bins = OrderedDict([(0, [])]) - for beam in beams: + for beam in beams_sorted: fits = False bins = self.sorted_dict(bins) for bin in bins.values(): @@ -58,9 +65,14 @@ def get_initial_bins(self, beams): bins[str(len(bins))] = [beam] return bins - def fill_bins(self, bins, beams): + def fill_bins(self, bins, beams, sort=True, shuffle=False): """fills a partial bins dictionary with beams, returns a dictionary of bins with beams nested in them""" - for beam in beams: + if sort: + beams_sorted = sorted(beams, key=lambda z: z.length, reverse=True) + elif shuffle: + beams_sorted = beams + self.shuffle(beams_sorted) + for beam in beams_sorted: fits = False bins = self.sorted_dict(bins) for bin in bins.values(): @@ -72,6 +84,11 @@ def fill_bins(self, bins, beams): bins[str(len(bins))] = [beam] return bins + def longest_cutoff(self, bin_dict): + """returns the longest cutoff in a bin dictionary""" + sorted_bins = self.sorted_dict(bin_dict) + return [self.space_remaining(bin) for bin in sorted_bins.values()] + def parse_bins(self, bin_dict): """evaluates the success of the nesting, returns a dictionary with the results of the nesting process""" dict_out = {"done": False} @@ -92,7 +109,7 @@ def parse_bins(self, bin_dict): dict_out["recycled_beams"] = recycled_beams return dict_out - def get_bins(self, beams, stock_length, tolerance=1, iterations=10): + def get_bins(self, beams, stock_length, tolerance=None, iterations=0): """returns a dictionary of bins with beams nested in them Parameters @@ -109,47 +126,48 @@ def get_bins(self, beams, stock_length, tolerance=1, iterations=10): """ self.stock_length = stock_length - self.tolerance = tolerance if tolerance is None: - self.tolerance = 1 + tolerance = stock_length / 100 + self.tolerance = tolerance + + else: + self.tolerance = tolerance if iterations is None: - iterations = 10 + iterations = 0 self.total_length = sum([beam.blank_length for beam in beams]) - sort = False bins_out = None all_bins = [] - solved = False + + if iterations == 0: + return self.get_initial_bins(beams) for i in range(iterations): - if solved: - break these_beams = beams these_bins = self.get_initial_bins(these_beams) results_dict = self.parse_bins(these_bins) if results_dict["done"]: bins_out = results_dict["finished_bins"] else: + sort = True + shuffle = False for x in range(iterations): - if sort: - beams_sorted = sorted(results_dict["recycled_beams"], key=lambda z: z.length, reverse=True) - else: - beams_sorted = results_dict["recycled_beams"] - self.shuffle(beams_sorted) - - temp_bins = self.fill_bins(results_dict["temporary_bins"], beams_sorted) + these_beams = results_dict["recycled_beams"] + temp_bins = self.fill_bins(results_dict["temporary_bins"], these_beams, sort, shuffle) results_dict = self.parse_bins(temp_bins) if results_dict["done"]: bins_out = results_dict["finished_bins"] print("success after {0} iterations.".format(x)) - solved = True break elif x == iterations - 1: all_bins.append(results_dict["finished_bins"]) else: sort = not sort + shuffle = not shuffle + if results_dict["done"]: + break if not bins_out: bins_out = min(all_bins, key=lambda x: len(x)) From c6e3e23f0931aed9671d745af05340463695daf3 Mon Sep 17 00:00:00 2001 From: oliver bucklin Date: Fri, 26 Apr 2024 12:29:56 +0200 Subject: [PATCH 5/6] cleaned and commented --- src/compas_timber/planning/beam_nesting.py | 90 +++++++++++----------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/src/compas_timber/planning/beam_nesting.py b/src/compas_timber/planning/beam_nesting.py index 79fde2000..8f83a14a4 100644 --- a/src/compas_timber/planning/beam_nesting.py +++ b/src/compas_timber/planning/beam_nesting.py @@ -43,15 +43,9 @@ def total_space(self, bin_dict): """returns the total space remaining in all bins, AKA total waste""" return sum([self.space_remaining(bin) for bin in bin_dict.values()]) - def get_initial_bins(self, beams, sort=True, shuffle=False): - """returns a dictionary of bins with beams nested in them""" - if sort: - beams_sorted = sorted(beams, key=lambda z: z.length, reverse=True) - elif shuffle: - beams_sorted = beams - self.shuffle(beams_sorted) - else: - beams_sorted = beams + def get_bins_basic(self, beams): + """returns a dictionary of bins with beams nested in them""" + beams_sorted = sorted(beams, key=lambda z: z.length, reverse=True) bins = OrderedDict([(0, [])]) for beam in beams_sorted: fits = False @@ -109,6 +103,14 @@ def parse_bins(self, bin_dict): dict_out["recycled_beams"] = recycled_beams return dict_out + def validate_bin_results(self, bins, beams): + beam_list_out = [] + for val in bins.values(): + beam_list_out.extend(val) + + if set(beam_list_out) != set(beams): + raise Exception("beams inputs and outputs dont match") + def get_bins(self, beams, stock_length, tolerance=None, iterations=0): """returns a dictionary of bins with beams nested in them @@ -127,9 +129,7 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): self.stock_length = stock_length if tolerance is None: - tolerance = stock_length / 100 - self.tolerance = tolerance - + self.tolerance = stock_length / 100 else: self.tolerance = tolerance if iterations is None: @@ -140,43 +140,39 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): all_bins = [] if iterations == 0: - return self.get_initial_bins(beams) - - for i in range(iterations): - these_beams = beams - these_bins = self.get_initial_bins(these_beams) - results_dict = self.parse_bins(these_bins) - if results_dict["done"]: - bins_out = results_dict["finished_bins"] - else: - sort = True - shuffle = False - for x in range(iterations): - these_beams = results_dict["recycled_beams"] - temp_bins = self.fill_bins(results_dict["temporary_bins"], these_beams, sort, shuffle) - - results_dict = self.parse_bins(temp_bins) - - if results_dict["done"]: - bins_out = results_dict["finished_bins"] - print("success after {0} iterations.".format(x)) - break - elif x == iterations - 1: - all_bins.append(results_dict["finished_bins"]) - else: - sort = not sort - shuffle = not shuffle - if results_dict["done"]: - break + return self.get_bins_basic(beams) + + else: + for i in range(iterations): #try with different shuffling + these_beams = beams + these_bins = self.get_bins_basic(these_beams) + results_dict = self.parse_bins(these_bins) + if results_dict["done"]: #if the nesting is successful + bins_out = results_dict["finished_bins"] + else: + sort = False + shuffle = True + for x in range(iterations): #tries to repack the beams that don't fit in the bins within the cutoff tolerance + these_beams = results_dict["recycled_beams"] + temp_bins = self.fill_bins(results_dict["temporary_bins"], these_beams, sort, shuffle) + results_dict = self.parse_bins(temp_bins) + if results_dict["done"]: + bins_out = results_dict["finished_bins"] + print("success after {0} iterations.".format(x)) + break + elif x == iterations - 1: #if the last iteration is reached, the best result is taken + all_bins.append(results_dict["finished_bins"]) + else: + sort = not sort + shuffle = not shuffle + if results_dict["done"]: + break if not bins_out: - bins_out = min(all_bins, key=lambda x: len(x)) - - beam_list_out = [] - for val in bins_out.values(): - beam_list_out.extend(val) + bins_out = min(all_bins, key=lambda x: len(x)) #if no successful nesting is found, the one with the least bins is taken - if set(beam_list_out) != set(beams): - raise Exception("beams inputs and outputs dont match") + self.validate_bin_results(bins_out, beams) return bins_out + + From 15f3f8dee010f9af8f9c005c39a365c664f6abb6 Mon Sep 17 00:00:00 2001 From: oliver bucklin Date: Fri, 26 Apr 2024 12:43:24 +0200 Subject: [PATCH 6/6] removed redundant list comparison --- .../components/CT_Beam_Nesting/code.py | 10 +------- src/compas_timber/planning/beam_nesting.py | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py index 07b23eaec..bbc084496 100644 --- a/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py +++ b/src/compas_timber/ghpython/components/CT_Beam_Nesting/code.py @@ -1,5 +1,4 @@ from ghpythonlib.componentbase import executingcomponent as component -from Grasshopper.Kernel.GH_RuntimeMessageLevel import Error from Grasshopper.Kernel.GH_RuntimeMessageLevel import Warning from Grasshopper import DataTree from Grasshopper.Kernel.Data import GH_Path @@ -8,7 +7,7 @@ class NestingComponent(component): - def RunScript(self, assembly, stock_length, tolerance=1, iterations=10): + def RunScript(self, assembly, stock_length, tolerance, iterations): if not assembly: self.AddRuntimeMessage(Warning, "input Assembly failed to collect data") @@ -30,11 +29,4 @@ def RunScript(self, assembly, stock_length, tolerance=1, iterations=10): info.append("bin count = {0}".format(len(nesting_dict.items()))) info.append("cutoff waste = {:.2f}".format(nester.total_space(nesting_dict))) - beam_list_out = [] - for val in nesting_dict.values(): - beam_list_out.extend(val) - - if set(beam_list_out) != set(assembly.beams): - self.AddRuntimeMessage(Error, "beams inputs and outputs dont match") - return data_tree, info diff --git a/src/compas_timber/planning/beam_nesting.py b/src/compas_timber/planning/beam_nesting.py index 8f83a14a4..0e56c7a54 100644 --- a/src/compas_timber/planning/beam_nesting.py +++ b/src/compas_timber/planning/beam_nesting.py @@ -44,7 +44,7 @@ def total_space(self, bin_dict): return sum([self.space_remaining(bin) for bin in bin_dict.values()]) def get_bins_basic(self, beams): - """returns a dictionary of bins with beams nested in them""" + """returns a dictionary of bins with beams nested in them""" beams_sorted = sorted(beams, key=lambda z: z.length, reverse=True) bins = OrderedDict([(0, [])]) for beam in beams_sorted: @@ -109,7 +109,7 @@ def validate_bin_results(self, bins, beams): beam_list_out.extend(val) if set(beam_list_out) != set(beams): - raise Exception("beams inputs and outputs dont match") + raise Exception("Beams input and nesting output dont match") def get_bins(self, beams, stock_length, tolerance=None, iterations=0): """returns a dictionary of bins with beams nested in them @@ -126,7 +126,6 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): number of iterations to run the nesting algorithm, the algorithm will stop when the total waste is less than the stock length """ - self.stock_length = stock_length if tolerance is None: self.tolerance = stock_length / 100 @@ -141,18 +140,20 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): if iterations == 0: return self.get_bins_basic(beams) - + else: - for i in range(iterations): #try with different shuffling + for i in range(iterations): # try with different shuffling these_beams = beams these_bins = self.get_bins_basic(these_beams) results_dict = self.parse_bins(these_bins) - if results_dict["done"]: #if the nesting is successful + if results_dict["done"]: # if the nesting is successful bins_out = results_dict["finished_bins"] else: sort = False shuffle = True - for x in range(iterations): #tries to repack the beams that don't fit in the bins within the cutoff tolerance + for x in range( + iterations + ): # tries to repack the beams that don't fit in the bins within the cutoff tolerance these_beams = results_dict["recycled_beams"] temp_bins = self.fill_bins(results_dict["temporary_bins"], these_beams, sort, shuffle) results_dict = self.parse_bins(temp_bins) @@ -160,7 +161,7 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): bins_out = results_dict["finished_bins"] print("success after {0} iterations.".format(x)) break - elif x == iterations - 1: #if the last iteration is reached, the best result is taken + elif x == iterations - 1: # if the last iteration is reached, the best result is taken all_bins.append(results_dict["finished_bins"]) else: sort = not sort @@ -169,10 +170,10 @@ def get_bins(self, beams, stock_length, tolerance=None, iterations=0): break if not bins_out: - bins_out = min(all_bins, key=lambda x: len(x)) #if no successful nesting is found, the one with the least bins is taken + bins_out = min( + all_bins, key=lambda x: len(x) + ) # if no successful nesting is found, the one with the least bins is taken self.validate_bin_results(bins_out, beams) return bins_out - -