- PDF Import tool: added support for detection of circular geometry drawn with white color which means actually invisible color. When detected, FlatCAM will build an Excellon file out of those geoms.

- PDF Import tool: fixed storing geometries in apertures with the right size (before they were all stored in aperture D10)
This commit is contained in:
Marius Stanciu 2019-04-23 02:02:20 +03:00
parent d66d914cc3
commit 82a0287f4d
3 changed files with 228 additions and 63 deletions

View File

@ -95,7 +95,7 @@ class App(QtCore.QObject):
# Version # Version
version = 8.914 version = 8.914
version_date = "2019/04/22" version_date = "2019/04/23"
beta = True beta = True
# current date now # current date now

View File

@ -16,7 +16,8 @@ CAD program, and create G-Code for Isolation routing.
- PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7 - PDF import tool: added support for save/restore Graphics stack. Only for scale and offset transformations and for the linewidth. This is the final fix for Microsoft PDF printer who saves in PDF format 1.7
- PDF Import tool: added support for PDF files that embed multiple Gerber layers (top, bottom, outline, silkscreen etc). Each will be opened in it's own Gerber file. The requirement is that each one is drawn in a different color - PDF Import tool: added support for PDF files that embed multiple Gerber layers (top, bottom, outline, silkscreen etc). Each will be opened in it's own Gerber file. The requirement is that each one is drawn in a different color
- PDF Import tool: fixed bugs when drag & dropping PDF files on canvas the files geometry previously opened was added to the new one. Also scaling issues. Solved. - PDF Import tool: fixed bugs when drag & dropping PDF files on canvas the files geometry previously opened was added to the new one. Also scaling issues. Solved.
- PDF Import tool: added support for detection of circular geometry drawn with white color which means actually invisible color. When detected, FlatCAM will build an Excellon file out of those geoms.
- PDF Import tool: fixed storing geometries in apertures with the right size (before they were all stored in aperture D10)
21.04.2019 21.04.2019

View File

@ -43,8 +43,12 @@ class ToolPDF(FlatCAMTool):
self.stream_re = re.compile(b'.*?FlateDecode.*?stream(.*?)endstream', re.S) self.stream_re = re.compile(b'.*?FlateDecode.*?stream(.*?)endstream', re.S)
# detect color change; it means a new object to be created # detect stroke color change; it means a new object to be created
self.color_re = re.compile(r'^\s*(\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)\s*RG$') self.stroke_color_re = re.compile(r'^\s*(\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)\s*RG$')
# detect fill color change; we check here for white color (transparent geometry);
# if detected we create an Excellon from it
self.fill_color_re = re.compile(r'^\s*(\d+\.?\d*) (\d+\.?\d*) (\d+\.?\d*)\s*rg$')
# detect 're' command # detect 're' command
self.rect_re = re.compile(r'^(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s*re$') self.rect_re = re.compile(r'^(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s(-?\d+\.?\d*)\s*re$')
@ -104,6 +108,7 @@ class ToolPDF(FlatCAMTool):
self.obj_dict = dict() self.obj_dict = dict()
self.pdf_parsed = '' self.pdf_parsed = ''
self.parsed_obj_dict = dict()
# conversion factor to INCH # conversion factor to INCH
self.point_to_unit_factor = 0.01388888888 self.point_to_unit_factor = 0.01388888888
@ -151,6 +156,8 @@ class ToolPDF(FlatCAMTool):
new_name = filename.split('/')[-1].split('\\')[-1] new_name = filename.split('/')[-1].split('\\')[-1]
self.obj_dict.clear() self.obj_dict.clear()
self.pdf_parsed = '' self.pdf_parsed = ''
self.parsed_obj_dict = {}
obj_type = 'gerber'
# the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH) # the UNITS in PDF files are points and here we set the factor to convert them to real units (either MM or INCH)
if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM': if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
@ -174,37 +181,96 @@ class ToolPDF(FlatCAMTool):
except Exception as e: except Exception as e:
log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e)) log.debug("ToolPDF.open_pdf().obj_init() --> %s" % str(e))
self.obj_dict = self.parse_pdf(pdf_content=self.pdf_parsed) self.parsed_obj_dict = self.parse_pdf(pdf_content=self.pdf_parsed)
for k in self.obj_dict: for k in self.parsed_obj_dict:
ap_dict = deepcopy(self.obj_dict[k]) ap_dict = deepcopy(self.parsed_obj_dict[k])
if ap_dict: if ap_dict:
def obj_init(grb_obj, app_obj): if k == 0:
# Excellon
obj_type = 'excellon'
grb_obj.apertures = ap_dict new_name = new_name + "_exc"
# store the points here until reconstitution: keys are diameters and values are list of (x,y) coords
points = {}
poly_buff = [] def obj_init(exc_obj, app_obj):
for ap in grb_obj.apertures: # print(self.parsed_obj_dict[0])
for k in grb_obj.apertures[ap]:
if k == 'solid_geometry':
poly_buff += ap_dict[ap][k]
poly_buff = unary_union(poly_buff) for geo in self.parsed_obj_dict[0]['0']['solid_geometry']:
poly_buff = poly_buff.buffer(0.0000001) xmin, ymin, xmax, ymax = geo.bounds
poly_buff = poly_buff.buffer(-0.0000001) center = (((xmax - xmin) / 2) + xmin, ((ymax - ymin) / 2) + ymin)
grb_obj.solid_geometry = deepcopy(poly_buff) # for drill bits, even in INCH, it's enough 3 decimals
correction_factor = 0.974
dia = (xmax - xmin) * correction_factor
dia = round(dia, 3)
if dia in points:
points[dia].append(center)
else:
points[dia] = [center]
with self.app.proc_container.new(_("Opening PDF layer #%d ...") % (int(k) - 2)): sorted_dia = sorted(points.keys())
ret = self.app.new_object("gerber", new_name, obj_init, autoselected=False) name_tool = 0
for dia in sorted_dia:
name_tool += 1
# create tools dictionary
spec = {"C": dia}
spec['solid_geometry'] = []
exc_obj.tools[str(name_tool)] = spec
# create drill list of dictionaries
for dia_points in points:
if dia == dia_points:
for pt in points[dia_points]:
exc_obj.drills.append({'point': Point(pt), 'tool': str(name_tool)})
break
ret = exc_obj.create_geometry()
if ret == 'fail':
log.debug("Could not create geometry for Excellon object.")
return "fail"
for tool in exc_obj.tools:
if exc_obj.tools[tool]['solid_geometry']:
return
app_obj.inform.emit(_("[ERROR_NOTCL] No geometry found in file: %s") % new_name)
return "fail"
else:
# Gerber
obj_type = 'gerber'
def obj_init(grb_obj, app_obj):
grb_obj.apertures = ap_dict
poly_buff = []
for ap in grb_obj.apertures:
for k in grb_obj.apertures[ap]:
if k == 'solid_geometry':
poly_buff += ap_dict[ap][k]
poly_buff = unary_union(poly_buff)
try:
poly_buff = poly_buff.buffer(0.0000001)
except ValueError:
pass
try:
poly_buff = poly_buff.buffer(-0.0000001)
except ValueError:
pass
grb_obj.solid_geometry = deepcopy(poly_buff)
with self.app.proc_container.new(_("Rendering PDF layer #%d ...") % (int(k) - 2)):
ret = self.app.new_object(obj_type, new_name, obj_init, autoselected=False)
if ret == 'fail': if ret == 'fail':
self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.')) self.app.inform.emit(_('[ERROR_NOTCL] Open PDF file failed.'))
return return
# Register recent file # Register recent file
self.app.file_opened.emit("gerber", filename) self.app.file_opened.emit(obj_type, filename)
# GUI feedback # GUI feedback
self.app.inform.emit(_("[success] Opened: %s") % filename) self.app.inform.emit(_("[success] Opened: %s") % filename)
@ -233,9 +299,6 @@ class ToolPDF(FlatCAMTool):
offset_geo = [0, 0] offset_geo = [0, 0]
scale_geo = [1, 1] scale_geo = [1, 1]
# initial aperture
aperture = 10
# store the objects to be transformed into Gerbers # store the objects to be transformed into Gerbers
object_dict = {} object_dict = {}
@ -245,14 +308,29 @@ class ToolPDF(FlatCAMTool):
# store the apertures here # store the apertures here
apertures_dict = {} apertures_dict = {}
# initial aperture
aperture = 10
# store the apertures with clear geometry here
# we are interested only in the circular geometry (drill holes) therefore we target only Bezier subpaths
clear_apertures_dict = dict()
# everything will be stored in the '0' aperture since we are dealing with clear polygons not strokes
clear_apertures_dict['0'] = dict()
clear_apertures_dict['0']['size'] = 0.0
clear_apertures_dict['0']['type'] = 'C'
clear_apertures_dict['0']['solid_geometry'] = []
# create first object # create first object
object_dict[object_nr] = apertures_dict object_dict[object_nr] = apertures_dict
object_nr += 1 object_nr += 1
# on color change we create a new apertures dictionary and store the old one in a storage from where it will be # on stroke color change we create a new apertures dictionary and store the old one in a storage from where
# transformed into Gerber object # it will be transformed into Gerber object
old_color = [None, None ,None] old_color = [None, None ,None]
# signal that we have clear geometry and the geometry will be added to a special object_nr = 0
flag_clear_geo = False
line_nr = 0 line_nr = 0
lines = pdf_content.splitlines() lines = pdf_content.splitlines()
@ -261,9 +339,12 @@ class ToolPDF(FlatCAMTool):
# log.debug("line %d: %s" % (line_nr, pline)) # log.debug("line %d: %s" % (line_nr, pline))
# COLOR DETECTION / OBJECT DETECTION # COLOR DETECTION / OBJECT DETECTION
match = self.color_re.search(pline) match = self.stroke_color_re.search(pline)
if match: if match:
color = [float(match.group(1)), float(match.group(2)), float(match.group(3))] color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
log.debug(
"ToolPDF.parse_pdf() --> STROKE Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
(line_nr, color[0], color[1], color[2]))
if color[0] == old_color[0] and color[1] == old_color[1] and color[2] == old_color[2]: if color[0] == old_color[0] and color[1] == old_color[1] and color[2] == old_color[2]:
# same color, do nothing # same color, do nothing
@ -271,9 +352,28 @@ class ToolPDF(FlatCAMTool):
else: else:
object_dict[object_nr] = deepcopy(apertures_dict) object_dict[object_nr] = deepcopy(apertures_dict)
object_nr += 1 object_nr += 1
object_dict[object_nr] = dict() object_dict[object_nr] = dict()
apertures_dict.clear() apertures_dict = {}
old_color = copy(color) old_color = copy(color)
# we make sure that the following geometry is added to the right storage
flag_clear_geo = False
continue
# CLEAR GEOMETRY detection
match = self.fill_color_re.search(pline)
if match:
fill_color = [float(match.group(1)), float(match.group(2)), float(match.group(3))]
log.debug(
"ToolPDF.parse_pdf() --> FILL Color change on line: %s --> RED=%f GREEN=%f BLUE=%f" %
(line_nr, fill_color[0], fill_color[1], fill_color[2]))
# if the color is white we are seeing 'clear_geometry' that can't be seen. It may be that those
# geometries are actually holes from which we can make an Excellon file
if fill_color[0] == 1 and fill_color[1] == 1 and fill_color[2] == 1:
flag_clear_geo = True
else:
flag_clear_geo = False
continue
# TRANSFORMATIONS DETECTION # # TRANSFORMATIONS DETECTION #
@ -366,7 +466,7 @@ class ToolPDF(FlatCAMTool):
# add the start point to subpaths # add the start point to subpaths
subpath['lines'].append(start_point) subpath['lines'].append(start_point)
# subpath['bezier'].append(start_point) # subpath['bezier'].append(start_point)
subpath['rectangle'].append(start_point) # subpath['rectangle'].append(start_point)
current_point = start_point current_point = start_point
continue continue
@ -440,7 +540,7 @@ class ToolPDF(FlatCAMTool):
current_point = stop current_point = stop
continue continue
# Draw Rectangle 're # Draw Rectangle 're'
match = self.rect_re.search(pline) match = self.rect_re.search(pline)
if match: if match:
current_subpath = 'rectangle' current_subpath = 'rectangle'
@ -454,7 +554,6 @@ class ToolPDF(FlatCAMTool):
pt2 = (x+width, y) pt2 = (x+width, y)
pt3 = (x+width, y+height) pt3 = (x+width, y+height)
pt4 = (x, y+height) pt4 = (x, y+height)
# TODO: I'm not sure if rectangles are a type of subpath that close by itself
subpath['rectangle'] += [pt1, pt2, pt3, pt4, pt1] subpath['rectangle'] += [pt1, pt2, pt3, pt4, pt1]
current_point = pt1 current_point = pt1
continue continue
@ -491,7 +590,7 @@ class ToolPDF(FlatCAMTool):
path['bezier'].append(copy(subpath['bezier'])) path['bezier'].append(copy(subpath['bezier']))
subpath['bezier'] = [] subpath['bezier'] = []
elif current_subpath == 'rectangle': elif current_subpath == 'rectangle':
subpath['rectangle'].append(start_point) # subpath['rectangle'].append(start_point)
# since we are closing the subpath add it to the path, a path may have chained subpaths # since we are closing the subpath add it to the path, a path may have chained subpaths
path['rectangle'].append(copy(subpath['rectangle'])) path['rectangle'].append(copy(subpath['rectangle']))
subpath['rectangle'] = [] subpath['rectangle'] = []
@ -566,12 +665,29 @@ class ToolPDF(FlatCAMTool):
path_geo.append(geo) path_geo.append(geo)
subpath['rectangle'] = [] subpath['rectangle'] = []
try: # store the found geometry
apertures_dict[str(aperture)]['solid_geometry'] += path_geo found_aperture = None
except KeyError: if apertures_dict:
# in case there is no stroke width yet therefore no aperture for apid in apertures_dict:
# if we already have an aperture with the current size (rounded to 5 decimals)
if apertures_dict[apid]['size'] == round(applied_size, 5):
found_aperture = apid
break
if found_aperture:
apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
found_aperture = None
else:
if str(aperture) in apertures_dict.keys():
aperture += 1
apertures_dict[str(aperture)] = {}
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
apertures_dict[str(aperture)]['type'] = 'C'
apertures_dict[str(aperture)]['solid_geometry'] = []
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
else:
apertures_dict[str(aperture)] = {} apertures_dict[str(aperture)] = {}
apertures_dict[str(aperture)]['size'] = applied_size apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
apertures_dict[str(aperture)]['type'] = 'C' apertures_dict[str(aperture)]['type'] = 'C'
apertures_dict[str(aperture)]['solid_geometry'] = [] apertures_dict[str(aperture)]['solid_geometry'] = []
apertures_dict[str(aperture)]['solid_geometry'] += path_geo apertures_dict[str(aperture)]['solid_geometry'] += path_geo
@ -583,8 +699,8 @@ class ToolPDF(FlatCAMTool):
if match: if match:
# scale the size here; some PDF printers apply transformation after the size is declared # scale the size here; some PDF printers apply transformation after the size is declared
applied_size = size * scale_geo[0] * self.point_to_unit_factor applied_size = size * scale_geo[0] * self.point_to_unit_factor
path_geo = list() path_geo = list()
if current_subpath == 'lines': if current_subpath == 'lines':
if path['lines']: if path['lines']:
for subp in path['lines']: for subp in path['lines']:
@ -632,8 +748,8 @@ class ToolPDF(FlatCAMTool):
for subp in path['rectangle']: for subp in path['rectangle']:
geo = copy(subp) geo = copy(subp)
# close the subpath if it was not closed already # close the subpath if it was not closed already
if close_subpath is False: if close_subpath is False and start_point is not None:
geo.append(geo[0]) geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) path_geo.append(geo_el)
# the path was painted therefore initialize it # the path was painted therefore initialize it
@ -650,24 +766,35 @@ class ToolPDF(FlatCAMTool):
# we finished painting and also closed the path if it was the case # we finished painting and also closed the path if it was the case
close_subpath = True close_subpath = True
try: # if there was a fill color change we look for circular geometries from which we can make drill holes
apertures_dict['0']['solid_geometry'] += path_geo # for the Excellon file
except KeyError: if flag_clear_geo is True:
# in case there is no stroke width yet therefore no aperture # we llok for circular geometries
apertures_dict['0'] = {} if current_subpath == 'bezier':
apertures_dict['0']['size'] = applied_size # if there are geometries in the list
apertures_dict['0']['type'] = 'C' if path_geo:
apertures_dict['0']['solid_geometry'] = [] clear_apertures_dict['0']['solid_geometry'] += path_geo
apertures_dict['0']['solid_geometry'] += path_geo else:
continue # else, add the geometry as usual
try:
apertures_dict['0']['solid_geometry'] += path_geo
except KeyError:
# in case there is no stroke width yet therefore no aperture
apertures_dict['0'] = {}
apertures_dict['0']['size'] = applied_size
apertures_dict['0']['type'] = 'C'
apertures_dict['0']['solid_geometry'] = []
apertures_dict['0']['solid_geometry'] += path_geo
continue
# fill and stroke the path # Fill and Stroke the path
match = self.fill_stroke_path_re.search(pline) match = self.fill_stroke_path_re.search(pline)
if match: if match:
# scale the size here; some PDF printers apply transformation after the size is declared # scale the size here; some PDF printers apply transformation after the size is declared
applied_size = size * scale_geo[0] * self.point_to_unit_factor applied_size = size * scale_geo[0] * self.point_to_unit_factor
path_geo = list() path_geo = list()
fill_geo = list()
if current_subpath == 'lines': if current_subpath == 'lines':
if path['lines']: if path['lines']:
# fill # fill
@ -677,7 +804,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(geo[0]) geo.append(geo[0])
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
for subp in path['lines']: for subp in path['lines']:
geo = copy(subp) geo = copy(subp)
@ -692,7 +819,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(start_point) geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
geo = copy(subpath['lines']) geo = copy(subpath['lines'])
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles) geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
@ -711,7 +838,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(geo[0]) geo.append(geo[0])
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
for subp in path['bezier']: for subp in path['bezier']:
geo = [] geo = []
@ -728,7 +855,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(start_point) geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
geo = [] geo = []
for b in subpath['bezier']: for b in subpath['bezier']:
@ -746,7 +873,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(geo[0]) geo.append(geo[0])
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
for subp in path['rectangle']: for subp in path['rectangle']:
geo = copy(subp) geo = copy(subp)
@ -761,7 +888,7 @@ class ToolPDF(FlatCAMTool):
if close_subpath is False: if close_subpath is False:
geo.append(start_point) geo.append(start_point)
geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles) geo_el = Polygon(geo).buffer(0.0000001, resolution=self.step_per_circles)
path_geo.append(geo_el) fill_geo.append(geo_el)
# stroke # stroke
geo = copy(subpath['rectangle']) geo = copy(subpath['rectangle'])
geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles) geo = LineString(geo).buffer((float(applied_size) / 2), resolution=self.step_per_circles)
@ -771,16 +898,53 @@ class ToolPDF(FlatCAMTool):
# we finished painting and also closed the path if it was the case # we finished painting and also closed the path if it was the case
close_subpath = True close_subpath = True
# store the found geometry for stroking the path
found_aperture = None
if apertures_dict:
for apid in apertures_dict:
# if we already have an aperture with the current size (rounded to 5 decimals)
if apertures_dict[apid]['size'] == round(applied_size, 5):
found_aperture = apid
break
if found_aperture:
apertures_dict[copy(found_aperture)]['solid_geometry'] += path_geo
found_aperture = None
else:
if str(aperture) in apertures_dict.keys():
aperture += 1
apertures_dict[str(aperture)] = {}
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
apertures_dict[str(aperture)]['type'] = 'C'
apertures_dict[str(aperture)]['solid_geometry'] = []
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
else:
apertures_dict[str(aperture)] = {}
apertures_dict[str(aperture)]['size'] = round(applied_size, 5)
apertures_dict[str(aperture)]['type'] = 'C'
apertures_dict[str(aperture)]['solid_geometry'] = []
apertures_dict[str(aperture)]['solid_geometry'] += path_geo
# store the found geometry for filling the path
try: try:
apertures_dict['0']['solid_geometry'] += path_geo apertures_dict['0']['solid_geometry'] += fill_geo
except KeyError: except KeyError:
# in case there is no stroke width yet therefore no aperture # in case there is no stroke width yet therefore no aperture
apertures_dict['0'] = {} apertures_dict['0'] = {}
apertures_dict['0']['size'] = applied_size apertures_dict['0']['size'] = round(applied_size, 5)
apertures_dict['0']['type'] = 'C' apertures_dict['0']['type'] = 'C'
apertures_dict['0']['solid_geometry'] = [] apertures_dict['0']['solid_geometry'] = []
apertures_dict['0']['solid_geometry'] += path_geo apertures_dict['0']['solid_geometry'] += fill_geo
continue continue
# tidy up. copy the current aperture dict to the object dict but only if it is not empty
if apertures_dict:
object_dict[object_nr] = deepcopy(apertures_dict)
if clear_apertures_dict['0']['solid_geometry']:
object_dict[0] = deepcopy(clear_apertures_dict)
return object_dict return object_dict
def bezier_to_points(self, start, c1, c2, stop): def bezier_to_points(self, start, c1, c2, stop):