From 3a6b217abc80bb1377382081cdd873513175a15a Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 08:29:30 -0400 Subject: [PATCH 001/100] Autopep8 format, author & version update Ran Autopep8 on all files in repo, they are otherwise untouched with one exception: In the __init__ file, I had to manually rearrange the import statements to avoid triggering a circular import error. This will be addressed in a future commit. Updated authors in bl_info to: 'Vilem Novak & Contributors' - this can be changed as required Updated version to: 1.0.5 --- scripts/addons/cam/__init__.py | 241 +- scripts/addons/cam/async_op.py | 52 +- scripts/addons/cam/autoupdate.py | 92 +- scripts/addons/cam/basrelief.py | 2155 +++++++++-------- scripts/addons/cam/bridges.py | 3 +- scripts/addons/cam/chunk.py | 286 +-- scripts/addons/cam/collision.py | 68 +- scripts/addons/cam/constants.py | 2 +- scripts/addons/cam/curvecamcreate.py | 168 +- scripts/addons/cam/curvecamequation.py | 78 +- scripts/addons/cam/curvecamtools.py | 30 +- scripts/addons/cam/exception.py | 2 +- scripts/addons/cam/gcodeimportparser.py | 14 +- scripts/addons/cam/gcodepath.py | 48 +- scripts/addons/cam/image_utils.py | 185 +- scripts/addons/cam/involute_gear.py | 81 +- scripts/addons/cam/joinery.py | 25 +- scripts/addons/cam/numba_wrapper.py | 7 +- scripts/addons/cam/ops.py | 97 +- scripts/addons/cam/pack.py | 3 +- scripts/addons/cam/parametric.py | 8 +- scripts/addons/cam/pattern.py | 14 +- scripts/addons/cam/polygon_utils_cam.py | 3 +- scripts/addons/cam/puzzle_joinery.py | 3 +- scripts/addons/cam/simple.py | 5 +- scripts/addons/cam/simulation.py | 37 +- scripts/addons/cam/slice.py | 5 +- scripts/addons/cam/strategy.py | 52 +- scripts/addons/cam/testing.py | 5 +- scripts/addons/cam/tests/gcode_generator.py | 1 - scripts/addons/cam/tests/install_addon.py | 49 +- scripts/addons/cam/tests/test_suite.py | 14 +- scripts/addons/cam/ui.py | 34 +- scripts/addons/cam/ui_panels/area.py | 21 +- scripts/addons/cam/ui_panels/buttons_panel.py | 2 + scripts/addons/cam/ui_panels/chains.py | 10 +- scripts/addons/cam/ui_panels/cutter.py | 53 +- scripts/addons/cam/ui_panels/feedrate.py | 16 +- scripts/addons/cam/ui_panels/gcode.py | 15 +- scripts/addons/cam/ui_panels/info.py | 26 +- scripts/addons/cam/ui_panels/interface.py | 4 +- scripts/addons/cam/ui_panels/machine.py | 41 +- scripts/addons/cam/ui_panels/material.py | 13 +- scripts/addons/cam/ui_panels/movement.py | 128 +- scripts/addons/cam/ui_panels/op_properties.py | 68 +- scripts/addons/cam/ui_panels/operations.py | 39 +- scripts/addons/cam/ui_panels/optimisation.py | 26 +- scripts/addons/cam/ui_panels/slice.py | 1 - scripts/addons/cam/utils.py | 111 +- scripts/addons/cam/version.py | 2 +- scripts/addons/cam/voronoi.py | 17 +- 51 files changed, 2395 insertions(+), 2065 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index bc54052ce..a8c172b17 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -42,27 +42,27 @@ # pip install required python stuff subprocess.check_call([sys.executable, "-m", "ensurepip"]) subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", "shapely","Equation","opencamlib"]) + subprocess.check_call([sys.executable, "-m", "pip", "install", + "shapely", "Equation", "opencamlib"]) # install numba if available for this platform, ignore failure subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) - -from bpy.app.handlers import persistent -from bpy.props import * -from bpy.types import Menu, Operator, UIList, AddonPreferences -from bpy_extras.object_utils import object_data_add from cam import ui, ops, curvecamtools, curvecamequation, curvecamcreate, utils, simple, \ polygon_utils_cam, autoupdate, basrelief # , post_processors from mathutils import * from shapely import geometry as sgeometry +from bpy_extras.object_utils import object_data_add +from bpy.types import Menu, Operator, UIList, AddonPreferences +from bpy.props import * +from bpy.app.handlers import persistent -from cam.ui import * from cam.version import __version__ +from cam.ui import * bl_info = { "name": "CAM - gcode generation tools", - "author": "Vilem Novak", - "version":(1,0,4), + "author": "Vilem Novak & Contributors", + "version": (1, 0, 5), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", @@ -75,8 +75,9 @@ was_hidden_dict = {} + def updateMachine(self, context): - global _IS_LOADING_DEFAULTS + global _IS_LOADING_DEFAULTS print('update machine ') if not _IS_LOADING_DEFAULTS: utils.addMachineAreaObject() @@ -125,7 +126,6 @@ def updateOperation(self, context): print(e) - class CamAddonPreferences(AddonPreferences): # this must match the addon name, use '__package__' # when defining this in a submodule of a python package. @@ -153,7 +153,6 @@ class CamAddonPreferences(AddonPreferences): default="" ) - just_updated: BoolProperty( name="Set to true on update or initial install", default=True @@ -164,8 +163,6 @@ class CamAddonPreferences(AddonPreferences): default="" ) - - default_interface_level: bpy.props.EnumProperty( name="Interface level in new file", description="Choose visible options", @@ -189,19 +186,22 @@ def draw(self, context): layout.prop(self, "update_source") layout.label(text="Choose a preset update source") - UPDATE_SOURCES=[("https://github.com/vilemduha/blendercam", "Stable", "Stable releases (github.com/vilemduja/blendercam)"), - ("https://github.com/pppalain/blendercam", "Unstable", "Unstable releases (github.com/pppalain/blendercam)"), - # comments for searching in github actions release script to automatically set this repo - # if required - ## REPO ON NEXT LINE - ("https://api.github.com/repos/pppalain/blendercam/commits","Direct from git (may not work)","Get from git commits directly"), - ## REPO ON PREV LINE - ("","None","Don't do auto update"), - ] - grid=layout.grid_flow(align=True) - for (url,short,long) in UPDATE_SOURCES: - op=grid.operator("render.cam_set_update_source",text=short) - op.new_source=url + UPDATE_SOURCES = [("https://github.com/vilemduha/blendercam", "Stable", "Stable releases (github.com/vilemduja/blendercam)"), + ("https://github.com/pppalain/blendercam", "Unstable", + "Unstable releases (github.com/pppalain/blendercam)"), + # comments for searching in github actions release script to automatically set this repo + # if required + # REPO ON NEXT LINE + ("https://api.github.com/repos/pppalain/blendercam/commits", + "Direct from git (may not work)", "Get from git commits directly"), + # REPO ON PREV LINE + ("", "None", "Don't do auto update"), + ] + grid = layout.grid_flow(align=True) + for (url, short, long) in UPDATE_SOURCES: + op = grid.operator("render.cam_set_update_source", text=short) + op.new_source = url + class machineSettings(bpy.types.PropertyGroup): """stores all data for machines""" @@ -209,7 +209,8 @@ class machineSettings(bpy.types.PropertyGroup): post_processor: EnumProperty(name='Post processor', items=(('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), ('MACH3', 'Mach3', 'default mach3'), - ('EMC', 'LinuxCNC - EMC2', 'Linux based CNC control software - formally EMC2'), + ('EMC', 'LinuxCNC - EMC2', + 'Linux based CNC control software - formally EMC2'), ('FADAL', 'Fadal', 'Fadal VMC'), ('GRBL', 'grbl', 'optimized gcode for grbl firmware on Arduino with cnc shield'), @@ -324,7 +325,8 @@ class PackObjectsSettings(bpy.types.PropertyGroup): tolerance: FloatProperty(name="Placement Tolerance", description="Tolerance for placement: smaller value slower placemant", min=0.001, max=0.02, default=0.005, precision=cam.constants.PRECISION, unit="LENGTH") - rotate: bpy.props.BoolProperty(name="enable rotation", description="Enable rotation of elements", default=True) + rotate: bpy.props.BoolProperty( + name="enable rotation", description="Enable rotation of elements", default=True) rotate_angle: FloatProperty(name="Placement Angle rotation step", description="bigger rotation angle,faster placemant", default=0.19635 * 4, min=math.pi/180, @@ -337,9 +339,11 @@ class SliceObjectsSettings(bpy.types.PropertyGroup): slice_distance: FloatProperty(name="Slicing distance", description="slices distance in z, should be most often thickness of plywood sheet.", min=0.001, max=10, default=0.005, precision=cam.constants.PRECISION, unit="LENGTH") - slice_above0: bpy.props.BoolProperty(name="Slice above 0", description="only slice model above 0", default=False) + slice_above0: bpy.props.BoolProperty( + name="Slice above 0", description="only slice model above 0", default=False) slice_3d: bpy.props.BoolProperty(name="3d slice", description="for 3d carving", default=False) - indexes: bpy.props.BoolProperty(name="add indexes", description="adds index text of layer + index", default=True) + indexes: bpy.props.BoolProperty( + name="add indexes", description="adds index text of layer + index", default=True) class import_settings(bpy.types.PropertyGroup): @@ -353,8 +357,9 @@ class import_settings(bpy.types.PropertyGroup): max_segment_size: FloatProperty(name="", description="Only Segments bigger then this value get subdivided", default=0.001, min=0.0001, max=1.0, unit="LENGTH") -def isValid(o,context): - valid=True + +def isValid(o, context): + valid = True if o.geometry_source == 'OBJECT': if o.object_name not in bpy.data.objects: valid = False @@ -369,11 +374,12 @@ def isValid(o,context): valid = False return valid + def operationValid(self, context): - scene=context.scene + scene = context.scene o = scene.cam_operations[scene.cam_active_operation] o.changed = True - o.valid = isValid(o,context) + o.valid = isValid(o, context) invalidmsg = "Invalid source object for operation.\n" if o.valid: o.info.warnings = "" @@ -386,20 +392,21 @@ def operationValid(self, context): o.update_zbufferimage_tag = True print('validity ') -def isChainValid(chain,context): + +def isChainValid(chain, context): s = context.scene - if len(chain.operations)==0: - return (False,"") + if len(chain.operations) == 0: + return (False, "") for cho in chain.operations: found_op = None for so in s.cam_operations: if so.name == cho.name: - found_op= so + found_op = so if found_op == None: - return (False,f"Couldn't find operation {cho.name}") - if cam.isValid(found_op,context) is False: - return (False,f"Operation {found_op.name} is not valid") - return (True,"") + return (False, f"Couldn't find operation {cho.name}") + if cam.isValid(found_op, context) is False: + return (False, f"Operation {found_op.name} is not valid") + return (True, "") def updateOperationValid(self, context): @@ -462,6 +469,7 @@ def updateStrategy(o, context): def updateCutout(o, context): pass + def updateExact(o, context): print('update exact ') o.changed = True @@ -484,6 +492,7 @@ def updateOpencamlib(o, context): o.optimisation.use_opencamlib = False print('Current operation cannot use opencamlib') + def updateBridges(o, context): print('update bridges ') o.changed = True @@ -589,10 +598,12 @@ class camOperation(bpy.types.PropertyGroup): curve_object1: bpy.props.StringProperty(name='Curve target', description='curve which will serve as attractor for the cutter when the cutter follows the curve', update=operationValid) - source_image_name: bpy.props.StringProperty(name='image_source', description='image source', update=operationValid) + source_image_name: bpy.props.StringProperty( + name='image_source', description='image source', update=operationValid) geometry_source: EnumProperty(name='Source of data', items=( - ('OBJECT', 'object', 'a'), ('COLLECTION', 'Collection of objects', 'a'), + ('OBJECT', 'object', 'a'), ('COLLECTION', + 'Collection of objects', 'a'), ('IMAGE', 'Image', 'a')), description='Geometry source', default='OBJECT', update=updateOperationValid) @@ -640,7 +651,8 @@ class camOperation(bpy.types.PropertyGroup): update=updateStrategy) strategy5axis: EnumProperty(name='Strategy', items=( - ('INDEXED', 'Indexed 3-axis', 'all 3 axis strategies, just rotated by 4+5th axes'), + ('INDEXED', 'Indexed 3-axis', + 'all 3 axis strategies, just rotated by 4+5th axes'), ), description='5 axis Strategy', default='INDEXED', @@ -684,13 +696,14 @@ class camOperation(bpy.types.PropertyGroup): # pocket options pocket_option: EnumProperty(name='Start Position', items=( ('INSIDE', 'Inside', 'a'), ('OUTSIDE', 'Outside', 'a')), - description='Pocket starting position', default='INSIDE', update=updateRest) + description='Pocket starting position', default='INSIDE', update=updateRest) pocketToCurve: bpy.props.BoolProperty(name="Pocket to curve", description="generates a curve instead of a path", default=False, update=updateRest) # Cutout cut_type: EnumProperty(name='Cut', - items=(('OUTSIDE', 'Outside', 'a'), ('INSIDE', 'Inside', 'a'), ('ONLINE', 'On line', 'a')), + items=(('OUTSIDE', 'Outside', 'a'), ('INSIDE', + 'Inside', 'a'), ('ONLINE', 'On line', 'a')), description='Type of cutter used', default='OUTSIDE', update=updateRest) outlines_count: bpy.props.IntProperty(name="Outlines count", description="Outlines count", default=1, min=1, max=32, update=updateCutout) @@ -720,7 +733,8 @@ class camOperation(bpy.types.PropertyGroup): max=0.035, default=0.005, unit="LENGTH", precision=cam.constants.PRECISION, update=updateOffsetImage) - cutter_description: StringProperty(name="Tool Description", default="", update=updateOffsetImage) + cutter_description: StringProperty( + name="Tool Description", default="", update=updateOffsetImage) Laser_on: bpy.props.StringProperty(name="Laser ON string", default="M68 E0 Q100") Laser_off: bpy.props.StringProperty(name="Laser OFF string", default="M68 E0 Q0") @@ -758,7 +772,8 @@ class camOperation(bpy.types.PropertyGroup): subtype="ANGLE", unit="ROTATION", update=updateRotation) enable_A: bpy.props.BoolProperty(name="Enable A axis", description="Rotate A axis", default=False, update=updateRotation) - A_along_x: bpy.props.BoolProperty(name="A Along X ", description="A Parallel to X", default=True, update=updateRest) + A_along_x: bpy.props.BoolProperty( + name="A Along X ", description="A Parallel to X", default=True, update=updateRest) rotation_B: bpy.props.FloatProperty(name="B axis angle", description="Rotate B axis\nto specified angle", default=0, min=-360, max=360, precision=0, @@ -772,9 +787,10 @@ class camOperation(bpy.types.PropertyGroup): # drill only drill_type: EnumProperty(name='Holes on', items=( - ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), + ('MIDDLE_SYMETRIC', 'Middle of symetric curves', + 'a'), ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), ('ALL_POINTS', 'All points in curve', 'a')), description='Strategy to detect holes to drill', - default='MIDDLE_SYMETRIC', update=updateRest) + default='MIDDLE_SYMETRIC', update=updateRest) # waterline only slice_detail: bpy.props.FloatProperty(name="Distance betwen slices", default=0.001, min=0.00001, max=32, precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) @@ -802,20 +818,20 @@ class camOperation(bpy.types.PropertyGroup): # helix_angle: bpy.props.FloatProperty(name="Helix ramp angle", default=3*math.pi/180, min=0.00001, max=math.pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) minz: bpy.props.FloatProperty(name="Operation depth end", - default=-0.01, min=-3, max=3, precision=cam.constants.PRECISION, - unit="LENGTH", - update=updateRest) + default=-0.01, min=-3, max=3, precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest) minz_from: bpy.props.EnumProperty(name='Set max depth from', - description = 'Set maximum operation depth', - items=( - ('OBJECT', 'Object', 'Set max operation depth from Object'), - ('MATERIAL', 'Material', 'Set max operation depth from Material'), - ('CUSTOM', 'Custom', 'Custom max depth'), - ), - default='OBJECT', - update=updateRest - ) + description='Set maximum operation depth', + items=( + ('OBJECT', 'Object', 'Set max operation depth from Object'), + ('MATERIAL', 'Material', 'Set max operation depth from Material'), + ('CUSTOM', 'Custom', 'Custom max depth'), + ), + default='OBJECT', + update=updateRest + ) start_type: bpy.props.EnumProperty(name='Start type', items=( @@ -832,9 +848,8 @@ class camOperation(bpy.types.PropertyGroup): update=updateRest) # EXPERIMENTAL first_down: bpy.props.BoolProperty(name="First down", - description="First go down on a contour, then go to the next one", - default=False, update=cam.utils.update_operation) - + description="First go down on a contour, then go to the next one", + default=False, update=cam.utils.update_operation) ####################################################### # Image related @@ -866,7 +881,6 @@ class camOperation(bpy.types.PropertyGroup): # Toolpath and area related ##################################################### - ambient_behaviour: EnumProperty(name='Ambient', items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), description='handling ambient surfaces', default='ALL', update=updateZbufferImage) @@ -972,20 +986,15 @@ class camOperation(bpy.types.PropertyGroup): # material settings - - - - ############################################################################## # MATERIAL SETTINGS min: bpy.props.FloatVectorProperty( name='Operation minimum', default=(0, 0, 0), unit='LENGTH', precision=cam.constants.PRECISION, - subtype="XYZ") + subtype="XYZ") max: bpy.props.FloatVectorProperty(name='Operation maximum', default=(0, 0, 0), unit='LENGTH', precision=cam.constants.PRECISION, subtype="XYZ") - # g-code options for operation output_header: BoolProperty(name="output g-code header", description="output user defined g-code command header at start of operation", @@ -996,16 +1005,16 @@ class camOperation(bpy.types.PropertyGroup): default="G53 G0") enable_dust: BoolProperty(name="Dust collector", - description="output user defined g-code command header at start of operation", - default=False) + description="output user defined g-code command header at start of operation", + default=False) gcode_start_dust_cmd: StringProperty(name="Start dust collector", - description="commands to start dust collection. Use ; for line breaks", - default="M100") + description="commands to start dust collection. Use ; for line breaks", + default="M100") gcode_stop_dust_cmd: StringProperty(name="Stop dust collector", - description="command to stop dust collection. Use ; for line breaks", - default="M101") + description="command to stop dust collection. Use ; for line breaks", + default="M101") enable_hold: BoolProperty(name="Hold down", description="output hold down command at start of operation", @@ -1068,8 +1077,10 @@ class camOperation(bpy.types.PropertyGroup): update_bullet_collision_tag: bpy.props.BoolProperty(name="mark bullet collisionworld for update", description="mark for update", default=True) - valid: bpy.props.BoolProperty(name="Valid", description="True if operation is ok for calculation", default=True); - changedata: bpy.props.StringProperty(name='changedata', description='change data for checking if stuff changed.') + valid: bpy.props.BoolProperty( + name="Valid", description="True if operation is ok for calculation", default=True) + changedata: bpy.props.StringProperty( + name='changedata', description='change data for checking if stuff changed.') # process related data @@ -1078,20 +1089,26 @@ class camOperation(bpy.types.PropertyGroup): outtext: bpy.props.StringProperty(name='outtext', description='outtext', default='') -class opReference(bpy.types.PropertyGroup): # this type is defined just to hold reference to operations for chains +# this type is defined just to hold reference to operations for chains +class opReference(bpy.types.PropertyGroup): name: bpy.props.StringProperty(name="Operation name", default="Operation") computing = False # for UiList display -class camChain(bpy.types.PropertyGroup): # chain is just a set of operations which get connected on export into 1 file. - index: bpy.props.IntProperty(name="index", description="index in the hard-defined camChains", default=-1) +# chain is just a set of operations which get connected on export into 1 file. +class camChain(bpy.types.PropertyGroup): + index: bpy.props.IntProperty( + name="index", description="index in the hard-defined camChains", default=-1) active_operation: bpy.props.IntProperty(name="active operation", description="active operation in chain", default=-1) name: bpy.props.StringProperty(name="Chain Name", default="Chain") filename: bpy.props.StringProperty(name="File name", default="Chain") # filename of - valid: bpy.props.BoolProperty(name="Valid", description="True if whole chain is ok for calculation", default=True); + valid: bpy.props.BoolProperty( + name="Valid", description="True if whole chain is ok for calculation", default=True) computing: bpy.props.BoolProperty(name="Computing right now", description="", default=False) - operations: bpy.props.CollectionProperty(type=opReference) # this is to hold just operation names. + # this is to hold just operation names. + operations: bpy.props.CollectionProperty(type=opReference) + class CAM_CUTTER_MT_presets(Menu): bl_label = "Cutter presets" @@ -1099,6 +1116,7 @@ class CAM_CUTTER_MT_presets(Menu): preset_operator = "script.execute_preset" draw = Menu.draw_preset + class CAM_MACHINE_MT_presets(Menu): bl_label = "Machine presets" preset_subdir = "cam_machines" @@ -1106,15 +1124,16 @@ class CAM_MACHINE_MT_presets(Menu): draw = Menu.draw_preset @classmethod - def post_cb(cls,context): + def post_cb(cls, context): name = cls.bl_label filepath = bpy.utils.preset_find(name, - cls.preset_subdir, - display_name=True, - ext=".py") - context.preferences.addons['cam'].preferences.default_machine_preset=filepath + cls.preset_subdir, + display_name=True, + ext=".py") + context.preferences.addons['cam'].preferences.default_machine_preset = filepath bpy.ops.wm.save_userpref() + class AddPresetCamCutter(bl_operators.presets.AddPresetBase, Operator): """Add a Cutter Preset""" bl_idname = "render.cam_preset_cutter_add" @@ -1151,7 +1170,8 @@ class AddPresetCamOperation(bl_operators.presets.AddPresetBase, Operator): bl_label = "Add Operation Preset" preset_menu = "CAM_OPERATION_MT_presets" - preset_defines = ["o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]"] + preset_defines = [ + "o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]"] preset_values = ['o.use_layers', 'o.info.duration', 'o.info.chipload', 'o.material.estimate_from_model', 'o.movement.stay_low', 'o.carve_depth', 'o.dist_along_paths', 'o.source_image_crop_end_x', 'o.source_image_crop_end_y', 'o.material.size', @@ -1216,7 +1236,9 @@ class BLENDERCAM_ENGINE(bpy.types.RenderEngine): bl_idname = 'BLENDERCAM_RENDER' bl_label = "Cam" -_IS_LOADING_DEFAULTS=False + +_IS_LOADING_DEFAULTS = False + @bpy.app.handlers.persistent def check_operations_on_load(context): @@ -1228,14 +1250,16 @@ def check_operations_on_load(context): o.computing = False # set interface level to previously used level for a new file if not bpy.data.filepath: - _IS_LOADING_DEFAULTS=True + _IS_LOADING_DEFAULTS = True s.interface.level = bpy.context.preferences.addons['cam'].preferences.default_interface_level - machine_preset=bpy.context.preferences.addons['cam'].preferences.machine_preset=bpy.context.preferences.addons['cam'].preferences.default_machine_preset - if len(machine_preset)>0: - print("Loading preset:",machine_preset) + machine_preset = bpy.context.preferences.addons[ + 'cam'].preferences.machine_preset = bpy.context.preferences.addons['cam'].preferences.default_machine_preset + if len(machine_preset) > 0: + print("Loading preset:", machine_preset) # load last used machine preset - bpy.ops.script.execute_preset(filepath=machine_preset,menu_idname="CAM_MACHINE_MT_presets") - _IS_LOADING_DEFAULTS=False + bpy.ops.script.execute_preset(filepath=machine_preset, + menu_idname="CAM_MACHINE_MT_presets") + _IS_LOADING_DEFAULTS = False # check for updated version of the plugin bpy.ops.render.cam_check_updates() # copy presets if not there yet @@ -1243,16 +1267,16 @@ def check_operations_on_load(context): preset_source_path = Path(__file__).parent / 'presets' preset_target_path = Path(bpy.utils.script_path_user()) / 'presets' - def copy_if_not_exists(src,dst): - if Path(dst).exists()==False: - shutil.copy2(src,dst) - shutil.copytree(preset_source_path,preset_target_path,copy_function=copy_if_not_exists,dirs_exist_ok=True) + def copy_if_not_exists(src, dst): + if Path(dst).exists() == False: + shutil.copy2(src, dst) + shutil.copytree(preset_source_path, preset_target_path, + copy_function=copy_if_not_exists, dirs_exist_ok=True) - bpy.context.preferences.addons['cam'].preferences.just_updated=False + bpy.context.preferences.addons['cam'].preferences.just_updated = False bpy.ops.wm.save_userpref() - def get_panels(): # convenience function for bot register and unregister functions # types = bpy.types return ( @@ -1404,7 +1428,7 @@ def compatible_panels(): t.MATERIAL_PT_strand, t.MATERIAL_PT_options, t.MATERIAL_PT_shadow, - + t.MATERIAL_PT_transp_game, t.MATERIAL_PT_volume_density, t.MATERIAL_PT_volume_shading, @@ -1571,7 +1595,8 @@ def register(): s = bpy.types.Scene s.cam_chains = bpy.props.CollectionProperty(type=camChain) - s.cam_active_chain = bpy.props.IntProperty(name="CAM Active Chain", description="The selected chain") + s.cam_active_chain = bpy.props.IntProperty( + name="CAM Active Chain", description="The selected chain") s.cam_operations = bpy.props.CollectionProperty(type=camOperation) diff --git a/scripts/addons/cam/async_op.py b/scripts/addons/cam/async_op.py index c2239adef..6e212fa51 100644 --- a/scripts/addons/cam/async_op.py +++ b/scripts/addons/cam/async_op.py @@ -3,24 +3,27 @@ import types + @types.coroutine -def progress_async(text, n=None,value_type='%'): +def progress_async(text, n=None, value_type='%'): """function for reporting during the script, works for background operations in the header.""" - throw_exception=yield ('progress',{'text':text,'n':n,"value_type":value_type}) + throw_exception = yield ('progress', {'text': text, 'n': n, "value_type": value_type}) if throw_exception is not None: raise throw_exception + class AsyncCancelledException(Exception): pass + class AsyncOperatorMixin: def __init__(self): - self.timer=None - self.coroutine=None - self._is_cancelled=False + self.timer = None + self.coroutine = None + self._is_cancelled = False - def modal(self,context,event): + def modal(self, context, event): if bpy.app.background: return {'PASS_THROUGH'} @@ -35,10 +38,10 @@ def modal(self,context,event): except Exception as e: context.window_manager.event_timer_remove(self.timer) bpy.context.workspace.status_text_set(None) - self.report({'ERROR'},str(e)) + self.report({'ERROR'}, str(e)) return {'FINISHED'} elif event.type == 'ESC': - self._is_cancelled=True + self._is_cancelled = True self.tick(context) context.window_manager.event_timer_remove(self.timer) bpy.context.workspace.status_text_set(None) @@ -48,7 +51,7 @@ def modal(self,context,event): else: return {'PASS_THROUGH'} - def show_progress(self,context,text, n,value_type): + def show_progress(self, context, text, n, value_type): if n is not None: progress_text = f"{text}: {n:.2f}{value_type}" else: @@ -57,45 +60,46 @@ def show_progress(self,context,text, n,value_type): sys.stdout.write(f"Progress: {progress_text}\n") sys.stdout.flush() - def tick(self,context): - if self.coroutine==None: - self.coroutine=self.execute_async(context) + def tick(self, context): + if self.coroutine == None: + self.coroutine = self.execute_async(context) try: if self._is_cancelled: - (msg,args)=self.coroutine.send(AsyncCancelledException("Cancelled with ESC key")) + (msg, args) = self.coroutine.send(AsyncCancelledException("Cancelled with ESC key")) raise StopIteration else: - (msg,args)=self.coroutine.send(None) - if msg=='progress': - self.show_progress(context,**args) + (msg, args) = self.coroutine.send(None) + if msg == 'progress': + self.show_progress(context, **args) else: sys.stdout.write(f"{msg},{args}") return True except StopIteration: return False except Exception as e: - print("Exception thrown in tick:",e) + print("Exception thrown in tick:", e) def execute(self, context): if bpy.app.background: # running in background - don't run as modal, # otherwise tests all fail - while self.tick(context)==True: + while self.tick(context) == True: pass return {'FINISHED'} else: - self.timer=context.window_manager.event_timer_add(.001, window=context.window) + self.timer = context.window_manager.event_timer_add(.001, window=context.window) context.window_manager.modal_handler_add(self) return {'RUNNING_MODAL'} -class AsyncTestOperator(bpy.types.Operator,AsyncOperatorMixin): + +class AsyncTestOperator(bpy.types.Operator, AsyncOperatorMixin): """test async operator""" bl_idname = "object.cam_async_test_operator" bl_label = "Test operator for async stuff" - bl_options = {'REGISTER', 'UNDO','BLOCKING'} + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} - async def execute_async(self,context): + async def execute_async(self, context): for x in range(100): - await progress_async("Async test:",x) + await progress_async("Async test:", x) -#bpy.utils.register_class(AsyncTestOperator) \ No newline at end of file +# bpy.utils.register_class(AsyncTestOperator) diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 9b9361b2f..25e0a2c0f 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -11,6 +11,7 @@ import sys import calendar + class UpdateChecker(bpy.types.Operator): """check for updates""" bl_idname = "render.cam_check_updates" @@ -21,31 +22,31 @@ def execute(self, context): if bpy.app.background: return {"FINISHED"} last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check - today=date.today().toordinal() + today = date.today().toordinal() update_source = bpy.context.preferences.addons['cam'].preferences.update_source - match = re.match(r"https://github.com/([^/]+/[^/]+)",update_source) + match = re.match(r"https://github.com/([^/]+/[^/]+)", update_source) if match: update_source = f"https://api.github.com/repos/{match.group(1)}/releases" - + print(f"update check: {update_source}") - if update_source=="None" or len(update_source)==0: + if update_source == "None" or len(update_source) == 0: return {'FINISHED'} bpy.context.preferences.addons['cam'].preferences.new_version_available = "" bpy.ops.wm.save_userpref() # get list of releases from github release if update_source.endswith("/releases"): - with urlopen(update_source,timeout=2.0) as response: + with urlopen(update_source, timeout=2.0) as response: body = response.read().decode("UTF-8") # find the tag name - release_list=json.loads(body) + release_list = json.loads(body) if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] print(f"Found release: {tag}") - match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)",tag) + match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: - version_num = tuple(map(int,match.groups())) + version_num = tuple(map(int, match.groups())) print(f"Found version: {version_num}") bpy.context.preferences.addons['cam'].preferences.last_update_check = today @@ -54,17 +55,18 @@ def execute(self, context): [str(x) for x in version_num]) bpy.ops.wm.save_userpref() elif update_source.endswith("/commits"): - with urlopen(update_source+"?per_page=1",timeout=2) as response: + with urlopen(update_source+"?per_page=1", timeout=2) as response: body = response.read().decode("UTF-8") # find the tag name - commit_list=json.loads(body) - commit_sha=commit_list[0]['sha'] - commit_date=commit_list[0]['commit']['author']['date'] + commit_list = json.loads(body) + commit_sha = commit_list[0]['sha'] + commit_date = commit_list[0]['commit']['author']['date'] if bpy.context.preferences.addons['cam'].preferences.last_commit_hash != commit_sha: - bpy.context.preferences.addons['cam'].preferences.new_version_available=commit_date + bpy.context.preferences.addons['cam'].preferences.new_version_available = commit_date bpy.ops.wm.save_userpref() return {'FINISHED'} + class Updater(bpy.types.Operator): """update to newer version if possible """ bl_idname = "render.cam_update_now" @@ -74,27 +76,27 @@ class Updater(bpy.types.Operator): def execute(self, context): print("update check") last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check - today=date.today().toordinal() + today = date.today().toordinal() update_source = bpy.context.preferences.addons['cam'].preferences.update_source - if update_source=="None" or len(update_source)==0: + if update_source == "None" or len(update_source) == 0: return {'FINISHED'} - match = re.match(r"https://github.com/([^/]+/[^/]+)",update_source) + match = re.match(r"https://github.com/([^/]+/[^/]+)", update_source) if match: update_source = f"https://api.github.com/repos/{match.group(1)}/releases" # get list of releases from github release if update_source.endswith("/releases"): - with urlopen(update_source,timeout=2) as response: + with urlopen(update_source, timeout=2) as response: body = response.read().decode("UTF-8") # find the tag name - release_list=json.loads(body) + release_list = json.loads(body) if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] print(f"Found release: {tag}") - match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)",tag) + match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: - version_num = tuple(map(int,match.groups())) + version_num = tuple(map(int, match.groups())) print(f"Found version: {version_num}") bpy.context.preferences.addons['cam'].preferences.last_update_check = today bpy.ops.wm.save_userpref() @@ -105,62 +107,64 @@ def execute(self, context): self.install_zip_from_url(zip_url) return {'FINISHED'} elif update_source.endswith("/commits"): - with urlopen(update_source+"?per_page=1",timeout=2) as response: + with urlopen(update_source+"?per_page=1", timeout=2) as response: body = response.read().decode("UTF-8") # find the tag name - commit_list=json.loads(body) - commit_sha=commit_list[0]['sha'] + commit_list = json.loads(body) + commit_sha = commit_list[0]['sha'] if bpy.context.preferences.addons['cam'].preferences.last_commit_hash != commit_sha: # get zipball from this commit - zip_url = update_source.replace("/commits",f"/zipball/{commit_sha}") + zip_url = update_source.replace("/commits", f"/zipball/{commit_sha}") self.install_zip_from_url(zip_url) bpy.context.preferences.addons['cam'].preferences.last_commit_hash = commit_sha bpy.ops.wm.save_userpref() return {'FINISHED'} - - def install_zip_from_url(self,zip_url): + + def install_zip_from_url(self, zip_url): with urlopen(zip_url) as zip_response: - zip_body=zip_response.read() - buffer= io.BytesIO(zip_body) - zf=zipfile.ZipFile(buffer,mode='r') - files=zf.infolist() + zip_body = zip_response.read() + buffer = io.BytesIO(zip_body) + zf = zipfile.ZipFile(buffer, mode='r') + files = zf.infolist() cam_addon_path = pathlib.Path(__file__).parent for fileinfo in files: - filename=fileinfo.filename + filename = fileinfo.filename if fileinfo.is_dir() == False: - path_pos=filename.replace("\\","/").find("/scripts/addons/cam/") - if path_pos!=-1: - relative_path=filename[path_pos+len("/scripts/addons/cam/"):] + path_pos = filename.replace("\\", "/").find("/scripts/addons/cam/") + if path_pos != -1: + relative_path = filename[path_pos+len("/scripts/addons/cam/"):] out_path = cam_addon_path / relative_path print(out_path) # check folder exists - out_path.parent.mkdir(parents=True,exist_ok=True) - with zf.open(filename,"r") as in_file, open(out_path,"wb") as out_file: - time_struct=(*fileinfo.date_time,0,0,0) - mtime=calendar.timegm(time_struct) + out_path.parent.mkdir(parents=True, exist_ok=True) + with zf.open(filename, "r") as in_file, open(out_path, "wb") as out_file: + time_struct = (*fileinfo.date_time, 0, 0, 0) + mtime = calendar.timegm(time_struct) out_file.write(in_file.read()) - os.utime(out_path,times=(mtime,mtime)) + os.utime(out_path, times=(mtime, mtime)) # TODO: check for newer times # TODO: what about if a file is deleted... # updated everything, now mark as updated and reload scripts - bpy.context.preferences.addons['cam'].preferences.just_updated=True + bpy.context.preferences.addons['cam'].preferences.just_updated = True bpy.context.preferences.addons['cam'].preferences.new_version_available = "" bpy.ops.wm.save_userpref() # unload ourself from python module system - delete_list=[] + delete_list = [] for m in sys.modules.keys(): - if m.startswith("cam.") or m=='cam': + if m.startswith("cam.") or m == 'cam': delete_list.append(m) for d in delete_list: del sys.modules[d] bpy.ops.script.reload() + class UpdateSourceOperator(bpy.types.Operator): bl_idname = "render.cam_set_update_source" bl_label = "Set blendercam update source" new_source: bpy.props.StringProperty(default='') - def execute(self,context): - bpy.context.preferences.addons['cam'].preferences.update_source=self.new_source + + def execute(self, context): + bpy.context.preferences.addons['cam'].preferences.update_source = self.new_source bpy.ops.wm.save_userpref() return {'FINISHED'} diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index 1a163b90d..d6551a807 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -1,4 +1,5 @@ -import bpy,time +import bpy +import time import numpy import math import re @@ -6,1149 +7,1183 @@ from bpy.props import * -##//////////////////////////////////////////////////////////////////// -#// Full Multigrid Algorithm for solving partial differential equations -#////////////////////////////////////////////////////////////////////// -#MODYF = 0 #/* 1 or 0 (1 is better) */ -#MINS = 16 #/* minimum size 4 6 or 100 */ +# //////////////////////////////////////////////////////////////////// +# // Full Multigrid Algorithm for solving partial differential equations +# ////////////////////////////////////////////////////////////////////// +# MODYF = 0 #/* 1 or 0 (1 is better) */ +# MINS = 16 #/* minimum size 4 6 or 100 */ -#SMOOTH_IT = 2 #/* minimum 1 */ -#V_CYCLE = 10 #/* number of v-cycles 2*/ +# SMOOTH_IT = 2 #/* minimum 1 */ +# V_CYCLE = 10 #/* number of v-cycles 2*/ #ITERATIONS = 5 -#// precision +# // precision EPS = 1.0e-32 -PRECISION=5 -NUMPYALG=False -#PLANAR_CONST=True +PRECISION = 5 +NUMPYALG = False +# PLANAR_CONST=True -def copy_compbuf_data(inbuf, outbuf): - outbuf[:]=inbuf[:] - -def restrictbuf( inbuf, outbuf ):#scale down array.... - - inx = inbuf.shape[0] - iny = inbuf.shape[1] - outx = outbuf.shape[0] - outy = outbuf.shape[1] +def copy_compbuf_data(inbuf, outbuf): + outbuf[:] = inbuf[:] - dx=inx/outx - dy=iny/outy - filterSize = 0.5 - xfiltersize=dx*filterSize +def restrictbuf(inbuf, outbuf): # scale down array.... - sy=dy/2-0.5 - if dx==2 and dy==2:#much simpler method - #if dx<2: - #restricted= - #num=restricted.shape[0]*restricted.shape[1] - outbuf[:]=(inbuf[::2,::2]+inbuf[1::2,::2]+inbuf[::2,1::2]+inbuf[1::2,1::2])/4.0 + inx = inbuf.shape[0] + iny = inbuf.shape[1] - elif NUMPYALG:#numpy method - yrange=numpy.arange(0,outy) - xrange=numpy.arange(0,outx) + outx = outbuf.shape[0] + outy = outbuf.shape[1] - w=0 - sx = dx/2-0.5 + dx = inx/outx + dy = iny/outy - sxrange=xrange*dx+sx - syrange=yrange*dy+sy + filterSize = 0.5 + xfiltersize = dx*filterSize - sxstartrange=numpy.array(numpy.ceil(sxrange-xfiltersize),dtype=int) - sxstartrange[sxstartrange<0]=0 - sxendrange=numpy.array(numpy.floor(sxrange+xfiltersize)+1,dtype=int) - sxendrange[sxendrange>inx]=inx + sy = dy/2-0.5 + if dx == 2 and dy == 2: # much simpler method + # if dx<2: + # restricted= + # num=restricted.shape[0]*restricted.shape[1] + outbuf[:] = (inbuf[::2, ::2]+inbuf[1::2, ::2]+inbuf[::2, 1::2]+inbuf[1::2, 1::2])/4.0 - systartrange=numpy.array(numpy.ceil(syrange-xfiltersize),dtype=int) - systartrange[systartrange<0]=0 - syendrange=numpy.array(numpy.floor(syrange+xfiltersize)+1,dtype=int) - syendrange[syendrange>iny]=iny - #np.arange(8*6*3).reshape((8, 6, 3)) - - indices=numpy.arange(outx*outy*2*3).reshape((2,outx*outy,3))#3is the maximum value...?pff. - - r=sxendrange-sxstartrange - - indices[0]=sxstartrange.repeat(outy) - - indices[1]=systartrange.repeat(outx).reshape(outx,outy).swapaxes(0,1).flatten() - - #systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) - #syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 - - outbuf.fill(0) - tempbuf=inbuf[indices[0],indices[1]] - tempbuf+=inbuf[indices[0]+1,indices[1]] - tempbuf+=inbuf[indices[0],indices[1]+1] - tempbuf+=inbuf[indices[0]+1,indices[1]+1] - tempbuf/=4.0 - outbuf[:]=tempbuf.reshape((outx,outy)) - #outbuf[:,:]=inbuf[]#inbuf[sxstartrange,systartrange] #+ inbuf[sxstartrange+1,systartrange] + inbuf[sxstartrange,systartrange+1] + inbuf[sxstartrange+1,systartrange+1])/4.0 - - - - else:#old method - for y in range(0,outy): - - sx = dx/2-0.5 - for x in range(0,outx): - pixVal = 0 - w = 0 + elif NUMPYALG: # numpy method + yrange = numpy.arange(0, outy) + xrange = numpy.arange(0, outx) - # - for ix in range(max( 0, ceil(sx-dx*filterSize)),min( floor( sx+dx*filterSize ), inx-1)+1): - for iy in range(max( 0, ceil( sy-dx*filterSize ) ),min( floor( sy+dx*filterSize), iny-1)+1): - pixVal += inbuf[ix,iy] - w += 1 - outbuf[x ,y] = pixVal/w + w = 0 + sx = dx/2-0.5 - sx+=dx - sy+=dy + sxrange = xrange*dx+sx + syrange = yrange*dy+sy -def prolongate( inbuf, outbuf ): + sxstartrange = numpy.array(numpy.ceil(sxrange-xfiltersize), dtype=int) + sxstartrange[sxstartrange < 0] = 0 + sxendrange = numpy.array(numpy.floor(sxrange+xfiltersize)+1, dtype=int) + sxendrange[sxendrange > inx] = inx - inx = inbuf.shape[0] - iny = inbuf.shape[1] + systartrange = numpy.array(numpy.ceil(syrange-xfiltersize), dtype=int) + systartrange[systartrange < 0] = 0 + syendrange = numpy.array(numpy.floor(syrange+xfiltersize)+1, dtype=int) + syendrange[syendrange > iny] = iny + #np.arange(8*6*3).reshape((8, 6, 3)) + + # 3is the maximum value...?pff. + indices = numpy.arange(outx*outy*2*3).reshape((2, outx*outy, 3)) + + r = sxendrange-sxstartrange + + indices[0] = sxstartrange.repeat(outy) + + indices[1] = systartrange.repeat(outx).reshape(outx, outy).swapaxes(0, 1).flatten() + + # systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) + # syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 + + outbuf.fill(0) + tempbuf = inbuf[indices[0], indices[1]] + tempbuf += inbuf[indices[0]+1, indices[1]] + tempbuf += inbuf[indices[0], indices[1]+1] + tempbuf += inbuf[indices[0]+1, indices[1]+1] + tempbuf /= 4.0 + outbuf[:] = tempbuf.reshape((outx, outy)) + # outbuf[:,:]=inbuf[]#inbuf[sxstartrange,systartrange] #+ inbuf[sxstartrange+1,systartrange] + inbuf[sxstartrange,systartrange+1] + inbuf[sxstartrange+1,systartrange+1])/4.0 + + else: # old method + for y in range(0, outy): + + sx = dx/2-0.5 + for x in range(0, outx): + pixVal = 0 + w = 0 - outx = outbuf.shape[0] - outy = outbuf.shape[1] + # + for ix in range(max(0, ceil(sx-dx*filterSize)), min(floor(sx+dx*filterSize), inx-1)+1): + for iy in range(max(0, ceil(sy-dx*filterSize)), min(floor(sy+dx*filterSize), iny-1)+1): + pixVal += inbuf[ix, iy] + w += 1 + outbuf[x, y] = pixVal/w - dx=inx/outx - dy=iny/outy + sx += dx + sy += dy - filterSize = 1 - xfiltersize=dx*filterSize - #outx[:]= - #outbuf.put(inbuf.repeat(4)) - if dx==0.5 and dy==0.5: - outbuf[::2,::2]=inbuf - outbuf[1::2,::2]=inbuf - outbuf[::2,1::2]=inbuf - outbuf[1::2,1::2]=inbuf - #x=inbuf::.flatten().repeat(2) - elif NUMPYALG:#numpy method - sy=-dy/2 - sx=-dx/2 - xrange=numpy.arange(0,outx) - yrange=numpy.arange(0,outy) +def prolongate(inbuf, outbuf): - sxrange=xrange*dx+sx - syrange=yrange*dy+sy - - sxstartrange=numpy.array(numpy.ceil(sxrange-xfiltersize),dtype=int) - sxstartrange[sxstartrange<0]=0 - sxendrange=numpy.array(numpy.floor(sxrange+xfiltersize)+1,dtype=int) - sxendrange[sxendrange>=inx]=inx-1 - systartrange=numpy.array(numpy.ceil(syrange-xfiltersize),dtype=int) - systartrange[systartrange<0]=0 - syendrange=numpy.array(numpy.floor(syrange+xfiltersize)+1,dtype=int) - syendrange[syendrange>=iny]=iny-1 + inx = inbuf.shape[0] + iny = inbuf.shape[1] - indices=numpy.arange(outx*outy*2).reshape((2,outx*outy)) - indices[0]=sxstartrange.repeat(outy) - indices[1]=systartrange.repeat(outx).reshape(outx,outy).swapaxes(0,1).flatten() + outx = outbuf.shape[0] + outy = outbuf.shape[1] - #systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) - #syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 - #outbuf.fill(0) - tempbuf=inbuf[indices[0],indices[1]] - #tempbuf+=inbuf[indices[0]+1,indices[1]] - #tempbuf+=inbuf[indices[0],indices[1]+1] - #tempbuf+=inbuf[indices[0]+1,indices[1]+1] - tempbuf/=4.0 - outbuf[:]=tempbuf.reshape((outx,outy)) + dx = inx/outx + dy = iny/outy - #outbuf.fill(0) - #outbuf[xrange,yrange]=inbuf[sxstartrange,systartrange]# + inbuf[sxendrange,systartrange] + inbuf[sxstartrange,syendrange] + inbuf[sxendrange,syendrange])/4.0 + filterSize = 1 + xfiltersize = dx*filterSize + # outx[:]= - else: - sy=-dy/2 - for y in range(0,outy): - sx=-dx/2 - for x in range(0,outx): - pixVal = 0 - weight = 0 - - for ix in range(max( 0, ceil( sx-filterSize ) ),min( floor(sx+filterSize), inx-1 )+1): - for iy in range(max( 0, ceil( sy-filterSize ) ),min( floor( sy+filterSize), iny-1 )+1): - fx = abs( sx - ix ) - fy = abs( sy - iy ) - - fval = (1-fx)*(1-fy) - - pixVal += inbuf[ix,iy] * fval - weight += fval - #if weight==0: - # print('error' ) - # return - outbuf[x,y]=pixVal/weight - sx+=dx - sy+=dy - -def idx(r,c,cols): - - return r*cols+c+1 - - -## smooth u using f at level -def smooth( U, F , linbcgiterations, planar): - - iter=0 - err=0 - - rows = U.shape[1] - cols = U.shape[0] - - n = U.size - - linbcg( n, F, U, 2, 0.001, linbcgiterations, iter, err , rows, cols, planar) - - - -def calculate_defect( D, U, F ): - - - sx = F.shape[0] - sy = F.shape[1] - - h = 1.0/sqrt(sx*sy*1.0) - h2i = 1.0/(h*h) - - h2i = 1 - D[1:-1,1:-1]=F[1:-1,1:-1] - U[:-2,1:-1] - U[2:,1:-1] - U[1:-1, :-2] - U[1:-1,2:] + 4*U[1:-1,1:-1] - #sides - D[1:-1,0]=F[1:-1,0] - U[:-2,0] - U[2:,0] - U[1:-1, 1] + 3*U[1:-1,0] - D[1:-1,-1]=F[1:-1,-1] - U[:-2,-1] - U[2:,-1] - U[1:-1, -2] + 3*U[1:-1,-1] - D[0,1:-1] = F[0,1:-1] - U[0,:-2] - U[0,:-2] - U[1,1:-1] + 3*U[0,1:-1] - D[-1,1:-1] = F[-1,1:-1] - U[-1,:-2] - U[-1,:-2] - U[-1,1:-1] + 3*U[-1,1:-1] - #coners - D[0,0]=F[0,0] - U[0,1] - U[1,0] + 2*U[0,0] - D[0,-1]=F[0,-1] - U[1,-1] - U[0,-2] + 2*U[0,-1] - D[-1,0]=F[-1,0] - U[-2,0] - U[-1,1] + 2*U[-1,0] - D[-1,-1]=F[-1,-1] - U[-2,-1] - U[-1,-2] + 2*U[-1,-1] - - # for y in range(0,sy): - # for x in range(0,sx): - # - # w = max(0,x-1) - # n = max(0,y-1) - # e = min(sx, x+1) - # s = min(sy, y+1) - # - # - # D[x,y] = F[x,y] -( U[e,y] + U[w,y] + U[x,n] + U[x,s] - 4.0*U[x,y]) - -def add_correction( U, C ): - U+=C + # outbuf.put(inbuf.repeat(4)) + if dx == 0.5 and dy == 0.5: + outbuf[::2, ::2] = inbuf + outbuf[1::2, ::2] = inbuf + outbuf[::2, 1::2] = inbuf + outbuf[1::2, 1::2] = inbuf + # x=inbuf::.flatten().repeat(2) + elif NUMPYALG: # numpy method + sy = -dy/2 + sx = -dx/2 + xrange = numpy.arange(0, outx) + yrange = numpy.arange(0, outy) + + sxrange = xrange*dx+sx + syrange = yrange*dy+sy + + sxstartrange = numpy.array(numpy.ceil(sxrange-xfiltersize), dtype=int) + sxstartrange[sxstartrange < 0] = 0 + sxendrange = numpy.array(numpy.floor(sxrange+xfiltersize)+1, dtype=int) + sxendrange[sxendrange >= inx] = inx-1 + systartrange = numpy.array(numpy.ceil(syrange-xfiltersize), dtype=int) + systartrange[systartrange < 0] = 0 + syendrange = numpy.array(numpy.floor(syrange+xfiltersize)+1, dtype=int) + syendrange[syendrange >= iny] = iny-1 + + indices = numpy.arange(outx*outy*2).reshape((2, outx*outy)) + indices[0] = sxstartrange.repeat(outy) + indices[1] = systartrange.repeat(outx).reshape(outx, outy).swapaxes(0, 1).flatten() + + # systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) + # syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 + # outbuf.fill(0) + tempbuf = inbuf[indices[0], indices[1]] + # tempbuf+=inbuf[indices[0]+1,indices[1]] + # tempbuf+=inbuf[indices[0],indices[1]+1] + # tempbuf+=inbuf[indices[0]+1,indices[1]+1] + tempbuf /= 4.0 + outbuf[:] = tempbuf.reshape((outx, outy)) + + # outbuf.fill(0) + # outbuf[xrange,yrange]=inbuf[sxstartrange,systartrange]# + inbuf[sxendrange,systartrange] + inbuf[sxstartrange,syendrange] + inbuf[sxendrange,syendrange])/4.0 + + else: + sy = -dy/2 + for y in range(0, outy): + sx = -dx/2 + for x in range(0, outx): + pixVal = 0 + weight = 0 + + for ix in range(max(0, ceil(sx-filterSize)), min(floor(sx+filterSize), inx-1)+1): + for iy in range(max(0, ceil(sy-filterSize)), min(floor(sy+filterSize), iny-1)+1): + fx = abs(sx - ix) + fy = abs(sy - iy) + + fval = (1-fx)*(1-fy) + + pixVal += inbuf[ix, iy] * fval + weight += fval + # if weight==0: + # print('error' ) + # return + outbuf[x, y] = pixVal/weight + sx += dx + sy += dy + + +def idx(r, c, cols): + + return r*cols+c+1 + + +# smooth u using f at level +def smooth(U, F, linbcgiterations, planar): + + iter = 0 + err = 0 + + rows = U.shape[1] + cols = U.shape[0] + + n = U.size + + linbcg(n, F, U, 2, 0.001, linbcgiterations, iter, err, rows, cols, planar) + + +def calculate_defect(D, U, F): + + sx = F.shape[0] + sy = F.shape[1] + + h = 1.0/sqrt(sx*sy*1.0) + h2i = 1.0/(h*h) + + h2i = 1 + D[1:-1, 1:-1] = F[1:-1, 1:-1] - U[:-2, 1:-1] - U[2:, 1:-1] - \ + U[1:-1, :-2] - U[1:-1, 2:] + 4*U[1:-1, 1:-1] + # sides + D[1:-1, 0] = F[1:-1, 0] - U[:-2, 0] - U[2:, 0] - U[1:-1, 1] + 3*U[1:-1, 0] + D[1:-1, -1] = F[1:-1, -1] - U[:-2, -1] - U[2:, -1] - U[1:-1, -2] + 3*U[1:-1, -1] + D[0, 1:-1] = F[0, 1:-1] - U[0, :-2] - U[0, :-2] - U[1, 1:-1] + 3*U[0, 1:-1] + D[-1, 1:-1] = F[-1, 1:-1] - U[-1, :-2] - U[-1, :-2] - U[-1, 1:-1] + 3*U[-1, 1:-1] + # coners + D[0, 0] = F[0, 0] - U[0, 1] - U[1, 0] + 2*U[0, 0] + D[0, -1] = F[0, -1] - U[1, -1] - U[0, -2] + 2*U[0, -1] + D[-1, 0] = F[-1, 0] - U[-2, 0] - U[-1, 1] + 2*U[-1, 0] + D[-1, -1] = F[-1, -1] - U[-2, -1] - U[-1, -2] + 2*U[-1, -1] + + # for y in range(0,sy): + # for x in range(0,sx): + # + # w = max(0,x-1) + # n = max(0,y-1) + # e = min(sx, x+1) + # s = min(sy, y+1) + # + # + # D[x,y] = F[x,y] -( U[e,y] + U[w,y] + U[x,n] + U[x,s] - 4.0*U[x,y]) + + +def add_correction(U, C): + U += C -#def alloc_compbuf(xmax,ymax,pix, 1): +# def alloc_compbuf(xmax,ymax,pix, 1): # ar=numpy.array() -def solve_pde_multigrid( F, U , vcycleiterations, linbcgiterations, smoothiterations, mins, levels, useplanar, planar): - - xmax = F.shape[0] - ymax = F.shape[1] - - #int i # index for simple loops - #int k # index for iterating through levels - #int k2 # index for iterating through levels in V-cycles - - ## 1. restrict f to coarse-grid (by the way count the number of levels) - ## k=0: fine-grid = f - ## k=levels: coarsest-grid - #pix = CB_VAL#what is this>??? - #int cycle - #int sx, sy - - RHS=[] - IU=[] - VF=[] - PLANAR=[] - for a in range(0,levels+1): - RHS.append(None) - IU.append(None) - VF.append(None) - PLANAR.append(None) - VF[0] = numpy.zeros((xmax,ymax), dtype=numpy.float) - #numpy.fill(pix)!? TODO - - RHS[0] = F.copy() - IU[0] = U.copy() - PLANAR[0] = planar.copy() - - sx=xmax - sy=ymax - #print(planar) - for k in range(0,levels): - # calculate size of next level - sx=int(sx/2) - sy=int(sy/2) - PLANAR[k+1] = numpy.zeros((sx,sy), dtype=numpy.float) - RHS[k+1] = numpy.zeros((sx,sy), dtype=numpy.float) - IU[k+1] = numpy.zeros((sx,sy), dtype=numpy.float) - VF[k+1] = numpy.zeros((sx,sy), dtype=numpy.float) - - # restrict from level k to level k+1 (coarser-grid) - restrictbuf(PLANAR[k], PLANAR[k+1]) - PLANAR[k+1]=PLANAR[k+1]>0 - #numpytoimage(PLANAR[k+1],'planar') - #print(PLANAR[k+1]) - restrictbuf( RHS[k], RHS[k+1] ) - #numpytoimage(RHS[k+1],'rhs') - - - - # 2. find exact sollution at the coarsest-grid (k=levels) - IU[levels].fill(0.0)#this was replaced to easify code. exact_sollution( RHS[levels], IU[levels] ) - - # 3. nested iterations - - for k in range(levels-1,-1,-1): - print('K:', str(k)) - - # 4. interpolate sollution from last coarse-grid to finer-grid - # interpolate from level k+1 to level k (finer-grid) - prolongate( IU[k+1], IU[k] ) - #print('k',k) - # 4.1. first target function is the equation target function - # (following target functions are the defect) - copy_compbuf_data( RHS[k], VF[k] ) +def solve_pde_multigrid(F, U, vcycleiterations, linbcgiterations, smoothiterations, mins, levels, useplanar, planar): + + xmax = F.shape[0] + ymax = F.shape[1] + + # int i # index for simple loops + # int k # index for iterating through levels + # int k2 # index for iterating through levels in V-cycles + + # 1. restrict f to coarse-grid (by the way count the number of levels) + # k=0: fine-grid = f + # k=levels: coarsest-grid + # pix = CB_VAL#what is this>??? + # int cycle + # int sx, sy + + RHS = [] + IU = [] + VF = [] + PLANAR = [] + for a in range(0, levels+1): + RHS.append(None) + IU.append(None) + VF.append(None) + PLANAR.append(None) + VF[0] = numpy.zeros((xmax, ymax), dtype=numpy.float) + # numpy.fill(pix)!? TODO + + RHS[0] = F.copy() + IU[0] = U.copy() + PLANAR[0] = planar.copy() + + sx = xmax + sy = ymax + # print(planar) + for k in range(0, levels): + # calculate size of next level + sx = int(sx/2) + sy = int(sy/2) + PLANAR[k+1] = numpy.zeros((sx, sy), dtype=numpy.float) + RHS[k+1] = numpy.zeros((sx, sy), dtype=numpy.float) + IU[k+1] = numpy.zeros((sx, sy), dtype=numpy.float) + VF[k+1] = numpy.zeros((sx, sy), dtype=numpy.float) + + # restrict from level k to level k+1 (coarser-grid) + restrictbuf(PLANAR[k], PLANAR[k+1]) + PLANAR[k+1] = PLANAR[k+1] > 0 + # numpytoimage(PLANAR[k+1],'planar') + # print(PLANAR[k+1]) + restrictbuf(RHS[k], RHS[k+1]) + # numpytoimage(RHS[k+1],'rhs') + + # 2. find exact sollution at the coarsest-grid (k=levels) + # this was replaced to easify code. exact_sollution( RHS[levels], IU[levels] ) + IU[levels].fill(0.0) + + # 3. nested iterations + + for k in range(levels-1, -1, -1): + print('K:', str(k)) + + # 4. interpolate sollution from last coarse-grid to finer-grid + # interpolate from level k+1 to level k (finer-grid) + prolongate(IU[k+1], IU[k]) + # print('k',k) + # 4.1. first target function is the equation target function + # (following target functions are the defect) + copy_compbuf_data(RHS[k], VF[k]) + + #print('lanar ') + + # 5. V-cycle (twice repeated) + + for cycle in range(0, vcycleiterations): + print('v-cycle iteration:', str(cycle)) + + # 6. downward stroke of V + for k2 in range(k, levels): + # 7. pre-smoothing of initial sollution using target function + # zero for initial guess at smoothing + # (except for level k when iu contains prolongated result) + if(k2 != k): + IU[k2].fill(0.0) + + for i in range(0, smoothiterations): + smooth(IU[k2], VF[k2], linbcgiterations, PLANAR[k2]) + + # 8. calculate defect at level + # d[k2] = Lh * ~u[k2] - f[k2] + + D = numpy.zeros_like(IU[k2]) + # if k2==0: + # IU[k2][planar[k2]]=IU[k2].max() + # print(IU[0]) + if useplanar and k2 == 0: + IU[k2][PLANAR[k2]] = IU[k2].min() + # if k2==0 : + + # VF[k2][PLANAR[k2]]=0.0 + # print(IU[0]) + calculate_defect(D, IU[k2], VF[k2]) + + # 9. restrict deffect as target function for next coarser-grid + # def -> f[k2+1] + restrictbuf(D, VF[k2+1]) + + # 10. solve on coarsest-grid (target function is the deffect) + # iu[levels] should contain sollution for + # the f[levels] - last deffect, iu will now be the correction + IU[levels].fill(0.0) # exact_sollution(VF[levels], IU[levels] ) + + # 11. upward stroke of V + for k2 in range(levels-1, k-1, -1): + print('k2: ', str(k2)) + # 12. interpolate correction from last coarser-grid to finer-grid + # iu[k2+1] -> cor + C = numpy.zeros_like(IU[k2]) + prolongate(IU[k2+1], C) + + # 13. add interpolated correction to initial sollution at level k2 + add_correction(IU[k2], C) + + # 14. post-smoothing of current sollution using target function + for i in range(0, smoothiterations): + + smooth(IU[k2], VF[k2], linbcgiterations, PLANAR[k2]) - #print('lanar ') + if useplanar and k2 == 0: + IU[0][planar] = IU[0].min() + # print(IU[0]) - # 5. V-cycle (twice repeated) + # --- end of V-cycle + + # --- end of nested iteration - for cycle in range(0,vcycleiterations): - print('v-cycle iteration:', str(cycle)) + # 15. final sollution + # IU[0] contains the final sollution - # 6. downward stroke of V - for k2 in range(k,levels): - # 7. pre-smoothing of initial sollution using target function - # zero for initial guess at smoothing - # (except for level k when iu contains prolongated result) - if( k2!=k ): - IU[k2].fill(0.0) + U[:] = IU[0] - for i in range(0,smoothiterations): - smooth( IU[k2], VF[k2], linbcgiterations, PLANAR[k2]) - # 8. calculate defect at level - # d[k2] = Lh * ~u[k2] - f[k2] - - D = numpy.zeros_like(IU[k2]) - #if k2==0: - #IU[k2][planar[k2]]=IU[k2].max() - #print(IU[0]) - if useplanar and k2==0: - IU[k2][PLANAR[k2]]=IU[k2].min() - #if k2==0 : - - # VF[k2][PLANAR[k2]]=0.0 - # print(IU[0]) - calculate_defect( D, IU[k2], VF[k2] ) - - # 9. restrict deffect as target function for next coarser-grid - # def -> f[k2+1] - restrictbuf( D, VF[k2+1] ) - - # 10. solve on coarsest-grid (target function is the deffect) - # iu[levels] should contain sollution for - # the f[levels] - last deffect, iu will now be the correction - IU[levels].fill(0.0)#exact_sollution(VF[levels], IU[levels] ) - - # 11. upward stroke of V - for k2 in range(levels-1,k-1,-1): - print('k2: ',str(k2)) - # 12. interpolate correction from last coarser-grid to finer-grid - # iu[k2+1] -> cor - C = numpy.zeros_like(IU[k2]) - prolongate( IU[k2+1], C ) +def asolve(b, x): + x[:] = -4*b - # 13. add interpolated correction to initial sollution at level k2 - add_correction( IU[k2], C ) +def atimes(x, res): + res[1:-1, 1:-1] = x[:-2, 1:-1]+x[2:, 1:-1]+x[1:-1, :-2]+x[1:-1, 2:] - 4*x[1:-1, 1:-1] + # sides + res[1:-1, 0] = x[0:-2, 0]+x[2:, 0]+x[1:-1, 1] - 3*x[1:-1, 0] + res[1:-1, -1] = x[0:-2, -1]+x[2:, -1]+x[1:-1, -2] - 3*x[1:-1, -1] + res[0, 1:-1] = x[0, :-2] + x[0, 2:] + x[1, 1:-1] - 3*x[0, 1:-1] + res[-1, 1:-1] = x[-1, :-2] + x[-1, 2:] + x[-2, 1:-1] - 3*x[-1, 1:-1] + # corners + res[0, 0] = x[1, 0]+x[0, 1]-2*x[0, 0] + res[-1, 0] = x[-2, 0]+x[-1, 1]-2*x[-1, 0] + res[0, -1] = x[0, -2]+x[1, -1]-2*x[0, -1] + res[-1, -1] = x[-1, -2]+x[-2, -1]-2*x[-1, -1] - # 14. post-smoothing of current sollution using target function - for i in range(0, smoothiterations): - smooth( IU[k2], VF[k2] ,linbcgiterations,PLANAR[k2]) +def snrm(n, sx, itol): + if (itol <= 3): + temp = sx*sx + ans = temp.sum() + return sqrt(ans) + else: + temp = numpy.abs(sx) + return temp.max() - if useplanar and k2==0: - IU[0][planar]=IU[0].min() - #print(IU[0]) +# /** +# * Biconjugate Gradient Method +# * from Numerical Recipes in C +# */ - #--- end of V-cycle - #--- end of nested iteration +def linbcg(n, b, x, itol, tol, itmax, iter, err, rows, cols, planar): - # 15. final sollution - # IU[0] contains the final sollution + p = numpy.zeros((cols, rows)) + pp = numpy.zeros((cols, rows)) + r = numpy.zeros((cols, rows)) + rr = numpy.zeros((cols, rows)) + z = numpy.zeros((cols, rows)) + zz = numpy.zeros((cols, rows)) - U[:]=IU[0] + iter = 0 + atimes(x, r) + r[:] = b-r + rr[:] = r + atimes(r, rr) # minimum residual -def asolve(b, x): - x[:] = -4*b + znrm = 1.0 + + if (itol == 1): + bnrm = snrm(n, b, itol) + + elif (itol == 2): + asolve(b, z) + bnrm = snrm(n, z, itol) + + elif (itol == 3 or itol == 4): + asolve(b, z) + bnrm = snrm(n, z, itol) + asolve(r, z) + znrm = snrm(n, z, itol) + else: + print("illegal itol in linbcg") + + asolve(r, z) + + while (iter <= itmax): + #print('linbcg iteration:', str(iter)) + iter += 1 + zm1nrm = znrm + asolve(rr, zz) + + bknum = 0.0 + + temp = z*rr + + bknum = temp.sum() # -z[0]*rr[0]???? + + if (iter == 1): + p[:] = z + pp[:] = zz + + else: + bk = bknum/bkden + p = bk*p+z + pp = bk*pp+zz + bkden = bknum + atimes(p, z) + temp = z*pp + akden = temp.sum() + ak = bknum/akden + atimes(pp, zz) + + x += ak*p + r -= ak*z + rr -= ak*zz + + asolve(r, z) + + if (itol == 1 or itol == 2): + znrm = 1.0 + err = snrm(n, r, itol)/bnrm + elif (itol == 3 or itol == 4): + znrm = snrm(n, z, itol) + if (abs(zm1nrm-znrm) > EPS*znrm): + dxnrm = abs(ak)*snrm(n, p, itol) + err = znrm/abs(zm1nrm-znrm)*dxnrm + else: + err = znrm/bnrm + continue + xnrm = snrm(n, x, itol) + + if (err <= 0.5*xnrm): + err /= xnrm + else: + err = znrm/bnrm + continue + if (err <= tol): + break + # if PLANAR_CONST and planar.shape==rr.shape: + # x[planar]=0.0 + + +# -------------------------------------------------------------------- + + +def numpysave(a, iname): + inamebase = bpy.path.basename(iname) + + i = numpytoimage(a, inamebase) + + r = bpy.context.scene.render + + r.image_settings.file_format = 'OPEN_EXR' + r.image_settings.color_mode = 'BW' + r.image_settings.color_depth = '32' + + i.save_render(iname) -def atimes(x, res): - res[1:-1,1:-1]=x[:-2,1:-1]+x[2:,1:-1]+x[1:-1,:-2]+x[1:-1,2:] - 4*x[1:-1,1:-1] - #sides - res[1:-1,0]=x[0:-2,0]+x[2:,0]+x[1:-1,1] - 3*x[1:-1,0] - res[1:-1,-1] = x[0:-2,-1]+x[2:,-1]+x[1:-1,-2]- 3*x[1:-1,-1] - res[0,1:-1] = x[0, :-2] + x[0, 2:] + x[1, 1:-1] -3*x [0,1:-1] - res[-1,1:-1] = x[-1, :-2] + x[-1, 2:] + x[-2, 1:-1] -3*x [-1,1:-1] - #corners - res[0,0]=x[1,0]+x[0,1]-2*x[0,0] - res[-1,0]=x[-2,0]+x[-1,1]-2*x[-1,0] - res[0,-1]=x[0,-2]+x[1,-1]-2*x[0,-1] - res[-1,-1]=x[-1,-2]+x[-2,-1]-2*x[-1,-1] -def snrm(n, sx, itol): +def numpytoimage(a, iname): + t = time.time() + print('numpy to image - here') + t = time.time() + print(a.shape[0], a.shape[1]) + foundimage = False + for image in bpy.data.images: + + if image.name[:len(iname)] == iname and image.size[0] == a.shape[0] and image.size[1] == a.shape[1]: + i = image + foundimage = True + if not foundimage: + bpy.ops.image.new(name=iname, width=a.shape[0], height=a.shape[1], color=( + 0, 0, 0, 1), alpha=True, generated_type='BLANK', float=True) + for image in bpy.data.images: + + if image.name[:len(iname)] == iname and image.size[0] == a.shape[0] and image.size[1] == a.shape[1]: + i = image + + d = a.shape[0]*a.shape[1] + a = a.swapaxes(0, 1) + a = a.reshape(d) + a = a.repeat(4) + a[3::4] = 1 + # i.pixels=a + i.pixels[:] = a[:] # this gives big speedup! + print('\ntime '+str(time.time()-t)) + return i - if (itol <= 3): - temp=sx*sx - ans=temp.sum() - return sqrt(ans) - else: - temp=numpy.abs(sx) - return temp.max() -#/** -# * Biconjugate Gradient Method -# * from Numerical Recipes in C -# */ -def linbcg(n, b, x, itol, tol, itmax, iter, err, rows, cols, planar): +def imagetonumpy(i): + t = time.time() + inc = 0 + + width = i.size[0] + height = i.size[1] + x = 0 + y = 0 + count = 0 + na = numpy.array((0.1), dtype=float) + + size = width*height + na.resize(size*4) + + # these 2 lines are about 15% faster than na=i.pixels[:].... whyyyyyyyy!!?!?!?!?! Blender image data access is evil. + p = i.pixels[:] + na[:] = p + # na=numpy.array(i.pixels[:])#this was terribly slow... at least I know why now, it probably + na = na[::4] + na = na.reshape(height, width) + na = na.swapaxes(0, 1) + + print('\ntime of image to numpy '+str(time.time()-t)) + return na + + +def tonemap(i, exponent): + # if depth buffer never got written it gets set + # to a great big value (10000000000.0) + # filter out anything within an order of magnitude of it + # so we only have things that are actually drawn + maxheight = i.max(where=i < 1000000000.0, initial=0) + minheight = i.min() + i[:] = numpy.clip(i, minheight, maxheight) + + i[:] = ((i-minheight))/(maxheight-minheight) + i[:] **= exponent + + +def vert(column, row, z, XYscaling, Zscaling): + """ Create a single vert """ + return column * XYscaling, row * XYscaling, z * Zscaling + + +def buildMesh(mesh_z, br): + global rows + global size + scale = 1 + scalez = 1 + decimateRatio = br.decimate_ratio # get variable from interactive table + bpy.ops.object.select_all(action='DESELECT') + for object in bpy.data.objects: + if re.search("BasReliefMesh", str(object)): + bpy.data.objects.remove(object) + print("old basrelief removed") + + print("Building mesh") + numY = mesh_z.shape[1] + numX = mesh_z.shape[0] + print(numX, numY) + + verts = list() + faces = list() + + for i, row in enumerate(mesh_z): + for j, col in enumerate(row): + verts.append(vert(i, j, col, scale, scalez)) + + count = 0 + for i in range(0, numY * (numX-1)): + if count < numY-1: + A = i # the first vertex + B = i+1 # the second vertex + C = (i+numY)+1 # the third vertex + D = (i+numY) # the fourth vertex + + face = (A, B, C, D) + faces.append(face) + count = count + 1 + else: + count = 0 + + # Create Mesh Datablock + mesh = bpy.data.meshes.new("displacement") + mesh.from_pydata(verts, [], faces) + + mesh.update() + + # make object from mesh + new_object = bpy.data.objects.new('BasReliefMesh', mesh) + scene = bpy.context.scene + scene.collection.objects.link(new_object) + + # mesh object is made - preparing to decimate. + ob = bpy.data.objects['BasReliefMesh'] + ob.select_set(True) + bpy.context.view_layer.objects.active = ob + bpy.context.active_object.dimensions = (br.widthmm/1000, br.heightmm/1000, br.thicknessmm/1000) + bpy.context.active_object.location = (float( + br.justifyx)*br.widthmm/1000, float(br.justifyy)*br.heightmm/1000, float(br.justifyz)*br.thicknessmm/1000) + + print("faces:" + str(len(ob.data.polygons))) + print("vertices:" + str(len(ob.data.vertices))) + if decimateRatio > 0.95: + print("skipping decimate ratio > 0.95") + else: + m = ob.modifiers.new(name="Foo", type='DECIMATE') + m.ratio = decimateRatio + print("decimating with ratio:"+str(decimateRatio)) + bpy.ops.object.modifier_apply(modifier=m.name) + print("decimated") + print("faces:" + str(len(ob.data.polygons))) + print("vertices:" + str(len(ob.data.vertices))) - p=numpy.zeros((cols,rows)) - pp=numpy.zeros((cols,rows)) - r=numpy.zeros((cols,rows)) - rr=numpy.zeros((cols,rows)) - z=numpy.zeros((cols,rows)) - zz=numpy.zeros((cols,rows)) +# Switches to cycles render to CYCLES to render the sceen then switches it back to BLENDERCAM_RENDER for basRelief - iter=0 - atimes(x,r) - r[:]=b-r - rr[:]=r - - atimes(r,rr) # minimum residual - znrm=1.0 - - if (itol == 1): - bnrm=snrm(n,b,itol) - - elif (itol == 2): - asolve(b,z) - bnrm=snrm(n,z,itol) - - elif (itol == 3 or itol == 4): - asolve(b,z) - bnrm=snrm(n,z,itol) - asolve(r,z) - znrm=snrm(n,z,itol) - else: - print("illegal itol in linbcg") - - asolve(r,z) - - while (iter <= itmax): - #print('linbcg iteration:', str(iter)) - iter+=1 - zm1nrm=znrm - asolve(rr,zz) - - bknum=0.0 - - temp=z*rr - - bknum=temp.sum()#-z[0]*rr[0]???? - - if (iter == 1): - p[:]=z - pp[:]=zz - - else: - bk=bknum/bkden - p=bk*p+z - pp=bk*pp+zz - bkden=bknum - atimes(p,z) - temp=z*pp - akden = temp.sum() - ak=bknum/akden - atimes(pp,zz) - - x+=ak*p - r-= ak*z - rr -= ak*zz - - asolve(r,z) - - if (itol == 1 or itol == 2): - znrm=1.0 - err=snrm(n,r,itol)/bnrm - elif (itol == 3 or itol == 4): - znrm=snrm(n,z,itol) - if (abs(zm1nrm-znrm) > EPS*znrm): - dxnrm=abs(ak)*snrm(n,p,itol) - err=znrm/abs(zm1nrm-znrm)*dxnrm - else: - err=znrm/bnrm - continue - xnrm=snrm(n,x,itol) - - if (err <= 0.5*xnrm): - err /= xnrm - else: - err=znrm/bnrm - continue - if (err <= tol): - break - #if PLANAR_CONST and planar.shape==rr.shape: - # x[planar]=0.0 - - -#-------------------------------------------------------------------- - - -def numpysave(a,iname): - inamebase=bpy.path.basename(iname) - - i=numpytoimage(a,inamebase) - - r=bpy.context.scene.render - - r.image_settings.file_format='OPEN_EXR' - r.image_settings.color_mode='BW' - r.image_settings.color_depth='32' - - i.save_render(iname) - -def numpytoimage(a,iname): - t=time.time() - print('numpy to image - here') - t=time.time() - print(a.shape[0],a.shape[1]) - foundimage=False - for image in bpy.data.images: - - if image.name[:len(iname)]==iname and image.size[0]==a.shape[0] and image.size[1]==a.shape[1]: - i=image - foundimage=True - if not foundimage: - bpy.ops.image.new(name=iname, width=a.shape[0], height=a.shape[1], color=(0, 0, 0, 1), alpha=True, generated_type='BLANK', float=True) - for image in bpy.data.images: - - if image.name[:len(iname)]==iname and image.size[0]==a.shape[0] and image.size[1]==a.shape[1]: - i=image - - d=a.shape[0]*a.shape[1] - a=a.swapaxes(0,1) - a=a.reshape(d) - a=a.repeat(4) - a[3::4]=1 - #i.pixels=a - i.pixels[:]=a[:]#this gives big speedup! - print('\ntime '+str(time.time()-t)) - return i +def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view_layer): + print("rendering scene") + scene = bpy.context.scene + # make sure we're in object mode or else bad things happen + if bpy.context.active_object: + bpy.ops.object.mode_set(mode='OBJECT') + + scene.render.engine = 'CYCLES' + our_viewer = None + our_renderer = None + if make_nodes: + # make depth render node and viewer node + if scene.use_nodes == False: + scene.use_nodes = True + node_tree = scene.node_tree + nodes = node_tree.nodes + our_viewer = node_tree.nodes.new(type='CompositorNodeViewer') + our_viewer.label = "CAM_basrelief_viewer" + our_renderer = node_tree.nodes.new(type='CompositorNodeRLayers') + our_renderer.label = "CAM_basrelief_renderlayers" + our_renderer.layer = view_layer + node_tree.links.new(our_renderer.outputs[our_renderer.outputs.find( + 'Depth')], our_viewer.inputs[our_viewer.inputs.find("Image")]) + scene.view_layers[view_layer].use_pass_z = True + # set our viewer as active so that it is what gets rendered to viewer node image + nodes.active = our_viewer + + # Set render resolution + passes = bit_diameter/(2*passes_per_radius) + x = round(width/passes) + y = round(height/passes) + print(x, y, passes) + scene.render.resolution_x = x + scene.render.resolution_y = y + scene.render.resolution_percentage = 100 + bpy.ops.render.render(animation=False, write_still=False, use_viewport=True, layer="", scene="") + if our_renderer is not None: + nodes.remove(our_renderer) + if our_viewer is not None: + nodes.remove(our_viewer) + bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' + print("done rendering") -def imagetonumpy(i): - t=time.time() - inc=0 - - width=i.size[0] - height=i.size[1] - x=0 - y=0 - count=0 - na=numpy.array((0.1),dtype=float) - - size=width*height - na.resize(size*4) - - p=i.pixels[:]#these 2 lines are about 15% faster than na=i.pixels[:].... whyyyyyyyy!!?!?!?!?! Blender image data access is evil. - na[:]=p - #na=numpy.array(i.pixels[:])#this was terribly slow... at least I know why now, it probably - na=na[::4] - na=na.reshape(height,width) - na=na.swapaxes(0,1) - - print('\ntime of image to numpy '+str(time.time()-t)) - return na - -def tonemap(i,exponent): - # if depth buffer never got written it gets set - # to a great big value (10000000000.0) - # filter out anything within an order of magnitude of it - # so we only have things that are actually drawn - maxheight=i.max(where=i<1000000000.0,initial=0) - minheight=i.min() - i[:]=numpy.clip(i,minheight,maxheight) - - i[:]=((i-minheight))/(maxheight-minheight) - i[:]**=exponent - -def vert(column, row, z,XYscaling,Zscaling): - """ Create a single vert """ - return column * XYscaling, row * XYscaling, z * Zscaling - -def buildMesh(mesh_z,br): - global rows - global size - scale=1 - scalez=1 - decimateRatio= br.decimate_ratio #get variable from interactive table - bpy.ops.object.select_all(action='DESELECT') - for object in bpy.data.objects: - if re.search("BasReliefMesh",str(object)): - bpy.data.objects.remove(object) - print("old basrelief removed") - - - - print("Building mesh") - numY = mesh_z.shape[1] - numX = mesh_z.shape[0] - print(numX,numY) - - verts = list() - faces = list() - - for i, row in enumerate(mesh_z): - for j, col in enumerate(row): - verts.append(vert(i, j, col,scale,scalez)) - - count = 0 - for i in range (0, numY *(numX-1)): - if count < numY-1: - A = i # the first vertex - B = i+1 # the second vertex - C = (i+numY)+1 # the third vertex - D = (i+numY) # the fourth vertex - - face = (A,B,C,D) - faces.append(face) - count = count + 1 - else: - count = 0 - - # Create Mesh Datablock - mesh = bpy.data.meshes.new("displacement") - mesh.from_pydata(verts, [], faces) - - mesh.update() - - # make object from mesh - new_object = bpy.data.objects.new('BasReliefMesh', mesh) - scene = bpy.context.scene - scene.collection.objects.link(new_object) - - #mesh object is made - preparing to decimate. - ob=bpy.data.objects['BasReliefMesh'] - ob.select_set(True) - bpy.context.view_layer.objects.active = ob - bpy.context.active_object.dimensions= (br.widthmm/1000,br.heightmm/1000,br.thicknessmm/1000) - bpy.context.active_object.location= (float(br.justifyx)*br.widthmm/1000,float(br.justifyy)*br.heightmm/1000,float(br.justifyz)*br.thicknessmm/1000) - - - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) - if decimateRatio > 0.95: - print("skipping decimate ratio > 0.95") - else: - m = ob.modifiers.new(name="Foo", type='DECIMATE') - m.ratio=decimateRatio - print("decimating with ratio:"+str(decimateRatio)) - bpy.ops.object.modifier_apply(modifier=m.name) - print("decimated") - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) - -# Switches to cycles render to CYCLES to render the sceen then switches it back to BLENDERCAM_RENDER for basRelief -def renderScene(width,height,bit_diameter,passes_per_radius,make_nodes,view_layer): - print("rendering scene") - scene = bpy.context.scene - # make sure we're in object mode or else bad things happen - if bpy.context.active_object: - bpy.ops.object.mode_set(mode='OBJECT') - - scene.render.engine = 'CYCLES' - our_viewer=None - our_renderer=None - if make_nodes: - # make depth render node and viewer node - if scene.use_nodes==False: - scene.use_nodes=True - node_tree=scene.node_tree - nodes=node_tree.nodes - our_viewer=node_tree.nodes.new(type = 'CompositorNodeViewer') - our_viewer.label="CAM_basrelief_viewer" - our_renderer=node_tree.nodes.new(type= 'CompositorNodeRLayers') - our_renderer.label="CAM_basrelief_renderlayers" - our_renderer.layer=view_layer - node_tree.links.new(our_renderer.outputs[our_renderer.outputs.find('Depth')],our_viewer.inputs[our_viewer.inputs.find("Image")]) - scene.view_layers[view_layer].use_pass_z=True - # set our viewer as active so that it is what gets rendered to viewer node image - nodes.active=our_viewer - - # Set render resolution - passes=bit_diameter/(2*passes_per_radius) - x=round(width/passes) - y=round(height/passes) - print(x,y,passes) - scene.render.resolution_x = x - scene.render.resolution_y = y - scene.render.resolution_percentage = 100 - bpy.ops.render.render(animation=False, write_still=False, use_viewport=True, layer="", scene="") - if our_renderer is not None: - nodes.remove(our_renderer) - if our_viewer is not None: - nodes.remove(our_viewer) - bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' - print("done rendering") - - def problemAreas(br): - t=time.time() - if br.use_image_source: - i=bpy.data.images[br.source_image_name] - else: - i=bpy.data.images["Viewer Node"] - silh_thres=br.silhouette_threshold - recover_silh=br.recover_silhouettes - silh_scale=br.silhouette_scale - MINS=br.min_gridsize - smoothiterations=br.smooth_iterations - vcycleiterations=br.vcycle_iterations - linbcgiterations=br.linbcg_iterations - useplanar=br.use_planar - #scale down before: - if br.gradient_scaling_mask_use: - m=bpy.data.images[br.gradient_scaling_mask_name] - #mask=nar=imagetonumpy(m) - - #if br.scale_down_before_use: - # i.scale(int(i.size[0]*br.scale_down_before),int(i.size[1]*br.scale_down_before)) - # if br.gradient_scaling_mask_use: - # m.scale(int(m.size[0]*br.scale_down_before),int(m.size[1]*br.scale_down_before)) - - nar=imagetonumpy(i) - #return - if br.gradient_scaling_mask_use: - mask=imagetonumpy(m) - #put image to scale - tonemap(nar,br.depth_exponent) - nar=1-nar# reverse z buffer+ add something - print(nar.min(),nar.max()) - gx=nar.copy() - gx.fill(0) - gx[:-1,:]=nar[1:,:]-nar[:-1,:] - gy=nar.copy() - gy.fill(0) - gy[:,:-1]=nar[:,1:]-nar[:,:-1] - - #it' ok, we can treat neg and positive silh separately here: - a=br.attenuation - planar=nar<(nar.min()+0.0001)#numpy.logical_or(silhxplanar,silhyplanar)# - #sqrt for silhouettes recovery: - sqrarx=numpy.abs(gx) - for iter in range(0,br.silhouette_exponent): - sqrarx=numpy.sqrt(sqrarx) - sqrary=numpy.abs(gy) - for iter in range(0,br.silhouette_exponent): - sqrary=numpy.sqrt(sqrary) - - - #detect and also recover silhouettes: - silhxpos=gx>silh_thres - gx=gx*(-silhxpos)+recover_silh*(silhxpos*silh_thres*silh_scale)*sqrarx - silhxneg=gx<-silh_thres - gx=gx*(-silhxneg)-recover_silh*(silhxneg*silh_thres*silh_scale)*sqrarx - silhx=numpy.logical_or(silhxpos,silhxneg) - gx=gx*silhx+(1.0/a*numpy.log(1.+a*(gx)))*(-silhx)#attenuate - - #if br.fade_distant_objects: - # gx*=(nar) - # gy*=(nar) - - silhypos=gy>silh_thres - gy=gy*(-silhypos)+recover_silh*(silhypos*silh_thres*silh_scale)*sqrary - silhyneg=gy<-silh_thres - gy=gy*(-silhyneg)-recover_silh*(silhyneg*silh_thres*silh_scale)*sqrary - silhy=numpy.logical_or(silhypos,silhyneg)#both silh - gy=gy*silhy+(1.0/a*numpy.log(1.+a*(gy)))*(-silhy)#attenuate - - #now scale slopes... - if br.gradient_scaling_mask_use: - gx*=mask - gy*=mask - - - divg=gx+gy - divga=numpy.abs(divg) - divgp= divga>silh_thres/4.0 - divgp=1-divgp - for a in range(0,2): - atimes(divgp,divga) - divga=divgp - - numpytoimage(divga,'problem') + t = time.time() + if br.use_image_source: + i = bpy.data.images[br.source_image_name] + else: + i = bpy.data.images["Viewer Node"] + silh_thres = br.silhouette_threshold + recover_silh = br.recover_silhouettes + silh_scale = br.silhouette_scale + MINS = br.min_gridsize + smoothiterations = br.smooth_iterations + vcycleiterations = br.vcycle_iterations + linbcgiterations = br.linbcg_iterations + useplanar = br.use_planar + # scale down before: + if br.gradient_scaling_mask_use: + m = bpy.data.images[br.gradient_scaling_mask_name] + # mask=nar=imagetonumpy(m) + + # if br.scale_down_before_use: + # i.scale(int(i.size[0]*br.scale_down_before),int(i.size[1]*br.scale_down_before)) + # if br.gradient_scaling_mask_use: + # m.scale(int(m.size[0]*br.scale_down_before),int(m.size[1]*br.scale_down_before)) + + nar = imagetonumpy(i) + # return + if br.gradient_scaling_mask_use: + mask = imagetonumpy(m) + # put image to scale + tonemap(nar, br.depth_exponent) + nar = 1-nar # reverse z buffer+ add something + print(nar.min(), nar.max()) + gx = nar.copy() + gx.fill(0) + gx[:-1, :] = nar[1:, :]-nar[:-1, :] + gy = nar.copy() + gy.fill(0) + gy[:, :-1] = nar[:, 1:]-nar[:, :-1] + + # it' ok, we can treat neg and positive silh separately here: + a = br.attenuation + planar = nar < (nar.min()+0.0001) # numpy.logical_or(silhxplanar,silhyplanar)# + # sqrt for silhouettes recovery: + sqrarx = numpy.abs(gx) + for iter in range(0, br.silhouette_exponent): + sqrarx = numpy.sqrt(sqrarx) + sqrary = numpy.abs(gy) + for iter in range(0, br.silhouette_exponent): + sqrary = numpy.sqrt(sqrary) + + # detect and also recover silhouettes: + silhxpos = gx > silh_thres + gx = gx*(-silhxpos)+recover_silh*(silhxpos*silh_thres*silh_scale)*sqrarx + silhxneg = gx < -silh_thres + gx = gx*(-silhxneg)-recover_silh*(silhxneg*silh_thres*silh_scale)*sqrarx + silhx = numpy.logical_or(silhxpos, silhxneg) + gx = gx*silhx+(1.0/a*numpy.log(1.+a*(gx)))*(-silhx) # attenuate + + # if br.fade_distant_objects: + # gx*=(nar) + # gy*=(nar) + + silhypos = gy > silh_thres + gy = gy*(-silhypos)+recover_silh*(silhypos*silh_thres*silh_scale)*sqrary + silhyneg = gy < -silh_thres + gy = gy*(-silhyneg)-recover_silh*(silhyneg*silh_thres*silh_scale)*sqrary + silhy = numpy.logical_or(silhypos, silhyneg) # both silh + gy = gy*silhy+(1.0/a*numpy.log(1.+a*(gy)))*(-silhy) # attenuate + + # now scale slopes... + if br.gradient_scaling_mask_use: + gx *= mask + gy *= mask + + divg = gx+gy + divga = numpy.abs(divg) + divgp = divga > silh_thres/4.0 + divgp = 1-divgp + for a in range(0, 2): + atimes(divgp, divga) + divga = divgp + + numpytoimage(divga, 'problem') def relief(br): - t=time.time() - - if br.use_image_source: - i=bpy.data.images[br.source_image_name] - else: - i=bpy.data.images["Viewer Node"] - silh_thres=br.silhouette_threshold - recover_silh=br.recover_silhouettes - silh_scale=br.silhouette_scale - MINS=br.min_gridsize - smoothiterations=br.smooth_iterations - vcycleiterations=br.vcycle_iterations - linbcgiterations=br.linbcg_iterations - useplanar=br.use_planar - #scale down before: - if br.gradient_scaling_mask_use: - m=bpy.data.images[br.gradient_scaling_mask_name] - #mask=nar=imagetonumpy(m) - - #if br.scale_down_before_use: - # i.scale(int(i.size[0]*br.scale_down_before),int(i.size[1]*br.scale_down_before)) - # if br.gradient_scaling_mask_use: - # m.scale(int(m.size[0]*br.scale_down_before),int(m.size[1]*br.scale_down_before)) - - nar=imagetonumpy(i) - #return - if br.gradient_scaling_mask_use: - mask=imagetonumpy(m) - #put image to scale - tonemap(nar,br.depth_exponent) - nar=1-nar# reverse z buffer+ add something - print("Range:",nar.min(),nar.max()) - if nar.min() - nar.max() ==0: - raise ReliefError("Input image is blank - check you have the correct view layer or input image set.") - - gx=nar.copy() - gx.fill(0) - gx[:-1,:]=nar[1:,:]-nar[:-1,:] - gy=nar.copy() - gy.fill(0) - gy[:,:-1]=nar[:,1:]-nar[:,:-1] - - #it' ok, we can treat neg and positive silh separately here: - a=br.attenuation - planar=nar<(nar.min()+0.0001)#numpy.logical_or(silhxplanar,silhyplanar)# - #sqrt for silhouettes recovery: - sqrarx=numpy.abs(gx) - for iter in range(0,br.silhouette_exponent): - sqrarx=numpy.sqrt(sqrarx) - sqrary=numpy.abs(gy) - for iter in range(0,br.silhouette_exponent): - sqrary=numpy.sqrt(sqrary) - - - #detect and also recover silhouettes: - silhxpos=gx>silh_thres - print("*** silhxpos is %s" %silhxpos) - gx=gx*(~silhxpos)+recover_silh*(silhxpos*silh_thres*silh_scale)*sqrarx - silhxneg=gx<-silh_thres - gx=gx*(~silhxneg)-recover_silh*(silhxneg*silh_thres*silh_scale)*sqrarx - silhx=numpy.logical_or(silhxpos,silhxneg) - gx=gx*silhx+(1.0/a*numpy.log(1.+a*(gx)))*(~silhx)#attenuate - - #if br.fade_distant_objects: - # gx*=(nar) - # gy*=(nar) - - silhypos=gy>silh_thres - gy=gy*(~silhypos)+recover_silh*(silhypos*silh_thres*silh_scale)*sqrary - silhyneg=gy<-silh_thres - gy=gy*(~silhyneg)-recover_silh*(silhyneg*silh_thres*silh_scale)*sqrary - silhy=numpy.logical_or(silhypos,silhyneg)#both silh - gy=gy*silhy+(1.0/a*numpy.log(1.+a*(gy)))*(~silhy)#attenuate - - #now scale slopes... - if br.gradient_scaling_mask_use: - gx*=mask - gy*=mask - - # - #print(silhx) - #silhx=abs(gx)>silh_thres - #gx=gx*(-silhx) - #silhy=abs(gy)>silh_thres - #gy=gy*(-silhy) - - - divg=gx+gy - divg[1:,:]=divg[1:,:]-gx[:-1,:] #subtract x - divg[:,1:]=divg[:,1:]-gy[:,:-1] #subtract y - - if br.detail_enhancement_use:# fourier stuff here!disabled by now - print("detail enhancement") - rows,cols=gx.shape - crow,ccol = int(rows/2), int(cols/2) - #dist=int(br.detail_enhancement_freq*gx.shape[0]/(2)) - #bandwidth=.1 - #dist= - divgmin=divg.min() - divg+=divgmin - divgf=numpy.fft.fft2(divg) - divgfshift=numpy.fft.fftshift(divgf) - #mspectrum = 20*numpy.log(numpy.abs(divgfshift)) - #numpytoimage(mspectrum,'mspectrum') - mask=divg.copy() - pos=numpy.array((crow,ccol)) - - #bpy.context.scene.view_settings.curve_mapping.initialize() - #cur=bpy.context.scene.view_settings.curve_mapping.curves[0] - def filterwindow(x,y, cx = 0, cy = 0):#, curve=None): - return abs((cx-x))+abs((cy-y)) - #v=(abs((cx-x)/(cx))+abs((cy-y)/(cy))) - #return v - - mask=numpy.fromfunction(filterwindow,divg.shape, cx=crow, cy=ccol)#, curve=cur) - mask=numpy.sqrt(mask) - #for x in range(mask.shape[0]): - # for y in range(mask.shape[1]): - # mask[x,y]=cur.evaluate(mask[x,y]) - maskmin=mask.min() - maskmax=mask.max() - mask=(mask-maskmin)/(maskmax-maskmin) - mask*=br.detail_enhancement_amount - mask+=1-mask.max() - #mask+=1 - mask[crow-1:crow+1,ccol-1:ccol+1]=1#to preserve basic freqencies. - #numpytoimage(mask,'mask') - divgfshift = divgfshift*mask - divgfshift = numpy.fft.ifftshift(divgfshift) - divg = numpy.abs(numpy.fft.ifft2(divgfshift)) - divg-=divgmin - divg=-divg - print("detail enhancement finished") - - levels=0 - mins = min(nar.shape[0],nar.shape[1]) - while (mins>=MINS): - levels+=1 - mins = mins/2 - - - target=numpy.zeros_like(divg) - - solve_pde_multigrid( divg, target ,vcycleiterations, linbcgiterations, smoothiterations, mins, levels, useplanar, planar) - - tonemap(target,1) - - buildMesh(target,br) + t = time.time() + + if br.use_image_source: + i = bpy.data.images[br.source_image_name] + else: + i = bpy.data.images["Viewer Node"] + silh_thres = br.silhouette_threshold + recover_silh = br.recover_silhouettes + silh_scale = br.silhouette_scale + MINS = br.min_gridsize + smoothiterations = br.smooth_iterations + vcycleiterations = br.vcycle_iterations + linbcgiterations = br.linbcg_iterations + useplanar = br.use_planar + # scale down before: + if br.gradient_scaling_mask_use: + m = bpy.data.images[br.gradient_scaling_mask_name] + # mask=nar=imagetonumpy(m) + + # if br.scale_down_before_use: + # i.scale(int(i.size[0]*br.scale_down_before),int(i.size[1]*br.scale_down_before)) + # if br.gradient_scaling_mask_use: + # m.scale(int(m.size[0]*br.scale_down_before),int(m.size[1]*br.scale_down_before)) + + nar = imagetonumpy(i) + # return + if br.gradient_scaling_mask_use: + mask = imagetonumpy(m) + # put image to scale + tonemap(nar, br.depth_exponent) + nar = 1-nar # reverse z buffer+ add something + print("Range:", nar.min(), nar.max()) + if nar.min() - nar.max() == 0: + raise ReliefError( + "Input image is blank - check you have the correct view layer or input image set.") + + gx = nar.copy() + gx.fill(0) + gx[:-1, :] = nar[1:, :]-nar[:-1, :] + gy = nar.copy() + gy.fill(0) + gy[:, :-1] = nar[:, 1:]-nar[:, :-1] + + # it' ok, we can treat neg and positive silh separately here: + a = br.attenuation + planar = nar < (nar.min()+0.0001) # numpy.logical_or(silhxplanar,silhyplanar)# + # sqrt for silhouettes recovery: + sqrarx = numpy.abs(gx) + for iter in range(0, br.silhouette_exponent): + sqrarx = numpy.sqrt(sqrarx) + sqrary = numpy.abs(gy) + for iter in range(0, br.silhouette_exponent): + sqrary = numpy.sqrt(sqrary) + + # detect and also recover silhouettes: + silhxpos = gx > silh_thres + print("*** silhxpos is %s" % silhxpos) + gx = gx*(~silhxpos)+recover_silh*(silhxpos*silh_thres*silh_scale)*sqrarx + silhxneg = gx < -silh_thres + gx = gx*(~silhxneg)-recover_silh*(silhxneg*silh_thres*silh_scale)*sqrarx + silhx = numpy.logical_or(silhxpos, silhxneg) + gx = gx*silhx+(1.0/a*numpy.log(1.+a*(gx)))*(~silhx) # attenuate + + # if br.fade_distant_objects: + # gx*=(nar) + # gy*=(nar) + + silhypos = gy > silh_thres + gy = gy*(~silhypos)+recover_silh*(silhypos*silh_thres*silh_scale)*sqrary + silhyneg = gy < -silh_thres + gy = gy*(~silhyneg)-recover_silh*(silhyneg*silh_thres*silh_scale)*sqrary + silhy = numpy.logical_or(silhypos, silhyneg) # both silh + gy = gy*silhy+(1.0/a*numpy.log(1.+a*(gy)))*(~silhy) # attenuate + + # now scale slopes... + if br.gradient_scaling_mask_use: + gx *= mask + gy *= mask + + # + # print(silhx) + # silhx=abs(gx)>silh_thres + # gx=gx*(-silhx) + # silhy=abs(gy)>silh_thres + # gy=gy*(-silhy) + + divg = gx+gy + divg[1:, :] = divg[1:, :]-gx[:-1, :] # subtract x + divg[:, 1:] = divg[:, 1:]-gy[:, :-1] # subtract y + + if br.detail_enhancement_use: # fourier stuff here!disabled by now + print("detail enhancement") + rows, cols = gx.shape + crow, ccol = int(rows/2), int(cols/2) + # dist=int(br.detail_enhancement_freq*gx.shape[0]/(2)) + # bandwidth=.1 + # dist= + divgmin = divg.min() + divg += divgmin + divgf = numpy.fft.fft2(divg) + divgfshift = numpy.fft.fftshift(divgf) + #mspectrum = 20*numpy.log(numpy.abs(divgfshift)) + # numpytoimage(mspectrum,'mspectrum') + mask = divg.copy() + pos = numpy.array((crow, ccol)) + + # bpy.context.scene.view_settings.curve_mapping.initialize() + # cur=bpy.context.scene.view_settings.curve_mapping.curves[0] + def filterwindow(x, y, cx=0, cy=0): # , curve=None): + return abs((cx-x))+abs((cy-y)) + # v=(abs((cx-x)/(cx))+abs((cy-y)/(cy))) + # return v + + mask = numpy.fromfunction(filterwindow, divg.shape, cx=crow, cy=ccol) # , curve=cur) + mask = numpy.sqrt(mask) + # for x in range(mask.shape[0]): + # for y in range(mask.shape[1]): + # mask[x,y]=cur.evaluate(mask[x,y]) + maskmin = mask.min() + maskmax = mask.max() + mask = (mask-maskmin)/(maskmax-maskmin) + mask *= br.detail_enhancement_amount + mask += 1-mask.max() + # mask+=1 + mask[crow-1:crow+1, ccol-1:ccol+1] = 1 # to preserve basic freqencies. + # numpytoimage(mask,'mask') + divgfshift = divgfshift*mask + divgfshift = numpy.fft.ifftshift(divgfshift) + divg = numpy.abs(numpy.fft.ifft2(divgfshift)) + divg -= divgmin + divg = -divg + print("detail enhancement finished") + + levels = 0 + mins = min(nar.shape[0], nar.shape[1]) + while (mins >= MINS): + levels += 1 + mins = mins/2 + + target = numpy.zeros_like(divg) + + solve_pde_multigrid(divg, target, vcycleiterations, linbcgiterations, + smoothiterations, mins, levels, useplanar, planar) + + tonemap(target, 1) + + buildMesh(target, br) # ipath=bpy.path.abspath(i.filepath)[:-len(bpy.path.basename(i.filepath))]+br.output_image_name+'.exr' # numpysave(target,ipath) - t=time.time()-t - print('total time:'+ str(t)+'\n') - #numpytoimage(target,br.output_image_name) + t = time.time()-t + print('total time:' + str(t)+'\n') + # numpytoimage(target,br.output_image_name) class BasReliefsettings(bpy.types.PropertyGroup): - use_image_source: bpy.props.BoolProperty(name="Use image source",description="", default=False) - source_image_name: bpy.props.StringProperty(name='Image source', description='image source') - view_layer_name: bpy.props.StringProperty(name='View layer source',description='Make a bas-relief from whatever is on this view layer') - bit_diameter: FloatProperty(name="Diameter of ball end in mm", description="Diameter of bit which will be used for carving", min=0.01, max=50.0, default=3.175, precision=PRECISION) - pass_per_radius: bpy.props.IntProperty(name="Passes per radius", description="Amount of passes per radius\n(more passes, more mesh precision)",default=2, min=1, max=10) - widthmm: bpy.props.IntProperty(name="Desired width in mm", default=200, min=5, max=4000) - heightmm: bpy.props.IntProperty(name="Desired height in mm", default=150, min=5, max=4000) - thicknessmm: bpy.props.IntProperty(name="Thickness in mm", default=15, min=5, max=100) - - justifyx: bpy.props.EnumProperty(name="X",items=[('1', 'Left','', 0),('-0.5', 'Centered','', 1),('-1', 'Right','', 2)],default='-1') - justifyy: bpy.props.EnumProperty(name="Y",items=[('1', 'Bottom','', 0),('-0.5', 'Centered','', 2),('-1', 'Top','', 1),],default='-1') - justifyz: bpy.props.EnumProperty(name="Z",items=[('-1', 'Below 0','', 0),('-0.5', 'Centered','', 2),('1', 'Above 0','', 1),],default='-1') - - depth_exponent: FloatProperty(name="Depth exponent", description="Initial depth map is taken to this power. Higher = sharper relief",min=0.5,max=10.0,default=1.0,precision=PRECISION) - - silhouette_threshold: FloatProperty(name="Silhouette threshold", description="Silhouette threshold", min=0.000001, max=1.0, default=0.003, precision=PRECISION) - recover_silhouettes: bpy.props.BoolProperty(name="Recover silhouettes",description="", default=True) - silhouette_scale: FloatProperty(name="Silhouette scale", description="Silhouette scale", min=0.000001, max=5.0, default=0.3, precision=PRECISION) - silhouette_exponent: bpy.props.IntProperty(name="Silhouette square exponent", description="If lower, true depht distances between objects will be more visibe in the relief", default=3, min=0, max=5) - attenuation: FloatProperty(name="Gradient attenuation", description="Gradient attenuation", min=0.000001, max=100.0, default=1.0, precision=PRECISION) - min_gridsize: bpy.props.IntProperty(name="Minimum grid size", default=16, min=2, max=512) - smooth_iterations: bpy.props.IntProperty(name="Smooth iterations", default=1, min=1, max=64) - vcycle_iterations: bpy.props.IntProperty(name="V-cycle iterations",description="set up higher for plananr constraint", default=2, min=1, max=128) - linbcg_iterations: bpy.props.IntProperty(name="Linbcg iterations",description="set lower for flatter relief, and when using planar constraint", default=5, min=1, max=64) - use_planar: bpy.props.BoolProperty(name="Use planar constraint",description="", default=False) - gradient_scaling_mask_use: bpy.props.BoolProperty(name="Scale gradients with mask",description="", default=False) - decimate_ratio: FloatProperty(name="Decimate Ratio", description="Simplify the mesh using the Decimate modifier. The lower the value the more simplyfied", min=0.01, max=1.0, default=0.1, precision=PRECISION) - - - gradient_scaling_mask_name: bpy.props.StringProperty(name='Scaling mask name', description='mask name') - scale_down_before_use: bpy.props.BoolProperty(name="Scale down image before processing",description="", default=False) - scale_down_before: FloatProperty(name="Image scale", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) - detail_enhancement_use: bpy.props.BoolProperty(name="Enhance details ",description="enhance details by frequency analysis", default=False) - #detail_enhancement_freq=FloatProperty(name="frequency limit", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) - detail_enhancement_amount: FloatProperty(name="amount", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) - - advanced: bpy.props.BoolProperty(name="Advanced options",description="show advanced options", default=True) + use_image_source: bpy.props.BoolProperty(name="Use image source", description="", default=False) + source_image_name: bpy.props.StringProperty(name='Image source', description='image source') + view_layer_name: bpy.props.StringProperty( + name='View layer source', description='Make a bas-relief from whatever is on this view layer') + bit_diameter: FloatProperty(name="Diameter of ball end in mm", description="Diameter of bit which will be used for carving", + min=0.01, max=50.0, default=3.175, precision=PRECISION) + pass_per_radius: bpy.props.IntProperty( + name="Passes per radius", description="Amount of passes per radius\n(more passes, more mesh precision)", default=2, min=1, max=10) + widthmm: bpy.props.IntProperty(name="Desired width in mm", default=200, min=5, max=4000) + heightmm: bpy.props.IntProperty(name="Desired height in mm", default=150, min=5, max=4000) + thicknessmm: bpy.props.IntProperty(name="Thickness in mm", default=15, min=5, max=100) + + justifyx: bpy.props.EnumProperty(name="X", items=[( + '1', 'Left', '', 0), ('-0.5', 'Centered', '', 1), ('-1', 'Right', '', 2)], default='-1') + justifyy: bpy.props.EnumProperty(name="Y", items=[( + '1', 'Bottom', '', 0), ('-0.5', 'Centered', '', 2), ('-1', 'Top', '', 1), ], default='-1') + justifyz: bpy.props.EnumProperty(name="Z", items=[( + '-1', 'Below 0', '', 0), ('-0.5', 'Centered', '', 2), ('1', 'Above 0', '', 1), ], default='-1') + + depth_exponent: FloatProperty(name="Depth exponent", description="Initial depth map is taken to this power. Higher = sharper relief", + min=0.5, max=10.0, default=1.0, precision=PRECISION) + + silhouette_threshold: FloatProperty( + name="Silhouette threshold", description="Silhouette threshold", min=0.000001, max=1.0, default=0.003, precision=PRECISION) + recover_silhouettes: bpy.props.BoolProperty( + name="Recover silhouettes", description="", default=True) + silhouette_scale: FloatProperty(name="Silhouette scale", description="Silhouette scale", + min=0.000001, max=5.0, default=0.3, precision=PRECISION) + silhouette_exponent: bpy.props.IntProperty( + name="Silhouette square exponent", description="If lower, true depht distances between objects will be more visibe in the relief", default=3, min=0, max=5) + attenuation: FloatProperty(name="Gradient attenuation", description="Gradient attenuation", + min=0.000001, max=100.0, default=1.0, precision=PRECISION) + min_gridsize: bpy.props.IntProperty(name="Minimum grid size", default=16, min=2, max=512) + smooth_iterations: bpy.props.IntProperty(name="Smooth iterations", default=1, min=1, max=64) + vcycle_iterations: bpy.props.IntProperty( + name="V-cycle iterations", description="set up higher for plananr constraint", default=2, min=1, max=128) + linbcg_iterations: bpy.props.IntProperty( + name="Linbcg iterations", description="set lower for flatter relief, and when using planar constraint", default=5, min=1, max=64) + use_planar: bpy.props.BoolProperty(name="Use planar constraint", description="", default=False) + gradient_scaling_mask_use: bpy.props.BoolProperty( + name="Scale gradients with mask", description="", default=False) + decimate_ratio: FloatProperty(name="Decimate Ratio", description="Simplify the mesh using the Decimate modifier. The lower the value the more simplyfied", + min=0.01, max=1.0, default=0.1, precision=PRECISION) + + gradient_scaling_mask_name: bpy.props.StringProperty( + name='Scaling mask name', description='mask name') + scale_down_before_use: bpy.props.BoolProperty( + name="Scale down image before processing", description="", default=False) + scale_down_before: FloatProperty( + name="Image scale", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) + detail_enhancement_use: bpy.props.BoolProperty( + name="Enhance details ", description="enhance details by frequency analysis", default=False) + #detail_enhancement_freq=FloatProperty(name="frequency limit", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) + detail_enhancement_amount: FloatProperty( + name="amount", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) + + advanced: bpy.props.BoolProperty( + name="Advanced options", description="show advanced options", default=True) + class BASRELIEF_Panel(bpy.types.Panel): - """Bas relief panel""" - bl_label = "Bas relief" - bl_idname = "WORLD_PT_BASRELIEF" - - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - - COMPAT_ENGINES = {'BLENDERCAM_RENDER'} - - #def draw_header(self, context): - # self.layout.menu("CAM_CUTTER_MT_presets", text="CAM Cutter") - @classmethod - def poll(cls, context): - rd = context.scene.render - return rd.engine in cls.COMPAT_ENGINES - - def draw(self, context): - layout = self.layout - #print(dir(layout)) - s=bpy.context.scene - - br=s.basreliefsettings - - #if br: - #cutter preset - layout.operator("scene.calculate_bas_relief", text="Calculate relief") - layout.prop(br,'advanced') - layout.prop(br,'use_image_source') - if br.use_image_source: - layout.prop_search(br,'source_image_name', bpy.data, "images") - else: - layout.prop_search(br,'view_layer_name',bpy.context.scene,"view_layers") - layout.prop(br,'depth_exponent') - layout.label(text="Project parameters") - layout.prop(br,'bit_diameter') - layout.prop(br,'pass_per_radius') - layout.prop(br,'widthmm') - layout.prop(br,'heightmm') - layout.prop(br,'thicknessmm') - - layout.label(text="Justification") - layout.prop(br,'justifyx') - layout.prop(br,'justifyy') - layout.prop(br,'justifyz') - - layout.label(text="Silhouette") - layout.prop(br,'silhouette_threshold') - layout.prop(br,'recover_silhouettes') - if br.recover_silhouettes: - layout.prop(br,'silhouette_scale') - if br.advanced: - layout.prop(br,'silhouette_exponent') - #layout.template_curve_mapping(br,'curva') - if br.advanced: - #layout.prop(br,'attenuation') - layout.prop(br,'min_gridsize') - layout.prop(br,'smooth_iterations') - layout.prop(br,'vcycle_iterations') - layout.prop(br,'linbcg_iterations') - layout.prop(br,'use_planar') - layout.prop(br,'decimate_ratio') - - - layout.prop(br,'gradient_scaling_mask_use') - if br.advanced: - if br.gradient_scaling_mask_use: - layout.prop_search(br,'gradient_scaling_mask_name', bpy.data, "images") - layout.prop(br,'detail_enhancement_use') - if br.detail_enhancement_use: - #layout.prop(br,'detail_enhancement_freq') - layout.prop(br,'detail_enhancement_amount') - #print(dir(layout)) - #layout.prop(s.view_settings.curve_mapping,"curves") - #layout.label('Frequency scaling:') - #s.view_settings.curve_mapping.clip_max_y=2 - - #layout.template_curve_mapping(s.view_settings, "curve_mapping") - - #layout.prop(br,'scale_down_before_use') - #if br.scale_down_before_use: - # layout.prop(br,'scale_down_before') + """Bas relief panel""" + bl_label = "Bas relief" + bl_idname = "WORLD_PT_BASRELIEF" + + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + + COMPAT_ENGINES = {'BLENDERCAM_RENDER'} + + # def draw_header(self, context): + # self.layout.menu("CAM_CUTTER_MT_presets", text="CAM Cutter") + @classmethod + def poll(cls, context): + rd = context.scene.render + return rd.engine in cls.COMPAT_ENGINES + + def draw(self, context): + layout = self.layout + # print(dir(layout)) + s = bpy.context.scene + + br = s.basreliefsettings + + # if br: + # cutter preset + layout.operator("scene.calculate_bas_relief", text="Calculate relief") + layout.prop(br, 'advanced') + layout.prop(br, 'use_image_source') + if br.use_image_source: + layout.prop_search(br, 'source_image_name', bpy.data, "images") + else: + layout.prop_search(br, 'view_layer_name', bpy.context.scene, "view_layers") + layout.prop(br, 'depth_exponent') + layout.label(text="Project parameters") + layout.prop(br, 'bit_diameter') + layout.prop(br, 'pass_per_radius') + layout.prop(br, 'widthmm') + layout.prop(br, 'heightmm') + layout.prop(br, 'thicknessmm') + + layout.label(text="Justification") + layout.prop(br, 'justifyx') + layout.prop(br, 'justifyy') + layout.prop(br, 'justifyz') + + layout.label(text="Silhouette") + layout.prop(br, 'silhouette_threshold') + layout.prop(br, 'recover_silhouettes') + if br.recover_silhouettes: + layout.prop(br, 'silhouette_scale') + if br.advanced: + layout.prop(br, 'silhouette_exponent') + # layout.template_curve_mapping(br,'curva') + if br.advanced: + # layout.prop(br,'attenuation') + layout.prop(br, 'min_gridsize') + layout.prop(br, 'smooth_iterations') + layout.prop(br, 'vcycle_iterations') + layout.prop(br, 'linbcg_iterations') + layout.prop(br, 'use_planar') + layout.prop(br, 'decimate_ratio') + + layout.prop(br, 'gradient_scaling_mask_use') + if br.advanced: + if br.gradient_scaling_mask_use: + layout.prop_search(br, 'gradient_scaling_mask_name', bpy.data, "images") + layout.prop(br, 'detail_enhancement_use') + if br.detail_enhancement_use: + # layout.prop(br,'detail_enhancement_freq') + layout.prop(br, 'detail_enhancement_amount') + # print(dir(layout)) + # layout.prop(s.view_settings.curve_mapping,"curves") + #layout.label('Frequency scaling:') + # s.view_settings.curve_mapping.clip_max_y=2 + + #layout.template_curve_mapping(s.view_settings, "curve_mapping") + + # layout.prop(br,'scale_down_before_use') + # if br.scale_down_before_use: + # layout.prop(br,'scale_down_before') + class ReliefError(Exception): - pass + pass + class DoBasRelief(bpy.types.Operator): - """calculate Bas relief""" - bl_idname = "scene.calculate_bas_relief" - bl_label = "calculate Bas relief" - bl_options = {'REGISTER', 'UNDO'} - - processes=[] - - def execute(self, context): - s=bpy.context.scene - br=s.basreliefsettings - if not br.use_image_source and br.view_layer_name=="": - br.view_layer_name=bpy.context.view_layer.name - - try: - renderScene(br.widthmm,br.heightmm,br.bit_diameter,br.pass_per_radius,not br.use_image_source,br.view_layer_name) - except ReliefError as e: - self.report({"ERROR"}, str(e)) - return {"CANCELLED"} - - try: - relief(br) - except ReliefError as e: - self.report({"ERROR"}, str(e)) - return {"CANCELLED"} - return {'FINISHED'} + """calculate Bas relief""" + bl_idname = "scene.calculate_bas_relief" + bl_label = "calculate Bas relief" + bl_options = {'REGISTER', 'UNDO'} + + processes = [] + + def execute(self, context): + s = bpy.context.scene + br = s.basreliefsettings + if not br.use_image_source and br.view_layer_name == "": + br.view_layer_name = bpy.context.view_layer.name + + try: + renderScene(br.widthmm, br.heightmm, br.bit_diameter, br.pass_per_radius, + not br.use_image_source, br.view_layer_name) + except ReliefError as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + + try: + relief(br) + except ReliefError as e: + self.report({"ERROR"}, str(e)) + return {"CANCELLED"} + return {'FINISHED'} + class ProblemAreas(bpy.types.Operator): - """find Bas relief Problem areas""" - bl_idname = "scene.problemareas_bas_relief" - bl_label = "problem areas Bas relief" - bl_options = {'REGISTER', 'UNDO'} + """find Bas relief Problem areas""" + bl_idname = "scene.problemareas_bas_relief" + bl_label = "problem areas Bas relief" + bl_options = {'REGISTER', 'UNDO'} - processes=[] + processes = [] - #@classmethod - #def poll(cls, context): - # return context.active_object is not None + # @classmethod + # def poll(cls, context): + # return context.active_object is not None - def execute(self, context): - s=bpy.context.scene - br=s.basreliefsettings - problemAreas(br) - return {'FINISHED'} + def execute(self, context): + s = bpy.context.scene + br = s.basreliefsettings + problemAreas(br) + return {'FINISHED'} def get_panels(): - return( - BasReliefsettings, - BASRELIEF_Panel, - DoBasRelief, - ProblemAreas - ) + return( + BasReliefsettings, + BASRELIEF_Panel, + DoBasRelief, + ProblemAreas + ) + def register(): - for p in get_panels(): - bpy.utils.register_class(p) - s=bpy.types.Scene - s.basreliefsettings = bpy.props.PointerProperty(type=BasReliefsettings) + for p in get_panels(): + bpy.utils.register_class(p) + s = bpy.types.Scene + s.basreliefsettings = bpy.props.PointerProperty(type=BasReliefsettings) -def unregister(): - for p in get_panels(): - bpy.utils.unregister_class(p) - s=bpy.types.Scene - del s.basreliefsettings +def unregister(): + for p in get_panels(): + bpy.utils.unregister_class(p) + s = bpy.types.Scene + del s.basreliefsettings diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index cb57a8d5b..dae8b3ae8 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -138,7 +138,7 @@ def useBridges(ch, o): vi = 0 newpoints = [] - ch_points=ch.get_points_np() + ch_points = ch.get_points_np() p1 = sgeometry.Point(ch_points[0]) startinside = o.bridgespoly.contains(p1) interrupted = False @@ -169,7 +169,6 @@ def useBridges(ch, o): if o.bridgespoly_boundary_prep.intersects(l): intersections = o.bridgespoly_boundary.intersection(l) - else: intersections = sgeometry.GeometryCollection() diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py index d4b1a4769..5427b31cc 100644 --- a/scripts/addons/cam/chunk.py +++ b/scripts/addons/cam/chunk.py @@ -26,7 +26,7 @@ from cam import polygon_utils_cam from cam.simple import * from cam.exception import CamException -from cam.numba_wrapper import jit,prange +from cam.numba_wrapper import jit, prange import math import numpy as np @@ -46,42 +46,45 @@ def Rotate_pbyp(originp, p, ang): # rotate point around another point with angl rot_p = [qx, qy, oz] return rot_p -@jit(nopython=True,parallel=True,fastmath=True,cache=True) -def _internalXyDistanceTo(ourpoints,theirpoints,cutoff): - v1=ourpoints[0] - v2=theirpoints[0] + +@jit(nopython=True, parallel=True, fastmath=True, cache=True) +def _internalXyDistanceTo(ourpoints, theirpoints, cutoff): + v1 = ourpoints[0] + v2 = theirpoints[0] minDistSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 - cutoffSq= cutoff**2 + cutoffSq = cutoff**2 for v1 in ourpoints: for v2 in theirpoints: - distSq= (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 - if distSq2 and np.array_equal(self.points[0],self.points[-1]): + chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) + if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): chunk.closed = True if self.depth is not None: chunk.depth = self.depth - + return chunk # an actual chunk - stores points as numpy arrays + + class camPathChunk: # parents=[] # children=[] @@ -91,11 +94,11 @@ class camPathChunk: def __init__(self, inpoints, startpoints=None, endpoints=None, rotations=None): # name this as _points so nothing external accesses it directly # for 3 axes, this is only storage of points. For N axes, here go the sampled points - if len(inpoints)==0: - self.points = np.empty(shape=(0,3)) + if len(inpoints) == 0: + self.points = np.empty(shape=(0, 3)) else: - self.points = np.array(inpoints) - self.poly = None # get polygon just in time + self.points = np.array(inpoints) + self.poly = None # get polygon just in time self.simppoly = None if startpoints: self.startpoints = startpoints # from where the sweep test begins, but also retract point for given path @@ -121,29 +124,28 @@ def __init__(self, inpoints, startpoints=None, endpoints=None, rotations=None): def update_poly(self): if len(self.points) > 2: - self.poly = sgeometry.Polygon(self.points[:,0:2]) + self.poly = sgeometry.Polygon(self.points[:, 0:2]) else: self.poly = sgeometry.Polygon() - - def get_point(self,n): + def get_point(self, n): return self.points[n].tolist() - def get_points(self): return self.points.tolist() def get_points_np(self): return self.points - def set_points(self,points): - self.points=np.array(points) + def set_points(self, points): + self.points = np.array(points) def count(self): return len(self.points) def copy(self): - nchunk = camPathChunk(inpoints=self.points.copy(), startpoints=self.startpoints, endpoints=self.endpoints, rotations=self.rotations) + nchunk = camPathChunk(inpoints=self.points.copy(), startpoints=self.startpoints, + endpoints=self.endpoints, rotations=self.rotations) nchunk.closed = self.closed nchunk.children = self.children nchunk.parents = self.parents @@ -152,38 +154,36 @@ def copy(self): return nchunk def shift(self, x, y, z): - self.points = self.points + np.array([x,y,z]) + self.points = self.points + np.array([x, y, z]) for i, p in enumerate(self.startpoints): self.startpoints[i] = (p[0] + x, p[1] + y, p[2] + z) for i, p in enumerate(self.endpoints): self.endpoints[i] = (p[0] + x, p[1] + y, p[2] + z) - def setZ(self, z,if_bigger=False): + def setZ(self, z, if_bigger=False): if if_bigger: - self.points[:,2]=z if z>self.points[:,2] else self.points[:,2] + self.points[:, 2] = z if z > self.points[:, 2] else self.points[:, 2] else: - self.points[:,2]=z - + self.points[:, 2] = z def offsetZ(self, z): - self.points[:,2]+=z + self.points[:, 2] += z def flipX(self, x_centre): - self.points[:,0]= x_centre - self.points[:,0] - + self.points[:, 0] = x_centre - self.points[:, 0] def isbelowZ(self, z): - return np.any(self.points[:,2]2 points # simplify them if they aren't already, to speed up distance finding if self.simppoly is None: - self.simppoly=self.poly.simplify(0.0003).boundary + self.simppoly = self.poly.simplify(0.0003).boundary if other.simppoly is None: - other.simppoly=other.poly.simplify(0.0003).boundary + other.simppoly = other.poly.simplify(0.0003).boundary return self.simppoly.distance(other.simppoly) else: # this is the old method, preferably should be replaced in most cases except parallel # where this method works probably faster. # print('warning, sorting will be slow due to bad parenting in parentChildDist') - return _internalXyDistanceTo(self.points,other.points,cutoff) + return _internalXyDistanceTo(self.points, other.points, cutoff) def adaptdist(self, pos, o): # reorders chunk so that it starts at the closest point to pos. if self.closed: - dist_sq = (pos[0]-self.points[:,0])**2 + (pos[1]-self.points[:,1])**2 + dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 point_idx = np.argmin(dist_sq) - new_points = np.concatenate((self.points[point_idx:],self.points[:point_idx+1])) - self.points=new_points + new_points = np.concatenate((self.points[point_idx:], self.points[:point_idx+1])) + self.points = new_points else: if o.movement.type == 'MEANDER': d1 = dist2d(pos, self.points[0]) d2 = dist2d(pos, self.points[-1]) if d2 < d1: - self.points=np.flip(self.points,axis=0) + self.points = np.flip(self.points, axis=0) def getNextClosest(self, o, pos): # finds closest chunk that can be milled, when inside sorting hierarchy. @@ -281,39 +279,39 @@ def getNextClosest(self, o, pos): def getLength(self): # computes length of the chunk - in 3d - point_differences=self.points[0:-1,:] - self.points[1:,:] - distances=np.linalg.norm(point_differences,axis=1) + point_differences = self.points[0:-1, :] - self.points[1:, :] + distances = np.linalg.norm(point_differences, axis=1) self.length = np.sum(distances) def reverse(self): - self.points=np.flip(self.points,axis=0) + self.points = np.flip(self.points, axis=0) self.startpoints.reverse() self.endpoints.reverse() self.rotations.reverse() def pop(self, index): - print("WARNING: Popping from chunk is slow",self,index) - self.points=np.concatenate((self.points[0:index],self.points[index+1:]),axis=0) + print("WARNING: Popping from chunk is slow", self, index) + self.points = np.concatenate((self.points[0:index], self.points[index+1:]), axis=0) if len(self.startpoints) > 0: self.startpoints.pop(index) self.endpoints.pop(index) self.rotations.pop(index) def dedupePoints(self): - if len(self.points)>1: - keep_points=np.empty(self.points.shape[0],dtype=bool) - keep_points[0]=True - diff_points=np.sum((self.points[1:]-self.points[:1])**2,axis=1) - keep_points[1:]=diff_points>0.000000001 - self.points=self.points[keep_points,:] - - - def insert(self,at_index,point,startpoint=None, endpoint=None, rotation=None): - self.append(point,startpoint=startpoint,endpoint=endpoint,rotation=rotation,at_index=at_index) - - def append(self, point, startpoint=None, endpoint=None, rotation=None,at_index=None): + if len(self.points) > 1: + keep_points = np.empty(self.points.shape[0], dtype=bool) + keep_points[0] = True + diff_points = np.sum((self.points[1:]-self.points[:1])**2, axis=1) + keep_points[1:] = diff_points > 0.000000001 + self.points = self.points[keep_points, :] + + def insert(self, at_index, point, startpoint=None, endpoint=None, rotation=None): + self.append(point, startpoint=startpoint, endpoint=endpoint, + rotation=rotation, at_index=at_index) + + def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index=None): if at_index is None: - self.points=np.concatenate((self.points,np.array([point]))) + self.points = np.concatenate((self.points, np.array([point]))) if startpoint is not None: self.startpoints.append(startpoint) if endpoint is not None: @@ -321,19 +319,20 @@ def append(self, point, startpoint=None, endpoint=None, rotation=None,at_index=N if rotation is not None: self.rotations.append(rotation) else: - self.points=np.concatenate((self.points[0:at_index],np.array([point]),self.points[at_index:])) + self.points = np.concatenate( + (self.points[0:at_index], np.array([point]), self.points[at_index:])) if startpoint is not None: - self.startpoints[at_index:at_index]=[startpoint] + self.startpoints[at_index:at_index] = [startpoint] if endpoint is not None: - self.endpoints[at_index:at_index]=[endpoint] + self.endpoints[at_index:at_index] = [endpoint] if rotation is not None: - self.rotations[at_index:at_index]=[rotation] + self.rotations[at_index:at_index] = [rotation] - def extend(self, points, startpoints=None, endpoints=None, rotations=None,at_index=None): - if len(points)==0: + def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_index=None): + if len(points) == 0: return if at_index is None: - self.points=np.concatenate((self.points,np.array(points))) + self.points = np.concatenate((self.points, np.array(points))) if startpoints is not None: self.startpoints.extend(startpoints) if endpoints is not None: @@ -341,25 +340,25 @@ def extend(self, points, startpoints=None, endpoints=None, rotations=None,at_ind if rotations is not None: self.rotations.extend(rotations) else: - self.points=np.concatenate((self.points[0:at_index],np.array(points),self.points[at_index:])) + self.points = np.concatenate( + (self.points[0:at_index], np.array(points), self.points[at_index:])) if startpoints is not None: - self.startpoints[at_index:at_index]=startpoints + self.startpoints[at_index:at_index] = startpoints if endpoints is not None: - self.endpoints[at_index:at_index]=endpoints + self.endpoints[at_index:at_index] = endpoints if rotations is not None: - self.rotations[at_index:at_index]=rotations + self.rotations[at_index:at_index] = rotations - def clip_points(self,minx,maxx,miny,maxy): + def clip_points(self, minx, maxx, miny, maxy): """ remove any points outside this range """ - included_values= (self.points[:,0]>=minx) and ((self.points[:,0]<=maxx) - and (self.points[:,1]>=maxy) and (self.points[:,1]<=maxy)) - self.points=self.points[included_values] - + included_values = (self.points[:, 0] >= minx) and ((self.points[:, 0] <= maxx) + and (self.points[:, 1] >= maxy) and (self.points[:, 1] <= maxy)) + self.points = self.points[included_values] def rampContour(self, zstart, zend, o): stepdown = zstart - zend - chunk_points=[] + chunk_points = [] estlength = (zstart - zend) / tan(o.movement.ramp_in_angle) self.getLength() ramplength = estlength # min(ch.length,estlength) @@ -456,7 +455,7 @@ def rampContour(self, zstart, zend, o): def rampZigZag(self, zstart, zend, o): # TODO: convert to numpy properly - if zend==None: + if zend == None: zend = self.points[0][2] chunk_points = [] # print(zstart,zend) @@ -505,7 +504,8 @@ def rampZigZag(self, zstart, zend, o): ramppoints.extend(negramppoints[1:]) traveled = 0.0 - chunk_points.append((self.points[0][0], self.points[0][1], max(self.points[0][2], zstart))) + chunk_points.append( + (self.points[0][0], self.points[0][1], max(self.points[0][2], zstart))) for r in range(turns): for p in range(0, len(ramppoints)): p1 = chunk_points[-1] @@ -514,7 +514,8 @@ def rampZigZag(self, zstart, zend, o): traveled += d ratio = traveled / ramplength znew = zstart - stepdown * ratio - chunk_points.append((p2[0], p2[1], max(p2[2], znew))) # max value here is so that it doesn't go + # max value here is so that it doesn't go + chunk_points.append((p2[0], p2[1], max(p2[2], znew))) # below surface in the case of 3d paths # chunks = setChunksZ([ch],zend) @@ -545,7 +546,8 @@ def rampZigZag(self, zstart, zend, o): else: zigzagtraveled = 0.0 haspoints = False - ramppoints = [(self.points[-1][0], self.points[-1][1], self.points[-1][2])] + ramppoints = [(self.points[-1][0], self.points[-1] + [1], self.points[-1][2])] i = len(self.points) - 2 while not haspoints: # print(i,zigzaglength,zigzagtraveled) @@ -591,8 +593,7 @@ def changePathStart(self, o): newstart = o.profile_start chunkamt = len(self.points) newstart = newstart % chunkamt - self.points=np.concatenate((self.points[newstart:],self.points[:newstart])) - + self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) def breakPathForLeadinLeadout(self, o): iradius = o.lead_in @@ -610,9 +611,12 @@ def breakPathForLeadinLeadout(self, o): if segmentLength > 2 * max(iradius, oradius): # Be certain there is enough room for the leadin and leadiout # add point on the line here - newpointx = (bpoint[0] + apoint[0]) / 2 # average of the two x points to find center - newpointy = (bpoint[1] + apoint[1]) / 2 # average of the two y points to find center - self.points=np.concatenate((self.points[:i+1],np.array([[newpointx, newpointy, apoint[2]]]),self.points[i+1:])) + # average of the two x points to find center + newpointx = (bpoint[0] + apoint[0]) / 2 + # average of the two y points to find center + newpointy = (bpoint[1] + apoint[1]) / 2 + self.points = np.concatenate( + (self.points[:i+1], np.array([[newpointx, newpointy, apoint[2]]]), self.points[i+1:])) def leadContour(self, o): perimeterDirection = 1 # 1 is clockwise, 0 is CCW @@ -689,7 +693,7 @@ def chunksCoherency(chunks): nchunk = camPathChunkBuilder() lastvec = vec if len(nchunk_points) > 4: # this is a testing threshold - nchunk.points=np.array(nchunk_points) + nchunk.points = np.array(nchunk_points) nchunks.append(nchunk) return nchunks @@ -704,13 +708,15 @@ def setChunksZ(chunks, z): # don't make this @jit parallel, because it sometimes gets called with small N # and the overhead of threading is too much. -@jit(nopython=True,fastmath=True,cache=True) -def _optimize_internal(points,keep_points,e,protect_vertical,protect_vertical_limit): + + +@jit(nopython=True, fastmath=True, cache=True) +def _optimize_internal(points, keep_points, e, protect_vertical, protect_vertical_limit): # inlined so that numba can optimize it nicely def _mag_sq(v1): return v1[0]**2 + v1[1]**2 + v1[2]**2 - def _dot_pr(v1,v2): + def _dot_pr(v1, v2): return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] def _applyVerticalLimit(v1, v2, cos_limit): @@ -719,7 +725,7 @@ def _applyVerticalLimit(v1, v2, cos_limit): if z > 0: # don't use this vector because dot product of 0,0,1 is trivially just v2[2] # vec_up = np.array([0, 0, 1]) - vec_diff= v1-v2 + vec_diff = v1-v2 vec_diff2 = v2-v1 vec_diff_mag = np.sqrt(_mag_sq(vec_diff)) # dot product = cos(angle) * mag1 * mag2 @@ -734,34 +740,35 @@ def _applyVerticalLimit(v1, v2, cos_limit): v2[0] = v1[0] v2[1] = v1[1] - cos_limit = cos( protect_vertical_limit ) + cos_limit = cos(protect_vertical_limit) prev_i = 0 - for i in range(1,points.shape[0]-1): + for i in range(1, points.shape[0]-1): v1 = points[prev_i] - v2=points[i+1] - vmiddle=points[i] + v2 = points[i+1] + vmiddle = points[i] line_direction = v2-v1 line_length = sqrt(_mag_sq(line_direction)) - if line_length==0: + if line_length == 0: # don't keep duplicate points - keep_points[i]=False + keep_points[i] = False continue # normalize line direction - line_direction*= (1.0/line_length) # N in formula below + line_direction *= (1.0/line_length) # N in formula below # X = A + tN (line formula) Distance to point P # A = v1, N = line_direction, P = vmiddle # distance = || (P - A) - ((P-A).N)N || point_offset = vmiddle - v1 - distance_sq = _mag_sq(point_offset - (line_direction * _dot_pr(point_offset,line_direction))) + distance_sq = _mag_sq(point_offset - (line_direction * + _dot_pr(point_offset, line_direction))) # compare on squared distance to save a sqrt if distance_sq < e*e: - keep_points[i]=False + keep_points[i] = False else: - keep_points[i]=True + keep_points[i] = True if protect_vertical: _applyVerticalLimit(points[prev_i], points[i], cos_limit) - prev_i=i + prev_i = i def optimizeChunk(chunk, operation): @@ -774,21 +781,23 @@ def optimizeChunk(chunk, operation): naxispoints = True protect_vertical = operation.movement.protect_vertical and operation.machine_axes == '3' - keep_points=np.full(points.shape[0],True) + keep_points = np.full(points.shape[0], True) # shape points need to be on line, # but we need to protect vertical - which # means changing point values # bits of this are moved from simple.py so that # numba can optimize as a whole - _optimize_internal(points,keep_points,operation.optimisation.optimize_threshold * 0.000001,protect_vertical,operation.movement.protect_vertical_limit) + _optimize_internal(points, keep_points, operation.optimisation.optimize_threshold * + 0.000001, protect_vertical, operation.movement.protect_vertical_limit) # now do numpy select by boolean array - chunk.points=points[keep_points] + chunk.points = points[keep_points] if naxispoints: # list comprehension so we don't have to do tons of appends - chunk.startpoints=[chunk.startpoints[i] for i,b in enumerate(keep_points) if b==True] - chunk.endpoints=[chunk.endpoints[i] for i,b in enumerate(keep_points) if b==True] - chunk.rotations=[chunk.rotations[i] for i,b in enumerate(keep_points) if b==True] + chunk.startpoints = [chunk.startpoints[i] + for i, b in enumerate(keep_points) if b == True] + chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] + chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] return chunk @@ -809,12 +818,12 @@ def limitChunks(chunks, o, closed = False nchunks.append(nch.to_chunk()) if nch1 is None: - nch1=nchunks[-1] + nch1 = nchunks[-1] nch = camPathChunkBuilder() elif sampled: nch.points.append(s) prevsampled = sampled - if len(nch.points) > 2 and closed and ch.closed and np.array_equal(ch.points[0] ,ch.points[-1]): + if len(nch.points) > 2 and closed and ch.closed and np.array_equal(ch.points[0], ch.points[-1]): nch.closed = True elif ch.closed and nch1 is not None and len(nch.points) > 1 and np.array_equal(nch.points[-1], nch1.points[0]): # here adds beginning of closed chunk to the end, if the chunks were split during limiting @@ -833,10 +842,10 @@ def parentChildPoly(parents, children, o): # hierarchy works like this: - children get milled first. for parent in parents: - if parent.poly is None: + if parent.poly is None: parent.update_poly() for child in children: - if child.poly is None: + if child.poly is None: child.update_poly() if child != parent: # and len(child.poly)>0 if parent.poly.contains(sgeometry.Point(child.poly.boundary.coords[0])): @@ -854,12 +863,12 @@ def parentChildDist(parents, children, o, distance=None): dlim = dlim * 2 else: dlim = distance - + for child in children: for parent in parents: isrelation = False if parent != child: - if parent.xyDistanceWithin(child,cutoff=dlim): + if parent.xyDistanceWithin(child, cutoff=dlim): parent.children.append(child) child.parents.append(parent) @@ -880,7 +889,7 @@ def chunksToShapely(chunks): # this does more cleve chunks to Poly with hierarc for ch in chunks: # first convert chunk to poly if len(ch.points) > 2: # pchunk=[] - ch.poly = sgeometry.Polygon(ch.points[:,0:2]) + ch.poly = sgeometry.Polygon(ch.points[:, 0:2]) if not ch.poly.is_valid: ch.poly = sgeometry.Polygon() else: @@ -1041,28 +1050,30 @@ def meshFromCurveToChunk(object): chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z lastvi = vi + 1 - chunk=chunk.to_chunk() + chunk = chunk.to_chunk() chunk.dedupePoints() - if chunk.count()>=1: - # dump single point chunks + if chunk.count() >= 1: + # dump single point chunks chunks.append(chunk) chunk = camPathChunkBuilder() progress('processing curve - FINISHED') vi = len(mesh.vertices) - 1 - chunk.points.append((mesh.vertices[vi].co.x + x, mesh.vertices[vi].co.y + y, mesh.vertices[vi].co.z + z)) + chunk.points.append((mesh.vertices[vi].co.x + x, + mesh.vertices[vi].co.y + y, mesh.vertices[vi].co.z + z)) if not (dk.isdisjoint([(vi, lastvi)])) or not (dk.isdisjoint([(lastvi, vi)])): chunk.closed = True chunk.points.append( (mesh.vertices[lastvi].co.x + x, mesh.vertices[lastvi].co.y + y, mesh.vertices[lastvi].co.z + z)) - chunk=chunk.to_chunk() + chunk = chunk.to_chunk() chunk.dedupePoints() - if chunk.count()>=1: - # dump single point chunks + if chunk.count() >= 1: + # dump single point chunks chunks.append(chunk) return chunks + def makeVisible(o): storage = [True, []] @@ -1096,10 +1107,11 @@ def meshFromCurve(o, use_modifiers=False): co = bpy.context.active_object - if co.type == 'FONT': # support for text objects is only and only here, just convert them to curves. + # support for text objects is only and only here, just convert them to curves. + if co.type == 'FONT': bpy.ops.object.convert(target='CURVE', keep_original=False) - elif co.type != 'CURVE': # curve must be a curve... - bpy.ops.object.delete() # delete temporary object + elif co.type != 'CURVE': # curve must be a curve... + bpy.ops.object.delete() # delete temporary object raise CamException("Source curve object must be of type CURVE") co.data.dimensions = '3D' co.data.bevel_depth = 0 diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index ddcd7741e..5fc5a5cd8 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -44,9 +44,9 @@ def getCutterBullet(o): type = o.cutter_type if type == 'END': - bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius= o.cutter_diameter / 2, - depth= o.cutter_diameter, end_fill_type='NGON', - align='WORLD', enter_editmode=False, location = CUTTER_OFFSET, + bpy.ops.mesh.primitive_cylinder_add(vertices=32, radius=o.cutter_diameter / 2, + depth=o.cutter_diameter, end_fill_type='NGON', + align='WORLD', enter_editmode=False, location=CUTTER_OFFSET, rotation=(0, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE @@ -57,29 +57,29 @@ def getCutterBullet(o): cutter.rigid_body.collision_shape = 'CYLINDER' elif type == 'BALLNOSE': - # ballnose ending used mainly when projecting from sides. - # the actual collision shape is capsule in this case. - bpy.ops.mesh.primitive_ico_sphere_add(subdivisions = 3, radius = o.cutter_diameter / 2, - align='WORLD', enter_editmode=False, - location = CUTTER_OFFSET, rotation=(0, 0, 0)) - cutter = bpy.context.active_object - cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') - bpy.ops.rigidbody.object_add(type='ACTIVE') - cutter = bpy.context.active_object - #cutter.dimensions.z = 0.2 * BULLET_SCALE # should be sufficient for now... 20 cm. - cutter.rigid_body.collision_shape = 'CAPSULE' - #bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + # ballnose ending used mainly when projecting from sides. + # the actual collision shape is capsule in this case. + bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=3, radius=o.cutter_diameter / 2, + align='WORLD', enter_editmode=False, + location=CUTTER_OFFSET, rotation=(0, 0, 0)) + cutter = bpy.context.active_object + cutter.scale *= BULLET_SCALE + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') + bpy.ops.rigidbody.object_add(type='ACTIVE') + cutter = bpy.context.active_object + # cutter.dimensions.z = 0.2 * BULLET_SCALE # should be sufficient for now... 20 cm. + cutter.rigid_body.collision_shape = 'CAPSULE' + #bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) elif type == 'VCARVE': angle = o.cutter_tip_angle s = math.tan(math.pi * (90 - angle / 2) / 180) / 2 # angles in degrees cone_d = o.cutter_diameter * s - bpy.ops.mesh.primitive_cone_add(vertices=32, radius1 = o.cutter_diameter / 2, radius2=0, - depth = cone_d, end_fill_type='NGON', - align='WORLD', enter_editmode=False, location = CUTTER_OFFSET, + bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=o.cutter_diameter / 2, radius2=0, + depth=cone_d, end_fill_type='NGON', + align='WORLD', enter_editmode=False, location=CUTTER_OFFSET, rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE @@ -92,12 +92,12 @@ def getCutterBullet(o): angle = o.cutter_tip_angle s = math.tan(math.pi * (90 - angle / 2) / 180) / 2 # angles in degrees - cylcone_d =(o.cutter_diameter - o.cylcone_diameter) * s - bpy.ops.mesh.primitive_cone_add(vertices = 32, radius1 = o.cutter_diameter / 2, - radius2 = o.cylcone_diameter / 2, - depth = cylcone_d, end_fill_type='NGON', - align = 'WORLD', enter_editmode = False, - location = CUTTER_OFFSET,rotation = (math.pi, 0, 0)) + cylcone_d = (o.cutter_diameter - o.cylcone_diameter) * s + bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=o.cutter_diameter / 2, + radius2=o.cylcone_diameter / 2, + depth=cylcone_d, end_fill_type='NGON', + align='WORLD', enter_editmode=False, + location=CUTTER_OFFSET, rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) @@ -112,23 +112,23 @@ def getCutterBullet(o): Ball_R = o.ball_radius/math.cos(angle) conedepth = (cutter_R - o.ball_radius)/math.tan(angle) bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), - Simple_Type='Point', use_cyclic_u=False) + Simple_Type='Point', use_cyclic_u=False) oy = Ball_R - for i in range(1 , 10): + for i in range(1, 10): ang = -i * (math.pi/2-angle) / 9 qx = math.sin(ang) * oy qy = oy - math.cos(ang) * oy - bpy.ops.curve.vertex_add(location=(qx , qy , 0)) + bpy.ops.curve.vertex_add(location=(qx, qy, 0)) conedepth += qy - bpy.ops.curve.vertex_add(location=(-cutter_R , conedepth , 0)) + bpy.ops.curve.vertex_add(location=(-cutter_R, conedepth, 0)) #bpy.ops.curve.vertex_add(location=(0 , conedepth , 0)) bpy.ops.object.editmode_toggle() bpy.ops.object.convert(target='MESH') - bpy.ops.transform.rotate(value = -math.pi / 2, orient_axis = 'X') + bpy.ops.transform.rotate(value=-math.pi / 2, orient_axis='X') bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) ob = bpy.context.active_object ob.name = "BallConeTool" - ob_scr = ob.modifiers.new(type='SCREW', name = 'scr') + ob_scr = ob.modifiers.new(type='SCREW', name='scr') ob_scr.angle = math.radians(-360) ob_scr.steps = 32 ob_scr.merge_threshold = 0 @@ -197,7 +197,8 @@ def subdivideLongEdges(ob, threshold): bpy.ops.mesh.subdivide(smoothness=0) if iter == 0: bpy.ops.mesh.select_all(action='SELECT') - bpy.ops.mesh.quads_convert_to_tris(quad_method='SHORTEST_DIAGONAL', ngon_method='BEAUTY') + bpy.ops.mesh.quads_convert_to_tris( + quad_method='SHORTEST_DIAGONAL', ngon_method='BEAUTY') bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.editmode_toggle() ob.update_from_editmode() @@ -266,7 +267,6 @@ def prepareBulletCollision(o): if active_collection in collisionob.users_collection: active_collection.objects.unlink(collisionob) - getCutterBullet(o) # machine objects scaling up to simulation scale diff --git a/scripts/addons/cam/constants.py b/scripts/addons/cam/constants.py index d93cb2f4c..595cab16f 100644 --- a/scripts/addons/cam/constants.py +++ b/scripts/addons/cam/constants.py @@ -6,6 +6,6 @@ CHIPLOAD_PRECISION = 10 -MAX_OPERATION_TIME = 3200000000 # seconds +MAX_OPERATION_TIME = 3200000000 # seconds G64_INCOMPATIBLE_MACHINES = ['GRBL'] diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index f80a44601..ef55b6a07 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -40,10 +40,14 @@ class CamCurveHatch(bpy.types.Operator): bl_label = "CrossHatch curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - angle: bpy.props.FloatProperty(name="angle", default=0, min=-math.pi/2, max=math.pi/2, precision=4, subtype="ANGLE") - distance: bpy.props.FloatProperty(name="spacing", default=0.015, min=0, max=3.0, precision=4, unit="LENGTH") - offset: bpy.props.FloatProperty(name="Margin", default=0.001, min=-1.0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height", default=0.000, min=-1.0, max=1.0, precision=4, unit="LENGTH") + angle: bpy.props.FloatProperty(name="angle", default=0, min=- + math.pi/2, max=math.pi/2, precision=4, subtype="ANGLE") + distance: bpy.props.FloatProperty(name="spacing", default=0.015, + min=0, max=3.0, precision=4, unit="LENGTH") + offset: bpy.props.FloatProperty(name="Margin", default=0.001, + min=-1.0, max=3.0, precision=4, unit="LENGTH") + height: bpy.props.FloatProperty(name="Height", default=0.000, + min=-1.0, max=1.0, precision=4, unit="LENGTH") amount: bpy.props.IntProperty(name="amount", default=10, min=1, max=10000) hull: bpy.props.BoolProperty(name="Convex Hull", default=False) contour: bpy.props.BoolProperty(name="Contour Curve", default=False) @@ -108,13 +112,15 @@ def execute(self, context): lines = MultiLineString(coords) # create a multilinestring shapely object rotated = affinity.rotate(lines, self.angle, use_radians=True) # rotate using shapely - translated = affinity.translate(rotated, xoff=centerx, yoff=centery) # move using shapely + translated = affinity.translate( + rotated, xoff=centerx, yoff=centery) # move using shapely simple.make_active('crosshatch_bound') bounds = simple.active_to_shapely_poly() if self.pocket_type == 'BOUNDS': - xing = translated.intersection(bounds) # Shapely detects intersections with the square bounds + # Shapely detects intersections with the square bounds + xing = translated.intersection(bounds) else: xing = translated.intersection(s.buffer(self.offset)) # Shapely detects intersections with the original curve or hull @@ -155,9 +161,12 @@ class CamCurvePlate(bpy.types.Operator): bl_label = "Sign plate" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - radius: bpy.props.FloatProperty(name="Corner Radius", default=.025, min=0, max=0.1, precision=4, unit="LENGTH") - width: bpy.props.FloatProperty(name="Width of plate", default=0.3048, min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of plate", default=0.457, min=0, max=3.0, precision=4, unit="LENGTH") + radius: bpy.props.FloatProperty(name="Corner Radius", default=.025, + min=0, max=0.1, precision=4, unit="LENGTH") + width: bpy.props.FloatProperty(name="Width of plate", default=0.3048, + min=0, max=3.0, precision=4, unit="LENGTH") + height: bpy.props.FloatProperty(name="Height of plate", default=0.457, + min=0, max=3.0, precision=4, unit="LENGTH") hole_diameter: bpy.props.FloatProperty(name="Hole diameter", default=0.01, min=0, max=3.0, precision=4, unit="LENGTH") hole_tolerance: bpy.props.FloatProperty(name="Hole V Tolerance", default=0.005, min=0, max=3.0, precision=4, @@ -169,11 +178,11 @@ class CamCurvePlate(bpy.types.Operator): hole_hamount: bpy.props.IntProperty(name="Hole horiz amount", default=1, min=0, max=50) resolution: bpy.props.IntProperty(name="Spline resolution", default=50, min=3, max=150) plate_type: EnumProperty(name='Type plate', - items=(('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), - ('COVE', 'Cove corner', 'Makes a plate with circles cut in each corner '), - ('BEVEL', 'Bevel corner', 'Makes a plate with beveled corners '), - ('OVAL', 'Elipse', 'Makes an oval plate')), - description='Type of Plate', default='ROUNDED') + items=(('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), + ('COVE', 'Cove corner', 'Makes a plate with circles cut in each corner '), + ('BEVEL', 'Bevel corner', 'Makes a plate with beveled corners '), + ('OVAL', 'Elipse', 'Makes an oval plate')), + description='Type of Plate', default='ROUNDED') def draw(self, context): layout = self.layout @@ -190,7 +199,6 @@ def draw(self, context): if self.plate_type in ["ROUNDED", "COVE", "BEVEL"]: layout.prop(self, 'radius') - def execute(self, context): left = -self.width / 2 + self.radius bottom = -self.height / 2 + self.radius @@ -198,21 +206,21 @@ def execute(self, context): top = -bottom if self.plate_type == "ROUNDED": - # create base + # create base bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(left, bottom, 0), scale=(1, 1, 1)) + location=(left, bottom, 0), scale=(1, 1, 1)) simple.active_name("_circ_LB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(right, bottom, 0), scale=(1, 1, 1)) + location=(right, bottom, 0), scale=(1, 1, 1)) simple.active_name("_circ_RB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(left, top, 0), scale=(1, 1, 1)) + location=(left, top, 0), scale=(1, 1, 1)) simple.active_name("_circ_LT") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(right, top, 0), scale=(1, 1, 1)) + location=(right, top, 0), scale=(1, 1, 1)) simple.active_name("_circ_RT") bpy.context.object.data.resolution_u = self.resolution @@ -223,25 +231,25 @@ def execute(self, context): elif self.plate_type == "OVAL": bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Ellipse', - Simple_a=self.width/2,Simple_b=self.height/2, use_cyclic_u=True, edit_mode=False) + Simple_a=self.width/2, Simple_b=self.height/2, use_cyclic_u=True, edit_mode=False) bpy.context.object.data.resolution_u = self.resolution simple.active_name("plate_base") elif self.plate_type == 'COVE': bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(left-self.radius, bottom-self.radius, 0), scale=(1, 1, 1)) + location=(left-self.radius, bottom-self.radius, 0), scale=(1, 1, 1)) simple.active_name("_circ_LB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(right+self.radius, bottom-self.radius, 0), scale=(1, 1, 1)) + location=(right+self.radius, bottom-self.radius, 0), scale=(1, 1, 1)) simple.active_name("_circ_RB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(left-self.radius, top+self.radius, 0), scale=(1, 1, 1)) + location=(left-self.radius, top+self.radius, 0), scale=(1, 1, 1)) simple.active_name("_circ_LT") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.primitive_bezier_circle_add(radius=self.radius, enter_editmode=False, align='WORLD', - location=(right+self.radius, top+self.radius, 0), scale=(1, 1, 1)) + location=(right+self.radius, top+self.radius, 0), scale=(1, 1, 1)) simple.active_name("_circ_RT") bpy.context.object.data.resolution_u = self.resolution @@ -252,27 +260,25 @@ def execute(self, context): edit_mode=False) simple.active_name("_base") - simple.difference("_","_base") - simple.rename("_base","plate_base") - - + simple.difference("_", "_base") + simple.rename("_base", "plate_base") elif self.plate_type == 'BEVEL': bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, location=(left-self.radius, bottom-self.radius, 0), - rotation=(0, 0, 0.785398),outputType='POLY', use_cyclic_u=True, edit_mode=False) + rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_LB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, location=(right+self.radius, bottom-self.radius, 0), - rotation=(0, 0, 0.785398),outputType='POLY', use_cyclic_u=True, edit_mode=False) + rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_RB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, location=(left-self.radius, top+self.radius, 0), - rotation=(0, 0, 0.785398),outputType='POLY', use_cyclic_u=True, edit_mode=False) + rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_LT") bpy.context.object.data.resolution_u = self.resolution @@ -280,7 +286,7 @@ def execute(self, context): bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, location=(right+self.radius, top+self.radius, 0), - rotation=(0, 0, 0.785398),outputType='POLY', use_cyclic_u=True, edit_mode=False) + rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_RT") bpy.context.object.data.resolution_u = self.resolution @@ -292,24 +298,24 @@ def execute(self, context): edit_mode=False) simple.active_name("_base") - simple.difference("_","_base") - simple.rename("_base","plate_base") - + simple.difference("_", "_base") + simple.rename("_base", "plate_base") if self.hole_diameter > 0 or self.hole_hamount > 0: bpy.ops.curve.primitive_bezier_circle_add(radius=self.hole_diameter / 2, enter_editmode=False, - align='WORLD', location=(0, self.hole_tolerance / 2, 0), - scale=(1, 1, 1)) + align='WORLD', location=(0, self.hole_tolerance / 2, 0), + scale=(1, 1, 1)) simple.active_name("_hole_Top") bpy.context.object.data.resolution_u = int(self.resolution / 4) if self.hole_tolerance > 0: bpy.ops.curve.primitive_bezier_circle_add(radius=self.hole_diameter / 2, enter_editmode=False, - align='WORLD', location=(0, -self.hole_tolerance / 2, 0), - scale=(1, 1, 1)) + align='WORLD', location=(0, -self.hole_tolerance / 2, 0), + scale=(1, 1, 1)) simple.active_name("_hole_Bottom") bpy.context.object.data.resolution_u = int(self.resolution / 4) - simple.select_multiple("_hole") # select everything starting with _hole and perform a convex hull on them + # select everything starting with _hole and perform a convex hull on them + simple.select_multiple("_hole") utils.polygonConvexHull(context) simple.active_name("plate_hole") simple.move(y=-self.hole_vdist / 2) @@ -344,7 +350,8 @@ def execute(self, context): simple.select_multiple("plate_") # select everything starting with plate_ - bpy.context.view_layer.objects.active = bpy.data.objects['plate_base'] # Make the plate base active + # Make the plate base active + bpy.context.view_layer.objects.active = bpy.data.objects['plate_base'] utils.polygonBoolean(context, "DIFFERENCE") # Remove holes from the base simple.remove_multiple("plate_") # Remove temporary base and holes simple.remove_multiple("_") @@ -355,18 +362,24 @@ def execute(self, context): return {'FINISHED'} + class CamCurveFlatCone(bpy.types.Operator): """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_flat_cone" bl_label = "Cone flat calculator" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - small_d: bpy.props.FloatProperty(name="small diameter", default=.025, min=0, max=0.1, precision=4, unit="LENGTH") - large_d: bpy.props.FloatProperty(name="large diameter", default=0.3048, min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of cone", default=0.457, min=0, max=3.0, precision=4, unit="LENGTH") - tab: bpy.props.FloatProperty(name="tab witdh", default=0.01, min=0, max=0.100, precision=4, unit="LENGTH") - intake: bpy.props.FloatProperty(name="intake diameter", default=0, min=0, max=0.200, precision=4, unit="LENGTH") - intake_skew: bpy.props.FloatProperty(name="intake_skew", default=1, min=0.1, max=4 ) + small_d: bpy.props.FloatProperty( + name="small diameter", default=.025, min=0, max=0.1, precision=4, unit="LENGTH") + large_d: bpy.props.FloatProperty( + name="large diameter", default=0.3048, min=0, max=3.0, precision=4, unit="LENGTH") + height: bpy.props.FloatProperty(name="Height of cone", default=0.457, + min=0, max=3.0, precision=4, unit="LENGTH") + tab: bpy.props.FloatProperty(name="tab witdh", default=0.01, min=0, + max=0.100, precision=4, unit="LENGTH") + intake: bpy.props.FloatProperty(name="intake diameter", default=0, + min=0, max=0.200, precision=4, unit="LENGTH") + intake_skew: bpy.props.FloatProperty(name="intake_skew", default=1, min=0.1, max=4) resolution: bpy.props.IntProperty(name="Resolution", default=12, min=5, max=200) def execute(self, context): @@ -404,11 +417,9 @@ def execute(self, context): simple.active_name('flat_cone') - return {'FINISHED'} - class CamCurveMortise(bpy.types.Operator): """Generates mortise along a curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_mortise" @@ -425,8 +436,10 @@ class CamCurveMortise(bpy.types.Operator): unit="LENGTH") plate_thickness: bpy.props.FloatProperty(name="Drawer plate thickness", default=0.00477, min=0.001, max=3.0, unit="LENGTH") - side_height: bpy.props.FloatProperty(name="side height", default=0.05, min=0.001, max=3.0, unit="LENGTH") - flex_pocket: bpy.props.FloatProperty(name="Flex pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH") + side_height: bpy.props.FloatProperty( + name="side height", default=0.05, min=0.001, max=3.0, unit="LENGTH") + flex_pocket: bpy.props.FloatProperty( + name="Flex pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH") top_bottom: bpy.props.BoolProperty(name="Side Top & bottom fingers", default=True) opencurve: bpy.props.BoolProperty(name="OpenCurve", default=False) adaptive: bpy.props.FloatProperty(name="Adaptive angle threshold", default=0.0, min=0.000, max=2, subtype="ANGLE", @@ -479,15 +492,19 @@ def execute(self, context): locations = joinery.variable_finger(c, length, self.min_finger_size, self.finger_size, self.plate_thickness, self.finger_tolerance, self.adaptive, True, self.double_adaptive) - joinery.create_flex_side(loop_length, self.side_height, self.plate_thickness, self.top_bottom) + joinery.create_flex_side(loop_length, self.side_height, + self.plate_thickness, self.top_bottom) if self.flex_pocket > 0: joinery.make_variable_flex_pocket(self.side_height, self.plate_thickness, self.flex_pocket, locations) else: - joinery.fixed_finger(c, length, self.finger_size, self.plate_thickness, self.finger_tolerance) - joinery.fixed_finger(c, length, self.finger_size, self.plate_thickness, self.finger_tolerance, True) - joinery.create_flex_side(loop_length, self.side_height, self.plate_thickness, self.top_bottom) + joinery.fixed_finger(c, length, self.finger_size, + self.plate_thickness, self.finger_tolerance) + joinery.fixed_finger(c, length, self.finger_size, + self.plate_thickness, self.finger_tolerance, True) + joinery.create_flex_side(loop_length, self.side_height, + self.plate_thickness, self.top_bottom) if self.flex_pocket > 0: joinery.make_flex_pocket(length, self.side_height, self.plate_thickness, self.finger_size, self.flex_pocket) @@ -578,9 +595,12 @@ class CamCurveDrawer(bpy.types.Operator): bl_label = "Drawer" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - depth: bpy.props.FloatProperty(name="Drawer Depth", default=0.2, min=0, max=1.0, precision=4, unit="LENGTH") - width: bpy.props.FloatProperty(name="Width of Drawer", default=0.125, min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of drawer", default=0.07, min=0, max=3.0, precision=4, unit="LENGTH") + depth: bpy.props.FloatProperty(name="Drawer Depth", default=0.2, + min=0, max=1.0, precision=4, unit="LENGTH") + width: bpy.props.FloatProperty(name="Width of Drawer", default=0.125, + min=0, max=3.0, precision=4, unit="LENGTH") + height: bpy.props.FloatProperty(name="Height of drawer", default=0.07, + min=0, max=3.0, precision=4, unit="LENGTH") finger_size: bpy.props.FloatProperty(name="Maximum Finger Size", default=0.015, min=0.005, max=3.0, precision=4, unit="LENGTH") finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4, @@ -623,7 +643,8 @@ def execute(self, context): bpy.context.object.data.resolution_u = 64 bpy.context.scene.cursor.location = (0, 0, 0) - joinery.vertical_finger(height_finger, self.drawer_plate_thickness, self.finger_tolerance, height_finger_amt) + joinery.vertical_finger(height_finger, self.drawer_plate_thickness, + self.finger_tolerance, height_finger_amt) joinery.horizontal_finger(width_finger, self.drawer_plate_thickness, self.finger_tolerance, width_finger_amt * 2) @@ -632,7 +653,8 @@ def execute(self, context): bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') # make drawer back - finger_pair = joinery.finger_pair("_vfa", self.width - self.drawer_plate_thickness - self.finger_inset * 2, 0) + finger_pair = joinery.finger_pair( + "_vfa", self.width - self.drawer_plate_thickness - self.finger_inset * 2, 0) simple.make_active('_wfa') fronth = bpy.context.active_object simple.make_active('_back') @@ -662,7 +684,8 @@ def execute(self, context): bpy.ops.transform.transform(mode='TRANSLATION', value=(0.0, 2 * self.height, 0.0, 0.0)) simple.make_active('drawer_back') - bpy.ops.transform.transform(mode='TRANSLATION', value=(self.width + 0.01, 2 * self.height, 0.0, 0.0)) + bpy.ops.transform.transform(mode='TRANSLATION', value=( + self.width + 0.01, 2 * self.height, 0.0, 0.0)) # make side finger_pair = joinery.finger_pair("_vfb", self.depth - self.drawer_plate_thickness, 0) @@ -686,7 +709,8 @@ def execute(self, context): simple.difference('_bot', '_bottom') simple.rotate(math.pi/2) - joinery.finger_pair("_wfb0", 0, self.width - self.drawer_plate_thickness - self.finger_inset * 2) + joinery.finger_pair("_wfb0", 0, self.width - + self.drawer_plate_thickness - self.finger_inset * 2) simple.active_name('_bot_fingers') simple.difference('_bot', '_bottom') @@ -773,19 +797,22 @@ class CamCurvePuzzle(bpy.types.Operator): twist_lock: bpy.props.BoolProperty(name="Add TwistLock", default=False) twist_thick: bpy.props.FloatProperty(name="Twist Thickness", default=0.0047, min=0.001, max=3.0, precision=4, unit="LENGTH") - twist_percent: bpy.props.FloatProperty(name="Twist neck", default=0.3, min=0.1, max=0.9, precision=4) + twist_percent: bpy.props.FloatProperty( + name="Twist neck", default=0.3, min=0.1, max=0.9, precision=4) twist_keep: bpy.props.BoolProperty(name="keep Twist holes", default=False) twist_line: bpy.props.BoolProperty(name="Add Twist to bar", default=False) twist_line_amount: bpy.props.IntProperty(name="amount of separators", default=2, min=1, max=600) twist_separator: bpy.props.BoolProperty(name="Add Twist separator", default=False) - twist_separator_amount: bpy.props.IntProperty(name="amount of separators", default=2, min=2, max=600) + twist_separator_amount: bpy.props.IntProperty( + name="amount of separators", default=2, min=2, max=600) twist_separator_spacing: bpy.props.FloatProperty(name="Separator spacing", default=0.025, min=-0.004, max=1.0, precision=4, unit="LENGTH") twist_separator_edge_distance: bpy.props.FloatProperty(name="Separator edge distance", default=0.01, min=0.0005, max=0.1, precision=4, unit="LENGTH") tile_x_amount: bpy.props.IntProperty(name="amount of x fingers", default=2, min=1, max=600) tile_y_amount: bpy.props.IntProperty(name="amount of y fingers", default=2, min=1, max=600) - interlock_amount: bpy.props.IntProperty(name="Interlock amount on curve", default=2, min=0, max=200) + interlock_amount: bpy.props.IntProperty( + name="Interlock amount on curve", default=2, min=0, max=200) overcut: bpy.props.BoolProperty(name="Add overcut", default=False) overcut_diameter: bpy.props.FloatProperty(name="Overcut toool Diameter", default=0.003175, min=-0.001, max=0.5, precision=4, unit="LENGTH") @@ -877,7 +904,8 @@ def execute(self, context): if self.interlock_type == 'JOINT': if self.finger_amount == 0: # cannot be 0 in joints self.finger_amount = 1 - puzzle_joinery.fingers(self.diameter, self.finger_tolerance, self.finger_amount, stem=self.stem_size) + puzzle_joinery.fingers(self.diameter, self.finger_tolerance, + self.finger_amount, stem=self.stem_size) if self.interlock_type == 'BAR': if not self.mitre: @@ -938,7 +966,7 @@ def execute(self, context): elif self.interlock_type == 'TILE': puzzle_joinery.tile(self.diameter, self.finger_tolerance, self.tile_x_amount, self.tile_y_amount, - stem=self.stem_size) + stem=self.stem_size) elif self.interlock_type == 'OPENCURVE' and curve_detected: puzzle_joinery.open_curve(line, self.height, self.diameter, self.finger_tolerance, self.finger_amount, @@ -978,7 +1006,7 @@ class CamCurveGear(bpy.types.Operator): rim_size: bpy.props.FloatProperty(name="Rim size", default=0.003175, min=0, max=3.0, precision=4, unit="LENGTH") hub_diameter: bpy.props.FloatProperty(name="Hub diameter", default=0.005, min=0, max=3.0, precision=4, - unit="LENGTH") + unit="LENGTH") pressure_angle: bpy.props.FloatProperty(name="Pressure Angle", default=math.radians(20), min=0.001, max=math.pi/2, precision=4, subtype="ANGLE", @@ -988,7 +1016,7 @@ class CamCurveGear(bpy.types.Operator): backlash: bpy.props.FloatProperty(name="Backlash", default=0.0, min=0.0, max=0.1, precision=4, unit="LENGTH") rack_height: bpy.props.FloatProperty(name="Rack Height", default=0.012, min=0.001, max=1, precision=4, - unit="LENGTH") + unit="LENGTH") rack_tooth_per_hole: bpy.props.IntProperty(name="teeth per mounting hole", default=7, min=2) gear_type: EnumProperty(name='Type of gear', items=(('PINION', 'Pinion', 'circular gear'), diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index 7ed90f90a..2c3935cb8 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -45,15 +45,21 @@ class CamSineCurve(bpy.types.Operator): ('triangle', 'Triangle Wave', 'triangle wave'), ('cycloid', 'Cycloid', 'Sine wave rectification'), ('invcycloid', 'Inverse Cycloid', 'Sine wave rectification')), default='sine') - amplitude: bpy.props.FloatProperty(name="Amplitude", default=.01, min=0, max=10, precision=4, unit="LENGTH") - period: bpy.props.FloatProperty(name="Period", default=.5, min=0.001, max=100, precision=4, unit="LENGTH") + amplitude: bpy.props.FloatProperty( + name="Amplitude", default=.01, min=0, max=10, precision=4, unit="LENGTH") + period: bpy.props.FloatProperty(name="Period", default=.5, + min=0.001, max=100, precision=4, unit="LENGTH") beatperiod: bpy.props.FloatProperty(name="Beat Period offset", default=0.0, min=0.0, max=100, precision=4, unit="LENGTH") - shift: bpy.props.FloatProperty(name="phase shift", default=0, min=-360, max=360, precision=4, unit="ROTATION") - offset: bpy.props.FloatProperty(name="offset", default=0, min=-1.0, max=1, precision=4, unit="LENGTH") + shift: bpy.props.FloatProperty(name="phase shift", default=0, + min=-360, max=360, precision=4, unit="ROTATION") + offset: bpy.props.FloatProperty(name="offset", default=0, min=- + 1.0, max=1, precision=4, unit="LENGTH") iteration: bpy.props.IntProperty(name="iteration", default=100, min=50, max=2000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, min=-3.0, max=3, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, min=-3.0, max=3, precision=4, unit="LENGTH") + maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, + min=-3.0, max=3, precision=4, unit="LENGTH") + mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, + min=-3.0, max=3, precision=4, unit="LENGTH") wave_distance: bpy.props.FloatProperty(name="distance between multiple waves", default=0.0, min=0.0, max=100, precision=4, unit="LENGTH") wave_angle_offset: bpy.props.FloatProperty(name="angle offset for multiple waves", default=math.pi/2, @@ -64,18 +70,22 @@ def execute(self, context): # z=Asin(B(x+C))+D if self.wave == 'sine': - zstring = ssine(self.amplitude, self.period, dc_offset=self.offset, phase_shift=self.shift) + zstring = ssine(self.amplitude, self.period, + dc_offset=self.offset, phase_shift=self.shift) if self.beatperiod != 0: zstring += "+"+ssine(self.amplitude, self.period+self.beatperiod, dc_offset=self.offset, phase_shift=self.shift) elif self.wave == 'triangle': # build triangle wave from fourier series - zstring = str(round(self.offset, 6)) + "+(" + str(triangle(80, self.period, self.amplitude))+")" + zstring = str(round(self.offset, 6)) + \ + "+(" + str(triangle(80, self.period, self.amplitude))+")" if self.beatperiod != 0: zstring += '+' + str(triangle(80, self.period+self.beatperiod, self.amplitude)) elif self.wave == 'cycloid': - zstring = "abs("+ssine(self.amplitude, self.period, dc_offset=self.offset, phase_shift=self.shift)+")" + zstring = "abs("+ssine(self.amplitude, self.period, + dc_offset=self.offset, phase_shift=self.shift)+")" elif self.wave == 'invcycloid': - zstring = "-1*abs("+ssine(self.amplitude, self.period, dc_offset=self.offset, phase_shift=self.shift)+")" + zstring = "-1*abs("+ssine(self.amplitude, self.period, + dc_offset=self.offset, phase_shift=self.shift)+")" print(zstring) e = Expression(zstring, ["t"]) # make equation from string @@ -106,24 +116,33 @@ class CamLissajousCurve(bpy.types.Operator): bl_label = "Create Lissajous figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - amplitude_A: bpy.props.FloatProperty(name="Amplitude A", default=.1, min=0, max=100, precision=4, unit="LENGTH") + amplitude_A: bpy.props.FloatProperty( + name="Amplitude A", default=.1, min=0, max=100, precision=4, unit="LENGTH") waveA: bpy.props.EnumProperty(name="Wave X", items=( ('sine', 'Sine Wave', 'Sine Wave'), ('triangle', 'Triangle Wave', 'triangle wave')), default='sine') - amplitude_B: bpy.props.FloatProperty(name="Amplitude B", default=.1, min=0, max=100, precision=4, unit="LENGTH") + amplitude_B: bpy.props.FloatProperty( + name="Amplitude B", default=.1, min=0, max=100, precision=4, unit="LENGTH") waveB: bpy.props.EnumProperty(name="Wave Y", items=( ('sine', 'Sine Wave', 'Sine Wave'), ('triangle', 'Triangle Wave', 'triangle wave')), default='sine') - period_A: bpy.props.FloatProperty(name="Period A", default=1.1, min=0.001, max=100, precision=4, unit="LENGTH") - period_B: bpy.props.FloatProperty(name="Period B", default=1.0, min=0.001, max=100, precision=4, unit="LENGTH") - period_Z: bpy.props.FloatProperty(name="Period Z", default=1.0, min=0.001, max=100, precision=4, unit="LENGTH") - amplitude_Z: bpy.props.FloatProperty(name="Amplitude Z", default=0.0, min=0, max=100, precision=4, unit="LENGTH") - shift: bpy.props.FloatProperty(name="phase shift", default=0, min=-360, max=360, precision=4, unit="ROTATION") + period_A: bpy.props.FloatProperty(name="Period A", default=1.1, + min=0.001, max=100, precision=4, unit="LENGTH") + period_B: bpy.props.FloatProperty(name="Period B", default=1.0, + min=0.001, max=100, precision=4, unit="LENGTH") + period_Z: bpy.props.FloatProperty(name="Period Z", default=1.0, + min=0.001, max=100, precision=4, unit="LENGTH") + amplitude_Z: bpy.props.FloatProperty( + name="Amplitude Z", default=0.0, min=0, max=100, precision=4, unit="LENGTH") + shift: bpy.props.FloatProperty(name="phase shift", default=0, + min=-360, max=360, precision=4, unit="ROTATION") iteration: bpy.props.IntProperty(name="iteration", default=500, min=50, max=10000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=11, min=-3.0, max=1000000, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, min=-10.0, max=3, precision=4, unit="LENGTH") + maxt: bpy.props.FloatProperty(name="Wave ends at x", default=11, + min=-3.0, max=1000000, precision=4, unit="LENGTH") + mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, + min=-10.0, max=3, precision=4, unit="LENGTH") def execute(self, context): # x=Asin(at+delta ),y=Bsin(bt) @@ -166,12 +185,14 @@ class CamHypotrochoidCurve(bpy.types.Operator): typecurve: bpy.props.EnumProperty(name="type of curve", items=( ('hypo', 'Hypotrochoid', 'inside ring'), ('epi', 'Epicycloid', 'outside inner ring'))) - R: bpy.props.FloatProperty(name="Big circle radius", default=0.25, min=0.001, max=100, precision=4, unit="LENGTH") + R: bpy.props.FloatProperty(name="Big circle radius", default=0.25, + min=0.001, max=100, precision=4, unit="LENGTH") r: bpy.props.FloatProperty(name="Small circle radius", default=0.18, min=0.0001, max=100, precision=4, unit="LENGTH") d: bpy.props.FloatProperty(name="distance from center of interior circle", default=0.050, min=0, max=100, precision=4, unit="LENGTH") - dip: bpy.props.FloatProperty(name="variable depth from center", default=0.00, min=-100, max=100, precision=4) + dip: bpy.props.FloatProperty(name="variable depth from center", + default=0.00, min=-100, max=100, precision=4) def execute(self, context): r = round(self.r, 6) @@ -190,7 +211,8 @@ def execute(self, context): xstring = str(Rpr) + "*cos(t)-" + str(d) + "*cos(" + str(Rpror) + "*t)" ystring = str(Rpr) + "*sin(t)-" + str(d) + "*sin(" + str(Rpror) + "*t)" - zstring = '(' + str(round(self.dip, 6)) + '*(sqrt(((' + xstring + ')**2)+((' + ystring + ')**2))))' + zstring = '(' + str(round(self.dip, 6)) + \ + '*(sqrt(((' + xstring + ')**2)+((' + ystring + ')**2))))' print("x= " + str(xstring)) print("y= " + str(ystring)) @@ -210,7 +232,8 @@ def f(t, offset: float = 0.0): if iter > 10000: # do not calculate more than 10000 points print("limiting calculations to 10000 points") iter = 10000 - parametric.create_parametric_curve(f, offset=0.0, min=0, max=maxangle, use_cubic=True, iterations=iter) + parametric.create_parametric_curve( + f, offset=0.0, min=0, max=maxangle, use_cubic=True, iterations=iter) return {'FINISHED'} @@ -223,11 +246,14 @@ class CamCustomCurve(bpy.types.Operator): xstring: StringProperty(name="X equation", description="Equation x=F(t)", default="t") ystring: StringProperty(name="Y equation", description="Equation y=F(t)", default="0") - zstring: StringProperty(name="Z equation", description="Equation z=F(t)", default="0.05*sin(2*pi*4*t)") + zstring: StringProperty(name="Z equation", description="Equation z=F(t)", + default="0.05*sin(2*pi*4*t)") iteration: bpy.props.IntProperty(name="iteration", default=100, min=50, max=2000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, min=-3.0, max=10, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, min=-3.0, max=3, precision=4, unit="LENGTH") + maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, + min=-3.0, max=10, precision=4, unit="LENGTH") + mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, + min=-3.0, max=3, precision=4, unit="LENGTH") def execute(self, context): print("x= " + self.xstring) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 05e0a3699..582b421dd 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -113,7 +113,8 @@ def execute(self, context): # Perimeter cut largen then intarsion pocket externally, optional - diam = self.diameter * 1.05 + self.backlight * 2 # make the diameter 5% larger and compensate for backlight + # make the diameter 5% larger and compensate for backlight + diam = self.diameter * 1.05 + self.backlight * 2 utils.silhoueteOffset(context, -diam / 2) o1 = bpy.context.active_object @@ -137,7 +138,8 @@ def execute(self, context): o3.select_set(True) context.view_layer.objects.active = o3 # intarsion profile is the inside piece of the intarsion - utils.silhoueteOffset(context, -self.tolerance / 2) # make smaller curve for material profile + # make smaller curve for material profile + utils.silhoueteOffset(context, -self.tolerance / 2) bpy.context.object.location[2] = self.intarsion_thickness o4 = bpy.context.active_object bpy.context.active_object.name = "intarsion_profil" @@ -146,7 +148,8 @@ def execute(self, context): if self.backlight > 0.0: # Make a smaller curve for backlighting purposes utils.silhoueteOffset(context, (-self.tolerance / 2) - self.backlight) bpy.context.active_object.name = "intarsion_backlight" - bpy.context.object.location[2] = -self.backlight_depth_from_top - self.intarsion_thickness + bpy.context.object.location[2] = - \ + self.backlight_depth_from_top - self.intarsion_thickness o4.select_set(True) o3.select_set(True) return {'FINISHED'} @@ -159,7 +162,8 @@ class CamCurveOvercuts(bpy.types.Operator): bl_label = "Add Overcuts" bl_options = {'REGISTER', 'UNDO'} - diameter: bpy.props.FloatProperty(name="diameter", default=.003175, min=0, max=100, precision=4, unit="LENGTH") + diameter: bpy.props.FloatProperty( + name="diameter", default=.003175, min=0, max=100, precision=4, unit="LENGTH") threshold: bpy.props.FloatProperty(name="threshold", default=math.pi / 2 * .99, min=-3.14, max=3.14, precision=4, subtype="ANGLE", unit="ROTATION") do_outer: bpy.props.BoolProperty(name="Outer polygons", default=True) @@ -349,7 +353,6 @@ def getCornerDelta(curidx, nextidx): else: loops = s.boundary - outercurve = self.do_outer or len(loops.geoms) == 1 for ci, c in enumerate(loops.geoms): if ci > 0 or outercurve: @@ -606,8 +609,10 @@ class CamOffsetSilhouete(bpy.types.Operator): bl_label = "Silhouete offset" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - offset: bpy.props.FloatProperty(name="offset", default=.003, min=-100, max=100, precision=4, unit="LENGTH") - mitrelimit: bpy.props.FloatProperty(name="Mitre Limit", default=.003, min=0.0, max=20, precision=4, unit="LENGTH") + offset: bpy.props.FloatProperty(name="offset", default=.003, + min=-100, max=100, precision=4, unit="LENGTH") + mitrelimit: bpy.props.FloatProperty( + name="Mitre Limit", default=.003, min=0.0, max=20, precision=4, unit="LENGTH") style: bpy.props.EnumProperty(name="type of curve", items=( ('1', 'Round', ''), ('2', 'Mitre', ''), ('3', 'Bevel', ''))) opencurve: bpy.props.BoolProperty(name="Dialate open curve", default=False) @@ -615,8 +620,8 @@ class CamOffsetSilhouete(bpy.types.Operator): @classmethod def poll(cls, context): return context.active_object is not None and ( - context.active_object.type == 'CURVE' or context.active_object.type == 'FONT' or - context.active_object.type == 'MESH') + context.active_object.type == 'CURVE' or context.active_object.type == 'FONT' or + context.active_object.type == 'MESH') def execute(self, context): # this is almost same as getobjectoutline, just without the need of operation data bpy.ops.object.curve_remove_doubles() @@ -624,7 +629,8 @@ def execute(self, context): # this is almost same as getobjectoutline, just wit if self.opencurve and ob.type == 'CURVE': bpy.ops.object.duplicate() obj = context.active_object - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) # apply all transforms + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=True) # apply all transforms bpy.context.object.data.resolution_u = 60 bpy.ops.object.convert(target='MESH') bpy.context.active_object.name = "temp_mesh" @@ -659,8 +665,8 @@ def poll(cls, context): # return context.active_object is not None and (context.active_object.type == 'CURVE' # or context.active_object.type == 'FONT' or context.active_object.type == 'MESH') return context.active_object is not None and ( - context.active_object.type == 'FONT' or - context.active_object.type == 'MESH') + context.active_object.type == 'FONT' or + context.active_object.type == 'MESH') def execute(self, context): # this is almost same as getobjectoutline, just without the need of operation data ob = bpy.context.active_object diff --git a/scripts/addons/cam/exception.py b/scripts/addons/cam/exception.py index 71b3ace3d..8d46ffc8c 100644 --- a/scripts/addons/cam/exception.py +++ b/scripts/addons/cam/exception.py @@ -1,2 +1,2 @@ class CamException(Exception): - pass \ No newline at end of file + pass diff --git a/scripts/addons/cam/gcodeimportparser.py b/scripts/addons/cam/gcodeimportparser.py index 5e6131042..81dd9beca 100644 --- a/scripts/addons/cam/gcodeimportparser.py +++ b/scripts/addons/cam/gcodeimportparser.py @@ -18,7 +18,8 @@ # # ***** END GPL LICENCE BLOCK ***** -import bpy, bmesh +import bpy +import bmesh import math import re @@ -375,7 +376,7 @@ def classifySegments(self): # some horizontal movement, and positive extruder movement: extrusion if ( ((seg.coords["X"] != coords["X"]) or (seg.coords["Y"] != coords["Y"]) or ( - seg.coords["Z"] != coords["Z"]))): # != coords["E"] + seg.coords["Z"] != coords["Z"]))): # != coords["E"] style = "extrude" # #force extrude if there is some movement @@ -386,7 +387,8 @@ def classifySegments(self): currentLayerIdx += 1 seg.style = style seg.layerIdx = currentLayerIdx - self.layers.append(layer) # add layer to list of Layers, used to later draw single layer objects + # add layer to list of Layers, used to later draw single layer objects + self.layers.append(layer) break # positive extruder movement of next point in a different Z signals a layer change for this segment @@ -435,7 +437,8 @@ def subdivide(self, subd_threshold): P2 = seg.coords # interpolated points - interp_coords = np.linspace(list(P1.values()), list(P2.values()), num=subdivs, endpoint=True) + interp_coords = np.linspace(list(P1.values()), list( + P2.values()), num=subdivs, endpoint=True) for i in range(len(interp_coords)): # inteprolated points array back to segment object @@ -454,7 +457,8 @@ def subdivide(self, subd_threshold): # write segment only if movement changes, # avoid double coordinates due to same start and endpoint of linspace - new_seg = Segment(seg.type, new_coords, seg.color, seg.toolnumber, seg.lineNb, seg.line) + new_seg = Segment(seg.type, new_coords, seg.color, + seg.toolnumber, seg.lineNb, seg.line) new_seg.layerIdx = seg.layerIdx new_seg.style = seg.style subdivided_segs.append(new_seg) diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index b566c55fd..678bb37ac 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -60,6 +60,7 @@ from cam.opencamlib.opencamlib import * from cam.nc import iso + def pointonline(a, b, c, tolerence): b = b - a # convert to vector by subtracting origin c = c - a @@ -98,7 +99,8 @@ def exportGcodePath(filename, vertslist, operations): print('file will be separated into %i files' % filesnum) print('1') - basefilename = bpy.data.filepath[:-len(bpy.path.basename(bpy.data.filepath))] + safeFileName(filename) + basefilename = bpy.data.filepath[:- + len(bpy.path.basename(bpy.data.filepath))] + safeFileName(filename) extension = '.tap' if m.post_processor == 'ISO': @@ -158,7 +160,7 @@ def startNewFile(): if split: fileindex = '_' + str(findex) filename = basefilename + fileindex + extension - print("writing: ",filename) + print("writing: ", filename) c = postprocessor.Creator() # process user overrides for post processor settings @@ -323,7 +325,7 @@ def startNewFile(): # skip the first vertex if this is a chained operation # ie: outputting more than one operation # otherwise the machine gets sent back to 0,0 for each operation which is unecessary - shapes += 1 # Count amount of shapes + shapes += 1 # Count amount of shapes if i > 0 and vi == 0: continue v = vert.co @@ -435,9 +437,9 @@ def startNewFile(): c.rapid(x=vx, y=vy, z=vz) # this is to evaluate operation time and adds a feedrate for fast moves if vz is not None: - f = plungefeedrate * fadjustval * 0.35 # compensate for multiple fast move accelerations + f = plungefeedrate * fadjustval * 0.35 # compensate for multiple fast move accelerations if vx is not None or vy is not None: - f = freefeedrate * 0.8 # compensate for free feedrate acceleration + f = freefeedrate * 0.8 # compensate for free feedrate acceleration else: c.rapid(x=vx, y=vy, z=vz, a=ra, b=rb) @@ -451,7 +453,7 @@ def startNewFile(): c.feed(x=vx, y=vy, z=vz) else: c.feed(x=vx, y=vy, z=vz, a=ra, b=rb) - cut_distance+=vect.length * unitcorr + cut_distance += vect.length * unitcorr vector_duration = vect.length / f duration += vector_duration last = v @@ -494,7 +496,7 @@ def startNewFile(): c.write(aline + '\n') o.info.duration = duration * unitcorr - print("total time:",round(o.info.duration * 60),"seconds") + print("total time:", round(o.info.duration * 60), "seconds") if bpy.context.scene.unit_settings.system == 'METRIC': unit_distance = 'm' cut_distance /= 1000 @@ -502,7 +504,7 @@ def startNewFile(): unit_distance = 'feet' cut_distance /= 12 - print("cut distance:", round(cut_distance,3), unit_distance) + print("cut distance:", round(cut_distance, 3), unit_distance) if enable_dust: c.write(stop_dust + '\n') if enable_hold: @@ -542,14 +544,16 @@ async def getPath(context, operation): # should do all path calculations. print(operation.machine_axes) if operation.machine_axes == '3': - if USE_PROFILER == True: # profiler - import cProfile, pstats, io + if USE_PROFILER == True: # profiler + import cProfile + import pstats + import io pr = cProfile.Profile() pr.enable() await getPath3axis(context, operation) pr.disable() pr.dump_stats(time.strftime("blendercam_%Y%m%d_%H%M.prof")) - else: + else: await getPath3axis(context, operation) elif (operation.machine_axes == '5' and operation.strategy5axis == 'INDEXED') or ( @@ -680,7 +684,8 @@ async def getPath3axis(context, operation): chunks = chunksCoherency(chunks) print('coherency check') - if o.strategy in ['PARALLEL', 'CROSS', 'PENCIL', 'OUTLINEFILL']: # and not o.movement.parallel_step_back: + # and not o.movement.parallel_step_back: + if o.strategy in ['PARALLEL', 'CROSS', 'PENCIL', 'OUTLINEFILL']: print('sorting') chunks = await utils.sortChunks(chunks, o) if o.strategy == 'OUTLINEFILL': @@ -779,7 +784,8 @@ async def getPath3axis(context, operation): o.inverse and not poly.is_empty and slicesfilled == 1): # first slice fill restpoly = lastslice - restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.optimisation.circle_detail) + restpoly = restpoly.buffer(-o.dist_between_paths, + resolution=o.optimisation.circle_detail) fillz = z i = 0 @@ -796,7 +802,8 @@ async def getPath3axis(context, operation): parentChildDist(lastchunks, nchunks, o) lastchunks = nchunks # slicechunks.extend(polyToChunks(restpoly,z)) - restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.optimisation.circle_detail) + restpoly = restpoly.buffer(-o.dist_between_paths, + resolution=o.optimisation.circle_detail) i += 1 # print(i) @@ -814,10 +821,12 @@ async def getPath3axis(context, operation): if o.inverse and poly.is_empty and slicesfilled > 0: restpoly = bound_rectangle.difference(lastslice) - restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.optimisation.circle_detail) + restpoly = restpoly.buffer(-o.dist_between_paths, + resolution=o.optimisation.circle_detail) i = 0 - while not restpoly.is_empty: # 'GeometryCollection':#len(restpoly.boundary.coords)>0: + # 'GeometryCollection':#len(restpoly.boundary.coords)>0: + while not restpoly.is_empty: # print(i) nchunks = shapelyToChunks(restpoly, fillz) ######################### @@ -825,7 +834,8 @@ async def getPath3axis(context, operation): slicechunks.extend(nchunks) parentChildDist(lastchunks, nchunks, o) lastchunks = nchunks - restpoly = restpoly.buffer(-o.dist_between_paths, resolution=o.optimisation.circle_detail) + restpoly = restpoly.buffer(-o.dist_between_paths, + resolution=o.optimisation.circle_detail) i += 1 percent = int(h / nslices * 100) @@ -847,11 +857,11 @@ async def getPath3axis(context, operation): strategy.chunksToMesh(chunks, o) elif o.strategy == 'DRILL': - await strategy.drill(o) + await strategy.drill(o) elif o.strategy == 'MEDIAL_AXIS': await strategy.medial_axis(o) - await progress_async(f"Done",time.time() - tw,"s") + await progress_async(f"Done", time.time() - tw, "s") async def getPath4axis(context, operation): diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index e783716e4..85018cdab 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -36,11 +36,11 @@ from cam import simulation from cam.async_op import progress_async -from cam.numba_wrapper import jit,prange +from cam.numba_wrapper import jit, prange def getCircle(r, z): - car = numpy.full(shape=(r*2,r*2),fill_value=-10,dtype=numpy.double) + car = numpy.full(shape=(r*2, r*2), fill_value=-10, dtype=numpy.double) res = 2 * r m = r v = mathutils.Vector((0, 0, 0)) @@ -54,7 +54,7 @@ def getCircle(r, z): def getCircleBinary(r): - car = numpy.full(shape=(r*2,r*2),fill_value=False,dtype=bool) + car = numpy.full(shape=(r*2, r*2), fill_value=False, dtype=bool) res = 2 * r m = r v = mathutils.Vector((0, 0, 0)) @@ -118,7 +118,7 @@ def imagetonumpy(i): width = i.size[0] height = i.size[1] - na = numpy.full(shape=(width*height*4,),fill_value=-10,dtype=numpy.double) + na = numpy.full(shape=(width*height*4,), fill_value=-10, dtype=numpy.double) p = i.pixels[:] # these 2 lines are about 15% faster than na[:]=i.pixels[:].... whyyyyyyyy!!?!?!?!?! @@ -132,11 +132,12 @@ def imagetonumpy(i): return na -@jit(nopython=True,parallel=True,fastmath=False,cache=True) -def _offset_inner_loop(y1,y2,cutterArrayNan,cwidth,sourceArray,width,height,comparearea): - for y in prange(y1,y2): - for x in range(0,width-cwidth): - comparearea[x,y] = numpy.nanmax(sourceArray[x:x+cwidth,y:y+cwidth] + cutterArrayNan) +@jit(nopython=True, parallel=True, fastmath=False, cache=True) +def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea): + for y in prange(y1, y2): + for x in range(0, width-cwidth): + comparearea[x, y] = numpy.nanmax(sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan) + async def offsetArea(o, samples): """ offsets the whole image with the cutter + skin offsets """ @@ -151,7 +152,7 @@ async def offsetArea(o, samples): width = len(sourceArray) height = len(sourceArray[0]) cwidth = len(cutterArray) - o.offset_image= numpy.full(shape=(width,height),fill_value=-10.0,dtype=numpy.double) + o.offset_image = numpy.full(shape=(width, height), fill_value=-10.0, dtype=numpy.double) t = time.time() @@ -161,11 +162,13 @@ async def offsetArea(o, samples): sourceArray = -sourceArray + minz comparearea = o.offset_image[m: width - cwidth + m, m:height - cwidth + m] # i=0 - cutterArrayNan=np.where(cutterArray>-10,cutterArray,np.full(cutterArray.shape,np.nan)) - for y in range(0,10): + cutterArrayNan = np.where(cutterArray > -10, cutterArray, + np.full(cutterArray.shape, np.nan)) + for y in range(0, 10): y1 = (y * comparearea.shape[1])//10 y2 = ((y+1) * comparearea.shape[1])//10 - _offset_inner_loop(y1,y2,cutterArrayNan,cwidth,sourceArray,width,height,comparearea) + _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, + sourceArray, width, height, comparearea) await progress_async('offset depth image', int((y2 * 100) / comparearea.shape[1])) o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea @@ -202,7 +205,7 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly # ##crop pixels that are on outer borders for chi in range(len(chunks) - 1, -1, -1): chunk = chunks[chi] - chunk.clip_points(o.min.x,o.max.x,o.min.y,o.max.y) + chunk.clip_points(o.min.x, o.max.x, o.min.y, o.max.y) # for si in range(len(chunk.points) - 1, -1, -1): # if not (o.min.x < chunk.points[si][0] < o.max.x and o.min.y < chunk.points[si][1] < o.max.y): # chunk.points.pop(si) @@ -347,8 +350,9 @@ async def crazyPath(o): resx = ceil(sx / o.optimisation.simulation_detail) + 2 * o.borderwidth resy = ceil(sy / o.optimisation.simulation_detail) + 2 * o.borderwidth - o.millimage = numpy.full(shape=(resx,resy),fill_value=0.,dtype=numpy.float) - o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) # getting inverted cutter + o.millimage = numpy.full(shape=(resx, resy), fill_value=0., dtype=numpy.float) + # getting inverted cutter + o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) def buildStroke(start, end, cutterArray): @@ -357,7 +361,7 @@ def buildStroke(start, end, cutterArray): size_y = abs(end[1] - start[1]) + cutterArray.size[0] r = cutterArray.size[0] / 2 - strokeArray = numpy.full(shape=(size_x,size_y),fill_value=-10.0,dtype=numpy.float) + strokeArray = numpy.full(shape=(size_x, size_y), fill_value=-10.0, dtype=numpy.float) samplesx = numpy.round(numpy.linspace(start[0], end[0], strokelength)) samplesy = numpy.round(numpy.linspace(start[1], end[1], strokelength)) samplesz = numpy.round(numpy.linspace(start[2], end[2], strokelength)) @@ -384,7 +388,8 @@ def crazyStrokeImage(o): # this surprisingly works, and can be used as a basis for something similar to adaptive milling strategy. minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z - r = int((o.cutter_diameter / 2.0) / o.optimisation.pixsize) # ceil((o.cutter_diameter/12)/o.optimisation.pixsize) + # ceil((o.cutter_diameter/12)/o.optimisation.pixsize) + r = int((o.cutter_diameter / 2.0) / o.optimisation.pixsize) d = 2 * r coef = 0.75 @@ -396,7 +401,8 @@ def crazyStrokeImage(o): cutterArrayNegative = -cutterArray cutterimagepix = cutterArray.sum() - satisfypix = cutterimagepix * o.crazy_threshold1 # a threshold which says if it is valuable to cut in a direction + # a threshold which says if it is valuable to cut in a direction + satisfypix = cutterimagepix * o.crazy_threshold1 toomuchpix = cutterimagepix * o.crazy_threshold2 indices = ar.nonzero() # first get white pixels startpix = ar.sum() # @@ -411,7 +417,8 @@ def crazyStrokeImage(o): nchunk = camPathChunkBuilder([(xs, ys)]) # startposition print(indices) print(indices[0][0], indices[1][0]) - lastvect = Vector((r, 0, 0)) # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. + # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. + lastvect = Vector((r, 0, 0)) testvect = lastvect.normalized() * r / 2.0 # multiply *2 not to get values <1 pixel rot = Euler((0, 0, 1)) i = 0 @@ -422,7 +429,8 @@ def crazyStrokeImage(o): maxtotaltests = 1000000 print(xs, ys, indices[0][0], indices[1][0], r) - ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - r:xs - r + d, ys - r:ys - r + d] * cutterArrayNegative + ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - + r:xs - r + d, ys - r:ys - r + d] * cutterArrayNegative anglerange = [-pi, pi] # range for angle of toolpath vector versus material vector testangleinit = 0 angleincrement = 0.05 @@ -469,7 +477,8 @@ def crazyStrokeImage(o): if success: nchunk.points.append([xs, ys]) lastvect = testvect - ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - r:xs - r + d, ys - r:ys - r + d] * (-cutterArray) + ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - + r:xs - r + d, ys - r:ys - r + d] * (-cutterArray) totpix -= eatpix itests = 0 if 0: @@ -531,7 +540,7 @@ def crazyStrokeImage(o): ys = r nchunk = camPathChunkBuilder([(xs, ys)]) # startposition ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - r:xs - r + d, - ys - r:ys - r + d] * cutterArrayNegative + ys - r:ys - r + d] * cutterArrayNegative r = random.random() * 2 * pi e = Euler((0, 0, r)) testvect = lastvect.normalized() * 4 # multiply *2 not to get values <1 pixel @@ -569,7 +578,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): ar[:, :o.borderwidth] = 0 ar[:, -o.borderwidth:] = 0 - r = int((o.cutter_diameter / 2.0) / o.optimisation.pixsize) # ceil((o.cutter_diameter/12)/o.optimisation.pixsize) + # ceil((o.cutter_diameter/12)/o.optimisation.pixsize) + r = int((o.cutter_diameter / 2.0) / o.optimisation.pixsize) d = 2 * r coef = 0.75 maxarx = ar.shape[0] @@ -581,9 +591,11 @@ def crazyStrokeImageBinary(o, ar, avoidar): cutterimagepix = cutterArray.sum() anglelimit = o.crazy_threshold3 - satisfypix = cutterimagepix * o.crazy_threshold1 # a threshold which says if it is valuable to cut in a direction + # a threshold which says if it is valuable to cut in a direction + satisfypix = cutterimagepix * o.crazy_threshold1 toomuchpix = cutterimagepix * o.crazy_threshold2 # same, but upper limit - optimalpix = cutterimagepix * o.crazy_threshold5 # (satisfypix+toomuchpix)/2.0# the ideal eating ratio + # (satisfypix+toomuchpix)/2.0# the ideal eating ratio + optimalpix = cutterimagepix * o.crazy_threshold5 indices = ar.nonzero() # first get white pixels startpix = ar.sum() # @@ -602,7 +614,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): nchunk = camPathChunkBuilder([(xs, ys)]) # startposition print(indices) print(indices[0][0], indices[1][0]) - lastvect = Vector((r, 0, 0)) # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. + # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. + lastvect = Vector((r, 0, 0)) testvect = lastvect.normalized() * r / 4.0 # multiply *2 not to get values <1 pixel rot = Euler((0, 0, 1)) i = 0 @@ -687,7 +700,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): nchunk.points.append([xs, ys]) lastvect = testvect - ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, ys - r:ys + r] * cutterArrayNegative + ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - + r:xs + r, ys - r:ys + r] * cutterArrayNegative totpix -= bestsolution[1] itests = 0 # if 0: @@ -781,7 +795,7 @@ def crazyStrokeImageBinary(o, ar, avoidar): nchunk = camPathChunk([(xs, ys)]) # startposition ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, - ys - r:ys + r] * cutterArrayNegative + ys - r:ys + r] * cutterArrayNegative # lastvect=Vector((r,0,0))#vector is 3d, # blender somehow doesn't rotate 2d vectors with angles. randomrot = random.random() * 2 * pi @@ -968,7 +982,8 @@ def imageToChunks(o, image, with_border=False): # print('directsimplify') reduxratio = 1.25 # was 1.25 - soptions = ['distance', 'distance', o.optimisation.pixsize * reduxratio, 5, o.optimisation.pixsize * reduxratio] + soptions = ['distance', 'distance', o.optimisation.pixsize * + reduxratio, 5, o.optimisation.pixsize * reduxratio] nchunks = [] for i, ch in enumerate(vecchunks): @@ -992,6 +1007,7 @@ def imageToShapely(o, i, with_border=False): return polys + def getSampleImage(s, sarray, minz): x = s[0] y = s[1] @@ -1002,10 +1018,10 @@ def getSampleImage(s, sarray, minz): maxx = minx + 1 miny = floor(y) maxy = miny + 1 - s1a=sarray[minx,miny] - s2a=sarray[maxx,miny] - s1b=sarray[minx,maxy] - s2b=sarray[maxx,maxy] + s1a = sarray[minx, miny] + s2a = sarray[maxx, miny] + s1b = sarray[minx, maxy] + s2b = sarray[maxx, maxy] # s1a = sarray.item(minx, miny) # most optimal access to array so far # s2a = sarray.item(maxx, miny) # s1b = sarray.item(minx, maxy) @@ -1029,28 +1045,30 @@ def getResolution(o): def _backup_render_settings(pairs): - properties=[] - for owner,struct_name in pairs: - obj = getattr(owner,struct_name) - if isinstance(obj,bpy.types.bpy_struct): + properties = [] + for owner, struct_name in pairs: + obj = getattr(owner, struct_name) + if isinstance(obj, bpy.types.bpy_struct): # structure, backup all properties - obj_value={} + obj_value = {} for k in dir(obj): if not k.startswith("_"): - obj_value[k]=getattr(obj,k) + obj_value[k] = getattr(obj, k) properties.append(obj_value) else: # simple value properties.append(obj) -def _restore_render_settings(pairs,properties): - for (owner,struct_name),obj_value in zip(pairs,properties): - obj = getattr(owner,struct_name) - if isinstance(obj,bpy.types.bpy_struct): - for k,v in obj_value.items(): - setattr(obj,k,v) + +def _restore_render_settings(pairs, properties): + for (owner, struct_name), obj_value in zip(pairs, properties): + obj = getattr(owner, struct_name) + if isinstance(obj, bpy.types.bpy_struct): + for k, v in obj_value.items(): + setattr(obj, k, v) else: - setattr(owner,struct_name,obj_value) + setattr(owner, struct_name, obj_value) + def renderSampleImage(o): t = time.time() @@ -1076,11 +1094,11 @@ def renderSampleImage(o): try: i = bpy.data.images.load(iname) if i.size[0] != resx or i.size[1] != resy: - print("Z buffer size changed:",i.size,resx,resy) + print("Z buffer size changed:", i.size, resx, resy) o.update_zbufferimage_tag = True - + except: - + o.update_zbufferimage_tag = True if o.update_zbufferimage_tag: s = bpy.context.scene @@ -1090,22 +1108,22 @@ def renderSampleImage(o): r = s.render SETTINGS_TO_BACKUP = [ - (s.render,"resolution_x"), - (s.render,"resolution_x"), - (s.cycles,"samples"), - (s,"camera"), - (vl,"samples"), - (vl.cycles,"use_denoising"), - (s.world,"mist_settings"), - (r,"resolution_x"), - (r,"resolution_y"), - (r,"resolution_percentage"), + (s.render, "resolution_x"), + (s.render, "resolution_x"), + (s.cycles, "samples"), + (s, "camera"), + (vl, "samples"), + (vl.cycles, "use_denoising"), + (s.world, "mist_settings"), + (r, "resolution_x"), + (r, "resolution_y"), + (r, "resolution_percentage"), ] for ob in s.objects: - SETTINGS_TO_BACKUP.append((ob,"hide_render")) - backup_settings=None + SETTINGS_TO_BACKUP.append((ob, "hide_render")) + backup_settings = None try: - backup_settings=_backup_render_settings(SETTINGS_TO_BACKUP) + backup_settings = _backup_render_settings(SETTINGS_TO_BACKUP) # prepare nodes first r.resolution_x = resx r.resolution_y = resy @@ -1113,19 +1131,19 @@ def renderSampleImage(o): # it renders okay on github actions r.engine = 'CYCLES' s.cycles.samples = 1 - vl.samples=1 - vl.cycles.use_denoising=False + vl.samples = 1 + vl.cycles.use_denoising = False n.links.clear() n.nodes.clear() - node_in = n.nodes.new('CompositorNodeRLayers') - s.view_layers[node_in.layer].use_pass_mist=True - mist_settings=s.world.mist_settings - s.world.mist_settings.depth=10.0 - s.world.mist_settings.start=0 - s.world.mist_settings.falloff="LINEAR" - s.world.mist_settings.height=0 - s.world.mist_settings.intensity=0 + node_in = n.nodes.new('CompositorNodeRLayers') + s.view_layers[node_in.layer].use_pass_mist = True + mist_settings = s.world.mist_settings + s.world.mist_settings.depth = 10.0 + s.world.mist_settings.start = 0 + s.world.mist_settings.falloff = "LINEAR" + s.world.mist_settings.height = 0 + s.world.mist_settings.intensity = 0 node_out = n.nodes.new("CompositorNodeOutputFile") node_out.base_path = os.path.dirname(iname) node_out.format.file_format = 'OPEN_EXR' @@ -1136,19 +1154,20 @@ def renderSampleImage(o): ################### # resize operation image - o.offset_image= numpy.full(shape=(resx,resy),fill_value=-10,dtype=numpy.double) + o.offset_image = numpy.full(shape=(resx, resy), fill_value=-10, dtype=numpy.double) # various settings for faster render r.resolution_percentage = 100 # add a new camera settings bpy.ops.object.camera_add(align='WORLD', enter_editmode=False, location=(0, 0, 0), - rotation=(0, 0, 0)) + rotation=(0, 0, 0)) camera = bpy.context.active_object bpy.context.scene.camera = camera camera.data.type = 'ORTHO' - camera.data.ortho_scale = max(resx * o.optimisation.pixsize, resy * o.optimisation.pixsize) + camera.data.ortho_scale = max( + resx * o.optimisation.pixsize, resy * o.optimisation.pixsize) camera.location = (o.min.x + sx / 2, o.min.y + sy / 2, 1) camera.rotation_euler = (0, 0, 0) camera.data.clip_end = 10.0 @@ -1169,21 +1188,19 @@ def renderSampleImage(o): camera.select_set(True) bpy.ops.object.delete() - os.replace(iname+"%04d.exr"%(s.frame_current),iname) + os.replace(iname+"%04d.exr" % (s.frame_current), iname) finally: if backup_settings is not None: - _restore_render_settings(SETTINGS_TO_BACKUP,backup_settings) + _restore_render_settings(SETTINGS_TO_BACKUP, backup_settings) else: print("Failed to backup scene settings") - i = bpy.data.images.load(iname) bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' - a = imagetonumpy(i) a = 10.0 * a - a= 1.0 - a + a = 1.0 - a o.zbuffer_image = a o.update_zbufferimage_tag = False @@ -1210,9 +1227,11 @@ def renderSampleImage(o): mina = numpy.min(rawimage) neg = o.source_image_scale_z < 0 if o.strategy == 'WATERLINE': # waterline strategy needs image border to have ok ambient. - a = numpy.full(shape=(2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]),fill_value=1-neg,dtype=numpy.float) + a = numpy.full(shape=( + 2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]), fill_value=1-neg, dtype=numpy.float) else: # other operations like parallel need to reach the border - a = numpy.full(shape=(2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]),fill_value=neg,dtype=numpy.float) + a = numpy.full(shape=( + 2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]), fill_value=neg, dtype=numpy.float) # 2*o.borderwidth a[o.borderwidth:-o.borderwidth, o.borderwidth:-o.borderwidth] = rawimage a = a[sx:ex + o.borderwidth * 2, sy:ey + o.borderwidth * 2] diff --git a/scripts/addons/cam/involute_gear.py b/scripts/addons/cam/involute_gear.py index 925718c88..5f857455d 100644 --- a/scripts/addons/cam/involute_gear.py +++ b/scripts/addons/cam/involute_gear.py @@ -111,34 +111,35 @@ def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, b = p * math.cos(pressure_angle) # radius of base circle r = p-(c-p)-clearance # radius of root circle t = mm_per_tooth / 2 - backlash / 2 # tooth thickness at pitch circle - k = - gear_iang(b, p) - t / 2 / p # angle to where involute meets base circle on each side of tooth + # angle to where involute meets base circle on each side of tooth + k = - gear_iang(b, p) - t / 2 / p shapely_gear = Polygon([ - (0, 0), - gear_polar(r, k if r < b else -pi / number_of_teeth), - gear_q7(0, r, b, c, k, 1), - gear_q7(0.1, r, b, c, k, 1), - gear_q7(0.2, r, b, c, k, 1), - gear_q7(0.3, r, b, c, k, 1), - gear_q7(0.4, r, b, c, k, 1), - gear_q7(0.5, r, b, c, k, 1), - gear_q7(0.6, r, b, c, k, 1), - gear_q7(0.7, r, b, c, k, 1), - gear_q7(0.8, r, b, c, k, 1), - gear_q7(0.9, r, b, c, k, 1), - gear_q7(1.0, r, b, c, k, 1), - gear_q7(1.0, r, b, c, k, -1), - gear_q7(0.9, r, b, c, k, -1), - gear_q7(0.8, r, b, c, k, -1), - gear_q7(0.7, r, b, c, k, -1), - gear_q7(0.6, r, b, c, k, -1), - gear_q7(0.5, r, b, c, k, -1), - gear_q7(0.4, r, b, c, k, -1), - gear_q7(0.3, r, b, c, k, -1), - gear_q7(0.2, r, b, c, k, -1), - gear_q7(0.1, r, b, c, k, -1), - gear_q7(0.0, r, b, c, k, -1), - gear_polar(r, -k if r < b else pi / number_of_teeth) - ]) + (0, 0), + gear_polar(r, k if r < b else -pi / number_of_teeth), + gear_q7(0, r, b, c, k, 1), + gear_q7(0.1, r, b, c, k, 1), + gear_q7(0.2, r, b, c, k, 1), + gear_q7(0.3, r, b, c, k, 1), + gear_q7(0.4, r, b, c, k, 1), + gear_q7(0.5, r, b, c, k, 1), + gear_q7(0.6, r, b, c, k, 1), + gear_q7(0.7, r, b, c, k, 1), + gear_q7(0.8, r, b, c, k, 1), + gear_q7(0.9, r, b, c, k, 1), + gear_q7(1.0, r, b, c, k, 1), + gear_q7(1.0, r, b, c, k, -1), + gear_q7(0.9, r, b, c, k, -1), + gear_q7(0.8, r, b, c, k, -1), + gear_q7(0.7, r, b, c, k, -1), + gear_q7(0.6, r, b, c, k, -1), + gear_q7(0.5, r, b, c, k, -1), + gear_q7(0.4, r, b, c, k, -1), + gear_q7(0.3, r, b, c, k, -1), + gear_q7(0.2, r, b, c, k, -1), + gear_q7(0.1, r, b, c, k, -1), + gear_q7(0.0, r, b, c, k, -1), + gear_polar(r, -k if r < b else pi / number_of_teeth) + ]) utils.shapelyToCurve('tooth', shapely_gear, 0.0) i = number_of_teeth while i > 1: @@ -170,7 +171,8 @@ def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, simple.join_multiple('_') - simple.add_rectangle(r-rim_size-((hub_diameter-hole_diameter)/4 + hole_diameter/2), hub_diameter/2, center_x=False) + simple.add_rectangle(r-rim_size-((hub_diameter-hole_diameter)/4 + + hole_diameter/2), hub_diameter/2, center_x=False) simple.move(x=(hub_diameter-hole_diameter)/4 + hole_diameter/2) simple.active_name('_spoke') @@ -200,21 +202,22 @@ def rack(mm_per_tooth=0.01, number_of_teeth=11, height=0.012, pressure_angle=0.3 pi = math.pi mm_per_tooth *= 1000 a = mm_per_tooth / pi # addendum - t = (a * math.sin(pressure_angle)) # tooth side is tilted so top/bottom corners move this amount + # tooth side is tilted so top/bottom corners move this amount + t = (a * math.sin(pressure_angle)) a /= 1000 mm_per_tooth /= 1000 t /= 1000 shapely_gear = Polygon([ - (-mm_per_tooth * 2/4*1.001, a-height), - (-mm_per_tooth * 2/4*1.001 - backlash, -a), - (-mm_per_tooth * 1/4 + backlash - t, -a), - (-mm_per_tooth * 1/4 + backlash + t, a), - (mm_per_tooth * 1/4 - backlash - t, a), - (mm_per_tooth * 1/4 - backlash + t, -a), - (mm_per_tooth * 2/4*1.001 + backlash, -a), - (mm_per_tooth * 2/4*1.001, a-height) - ]) + (-mm_per_tooth * 2/4*1.001, a-height), + (-mm_per_tooth * 2/4*1.001 - backlash, -a), + (-mm_per_tooth * 1/4 + backlash - t, -a), + (-mm_per_tooth * 1/4 + backlash + t, a), + (mm_per_tooth * 1/4 - backlash - t, a), + (mm_per_tooth * 1/4 - backlash + t, -a), + (mm_per_tooth * 2/4*1.001 + backlash, -a), + (mm_per_tooth * 2/4*1.001, a-height) + ]) utils.shapelyToCurve('_tooth', shapely_gear, 0.0) i = number_of_teeth @@ -236,5 +239,3 @@ def rack(mm_per_tooth=0.01, number_of_teeth=11, height=0.012, pressure_angle=0.3 name = 'rack-' + str(round(mm_per_tooth * 1000, 1)) name += '-PA-' + str(round(math.degrees(pressure_angle), 1)) simple.active_name(name) - - diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 5a50f498a..fe8c705b3 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -111,8 +111,8 @@ def twist_separator_slot(length, thickness, finger_play=0.00005, percentage=0.5) simple.mirrory() simple.join_multiple('simple_rectangle') simple.active_name('_separator_slot') - - + + def interlock_twist_separator(length, thickness, amount, spacing, edge_distance, finger_play=0.00005, percentage=0.5, start='rounded', end='rounded'): amount -= 1 @@ -171,7 +171,8 @@ def vertical_finger(length, thickness, finger_play, amount): # amount = amount of fingers for i in range(amount): - mortise(length, thickness, finger_play, 0, i * 2 * length + length / 2, rotation=math.pi / 2) + mortise(length, thickness, finger_play, 0, i * 2 * + length + length / 2, rotation=math.pi / 2) simple.active_name("_height_finger") simple.join_multiple("_height_finger") @@ -318,7 +319,7 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( - math.pi / 4)) # factor for tolerance for the finger + math.pi / 4)) # factor for tolerance for the finger if base: mortise(finger_size, finger_thick, finger_tolerance * mad, distance, 0, 0) @@ -432,7 +433,7 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( - math.pi / 4)) # factor for tolerance for the finger + math.pi / 4)) # factor for tolerance for the finger distance += mad * finger_tolerance # move finger by the factor mad greater with larger angle difference mortise_point = loop.interpolate(distance) if mad > 2 and double_adaptive: @@ -440,7 +441,8 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi hpos.append(distance + finger_sz) # saves the mortise center if base: - mortise(finger_sz, finger_thick, finger_tolerance * mad, distance + finger_sz, 0, 0) + mortise(finger_sz, finger_thick, finger_tolerance * + mad, distance + finger_sz, 0, 0) simple.active_name("_base") else: mortise(finger_sz, finger_thick, finger_tolerance * mad, mortise_point.x, mortise_point.y, @@ -450,7 +452,8 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi simple.remove_multiple("start_here") bpy.ops.mesh.primitive_cylinder_add(radius=finger_thick / 2, depth=0.025, enter_editmode=False, align='WORLD', - location=(mortise_point.x, mortise_point.y, 0), + location=(mortise_point.x, + mortise_point.y, 0), scale=(1, 1, 1)) simple.active_name("start_here_mortise") @@ -461,7 +464,7 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi # adaptive finger length start while finger_sz > min_finger and next_angle_difference > adaptive: -# while finger_sz > min_finger and next_angle_difference > adaptive: + # while finger_sz > min_finger and next_angle_difference > adaptive: finger_sz *= 0.95 # reduce the size of finger by a percentage... the closer to 1.0, the slower distance = old_distance + 3 * oldfinger_sz / 2 + finger_sz / 2 mortise_point = loop.interpolate(distance) # get the next mortise point @@ -487,7 +490,8 @@ def single_interlock(finger_depth, finger_thick, finger_tolerance, x, y, groove_ if type == "GROOVE": interlock_groove(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle) elif type == "TWIST": - interlock_twist(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle, percentage=twist_percentage) + interlock_twist(finger_depth, finger_thick, finger_tolerance, + x, y, groove_angle, percentage=twist_percentage) elif type == "PUZZLE": puzzle_joinery.fingers(finger_thick, finger_tolerance) @@ -535,7 +539,8 @@ def distributed_interlock(loop, loop_length, finger_depth, finger_thick, finger_ groove_point = loop.interpolate(distance) - print(j, "groove_angle", round(180 * groove_angle / math.pi), "distance", round(distance * 1000), "mm") + print(j, "groove_angle", round(180 * groove_angle / math.pi), + "distance", round(distance * 1000), "mm") single_interlock(finger_depth, finger_thick, finger_tolerance, groove_point.x, groove_point.y, groove_angle, type, twist_percentage=twist_percentage) diff --git a/scripts/addons/cam/numba_wrapper.py b/scripts/addons/cam/numba_wrapper.py index a7fc96413..0da9255db 100644 --- a/scripts/addons/cam/numba_wrapper.py +++ b/scripts/addons/cam/numba_wrapper.py @@ -1,14 +1,15 @@ try: - from numba import jit,prange + from numba import jit, prange print("numba: yes") except: print("numba: no") + def jit(f=None, *args, **kwargs): def decorator(func): - return func + return func if callable(f): return f else: - return decorator + return decorator prange = range diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 6b3ab9f39..53e54151a 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -26,9 +26,11 @@ from bpy.props import * from bpy_extras.io_utils import ImportHelper -import subprocess, os, threading +import subprocess +import os +import threading from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, simulation -from cam.async_op import AsyncOperatorMixin,AsyncCancelledException +from cam.async_op import AsyncOperatorMixin, AsyncCancelledException import shapely import mathutils import math @@ -38,6 +40,7 @@ import cam from cam.exception import * + class threadCom: # object passed to threads to read background process stdout info def __init__(self, o, proc): self.opname = o.name @@ -55,6 +58,7 @@ def threadread(tcom): e = inline.find('}') tcom.outtext = inline[s + 9:e] + @bpy.app.handlers.persistent def timer_update(context): """monitoring of background processes""" @@ -119,7 +123,8 @@ def execute(self, context): # self.__class__.cam_processes=[] if not hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'): bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes = [] - bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes.append([readthread, tcom]) + bpy.ops.object.calculate_cam_paths_background.__class__.cam_processes.append([ + readthread, tcom]) return {'FINISHED'} @@ -146,7 +151,7 @@ def execute(self, context): return {'FINISHED'} -async def _calc_path(operator,context): +async def _calc_path(operator, context): s = bpy.context.scene o = s.cam_operations[s.cam_active_operation] if o.geometry_source == 'OBJECT': @@ -172,18 +177,20 @@ async def _calc_path(operator,context): bpy.ops.object.delete() if not o.valid: - operator.report({'ERROR_INVALID_INPUT'}, "Operation can't be performed, see warnings for info") + operator.report({'ERROR_INVALID_INPUT'}, + "Operation can't be performed, see warnings for info") progress_async("Operation can't be performed, see warnings for info") - return {'FINISHED',False} - - #check for free movement height < maxz and return with error + return {'FINISHED', False} + + # check for free movement height < maxz and return with error if(o.movement.free_height < o.maxz): - operator.report({'ERROR_INVALID_INPUT'}, "Free movement height is less than Operation depth start \n correct and try again.") + operator.report({'ERROR_INVALID_INPUT'}, + "Free movement height is less than Operation depth start \n correct and try again.") progress_async("Operation can't be performed, see warnings for info") - return {'FINISHED',False} + return {'FINISHED', False} if o.computing: - return {'FINISHED',False} + return {'FINISHED', False} o.operator = operator @@ -194,40 +201,40 @@ async def _calc_path(operator,context): print("Got path okay") except CamException as e: traceback.print_tb(e.__traceback__) - error_str="\n".join(textwrap.wrap(str(e),width=80)) - operator.report({'ERROR'},error_str) - return {'FINISHED',False} + error_str = "\n".join(textwrap.wrap(str(e), width=80)) + operator.report({'ERROR'}, error_str) + return {'FINISHED', False} except AsyncCancelledException as e: - return {'CANCELLED',False} + return {'CANCELLED', False} except Exception as e: - print("FAIL",e) + print("FAIL", e) traceback.print_tb(e.__traceback__) - operator.report({'ERROR'},str(e)) - return {'FINISHED',False} + operator.report({'ERROR'}, str(e)) + return {'FINISHED', False} coll = bpy.data.collections.get('RigidBodyWorld') if coll: bpy.data.collections.remove(coll) - return {'FINISHED',True} + return {'FINISHED', True} -class CalculatePath(bpy.types.Operator,AsyncOperatorMixin): +class CalculatePath(bpy.types.Operator, AsyncOperatorMixin): """calculate CAM paths""" bl_idname = "object.calculate_cam_path" bl_label = "Calculate CAM paths" - bl_options = {'REGISTER', 'UNDO','BLOCKING'} - + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} + @classmethod - def poll(cls,context): + def poll(cls, context): s = context.scene o = s.cam_operations[s.cam_active_operation] if o is not None: - if cam.isValid(o,context): + if cam.isValid(o, context): return True return False async def execute_async(self, context): - (retval,success) = await _calc_path(self,context) + (retval, success) = await _calc_path(self, context) print(f"CALCULATED PATH (success={success},retval={retval}") return retval @@ -298,17 +305,17 @@ def getChainOperations(chain): return chop -class PathsChain(bpy.types.Operator,AsyncOperatorMixin): +class PathsChain(bpy.types.Operator, AsyncOperatorMixin): """calculate a chain and export the gcode alltogether. """ bl_idname = "object.calculate_cam_paths_chain" bl_label = "Calculate CAM paths in current chain and export chain gcode" - bl_options = {'REGISTER', 'UNDO','BLOCKING'} + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain,context)[0] + return cam.isChainValid(chain, context)[0] async def execute_async(self, context): s = context.scene @@ -319,14 +326,14 @@ async def execute_async(self, context): try: for i in range(0, len(chainops)): s.cam_active_operation = s.cam_operations.find(chainops[i].name) - self.report({'INFO'},f"Calculating path: {chainops[i].name}") - result,success=await _calc_path(self,context) + self.report({'INFO'}, f"Calculating path: {chainops[i].name}") + result, success = await _calc_path(self, context) if not success and 'FINISHED' in result: - self.report({'ERROR'},f"Couldn't calculate path: {chainops[i].name}") + self.report({'ERROR'}, f"Couldn't calculate path: {chainops[i].name}") except Exception as e: - print("FAIL",e) + print("FAIL", e) traceback.print_tb(e.__traceback__) - operator.report({'ERROR'},str(e)) + operator.report({'ERROR'}, str(e)) return {'FINISHED'} for o in chainops: @@ -345,7 +352,7 @@ class PathExportChain(bpy.types.Operator): def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain,context)[0] + return cam.isChainValid(chain, context)[0] def execute(self, context): s = bpy.context.scene @@ -374,20 +381,21 @@ def execute(self, context): s = bpy.context.scene operation = s.cam_operations[s.cam_active_operation] - print("EXPORTING", operation.filename, bpy.data.objects["cam_path_{}".format(operation.name)].data, operation) + print("EXPORTING", operation.filename, + bpy.data.objects["cam_path_{}".format(operation.name)].data, operation) gcodepath.exportGcodePath(operation.filename, [bpy.data.objects["cam_path_{}".format(operation.name)].data], [operation]) return {'FINISHED'} -class CAMSimulate(bpy.types.Operator,AsyncOperatorMixin): +class CAMSimulate(bpy.types.Operator, AsyncOperatorMixin): """simulate CAM operation this is performed by: creating an image, painting Z depth of the brush substractively. Works only for some operations, can not be used for 4-5 axis.""" bl_idname = "object.cam_simulate" bl_label = "CAM simulation" - bl_options = {'REGISTER', 'UNDO','BLOCKING'} + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} operation: StringProperty(name="Operation", description="Specify the operation to calculate", default='Operation') @@ -404,7 +412,7 @@ async def execute_async(self, context): except AsyncCancelledException as e: return {'CANCELLED'} else: - self.report({'ERROR'},'no computed path to simulate') + self.report({'ERROR'}, 'no computed path to simulate') return {'FINISHED'} return {'FINISHED'} @@ -418,13 +426,13 @@ class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin): to see how ops work together.""" bl_idname = "object.cam_simulate_chain" bl_label = "CAM simulation" - bl_options = {'REGISTER', 'UNDO','BLOCKING'} + bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain,context)[0] + return cam.isChainValid(chain, context)[0] operation: StringProperty(name="Operation", description="Specify the operation to calculate", default='Operation') @@ -608,7 +616,7 @@ def Add_Pocket(self, maxdepth, sname, new_cutter_diameter): if not mpocket_exists: # create a pocket operation if it does not exist already s.cam_operations.add() o = s.cam_operations[-1] - o.object_name= 'medial_pocket' + o.object_name = 'medial_pocket' s.cam_active_operation = len(s.cam_operations) - 1 o.name = 'MedialPocket' o.filename = o.name @@ -618,6 +626,7 @@ def Add_Pocket(self, maxdepth, sname, new_cutter_diameter): o.material.size[2] = -maxdepth o.minz_from = 'MATERIAL' + class CamOperationAdd(bpy.types.Operator): """Add new CAM operation""" bl_idname = "scene.cam_operation_add" @@ -671,7 +680,8 @@ def execute(self, context): fixUnits() scene = bpy.context.scene - if len(scene.cam_operations) == 0: return {'CANCELLED'} + if len(scene.cam_operations) == 0: + return {'CANCELLED'} copyop = scene.cam_operations[scene.cam_active_operation] scene.cam_operations.add() scene.cam_active_operation += 1 @@ -716,7 +726,8 @@ def poll(cls, context): def execute(self, context): scene = context.scene try: - if len(scene.cam_operations) == 0: return {'CANCELLED'} + if len(scene.cam_operations) == 0: + return {'CANCELLED'} active_op = scene.cam_operations[scene.cam_active_operation] active_op_object = bpy.data.objects[active_op.name] scene.objects.active = active_op_object diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index ce72f8fe0..8f32735c4 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -25,7 +25,8 @@ from shapely import geometry as sgeometry from shapely import affinity, prepared from shapely import speedups -import random, time +import random +import time import mathutils from mathutils import Vector diff --git a/scripts/addons/cam/parametric.py b/scripts/addons/cam/parametric.py index 0b24fbd99..2f734c99c 100644 --- a/scripts/addons/cam/parametric.py +++ b/scripts/addons/cam/parametric.py @@ -36,13 +36,14 @@ """ + + + import math from math import sin, cos, pi import bmesh import bpy from mathutils import Vector - - def derive_bezier_handles(a, b, c, d, tb, tc): """ Derives bezier handles by using the start and end of the curve with 2 intermediate @@ -182,7 +183,7 @@ def create_parametric_curve( curve_object = bpy.data.objects.new('Parametric', curve) context = bpy.context scene = context.scene - link_object = scene.collection.objects.link + link_object = scene.collection.objects.link link_object(curve_object) # Return the new object @@ -246,4 +247,3 @@ def make_edge_loops(*objects): # Join them together bpy.ops.object.join(ctx) - diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index 3fbc415ba..0533489c6 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -93,11 +93,13 @@ def getPathPatternParallel(o, angle): v1.rotate(e1) axis_across_paths = numpy.array((numpy.arange(int(-dim / pathd), int(dim / pathd)) * pathd * v1.x + xm, - numpy.arange(int(-dim / pathd), int(dim / pathd)) * pathd * v1.y + ym, + numpy.arange(int(-dim / pathd), + int(dim / pathd)) * pathd * v1.y + ym, numpy.arange(int(-dim / pathd), int(dim / pathd)) * 0)) axis_along_paths = numpy.array((numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * pathstep * v.x, - numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * pathstep * v.y, + numpy.arange(int(-dim / pathstep), + int(dim / pathstep)) * pathstep * v.y, numpy.arange(int(-dim / pathstep), int(dim / pathstep)) * 0 + zlevel)) # rotate this first progress(axis_along_paths) @@ -247,7 +249,7 @@ def getPathPattern(operation): if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): - ##TODO + # TODO chunk.flipX(o.max.x+o.min.x) # for si in range(0, len(chunk.points)): # s = chunk.points[si] @@ -289,7 +291,7 @@ def getPathPattern(operation): else: if len(chunk.points) > 0: chunk.closed = False - chunk=chunk.to_chunk() + chunk = chunk.to_chunk() pathchunks.append(chunk) currentstepchunks.append(chunk) chunk = camPathChunkBuilder([]) @@ -299,7 +301,7 @@ def getPathPattern(operation): chunk.points.append(firstchunk.points[0]) if chunk == firstchunk: chunk.closed = True - chunk=chunk.to_chunk() + chunk = chunk.to_chunk() pathchunks.append(chunk) currentstepchunks.append(chunk) chunk = camPathChunkBuilder([]) @@ -541,7 +543,7 @@ def getPathPattern4axis(operation): cutterstart.rotate(e) cutterend.rotate(e) - chunk=chunk.to_chunk() + chunk = chunk.to_chunk() chunk.depth = radiusend - radius pathchunks.append(chunk) diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index e845dca62..362911f4c 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -127,7 +127,8 @@ def shapelyToCoords(anydata): def shapelyToCurve(name, p, z): - import bpy, bmesh + import bpy + import bmesh from bpy_extras import object_utils verts = [] edges = [] diff --git a/scripts/addons/cam/puzzle_joinery.py b/scripts/addons/cam/puzzle_joinery.py index 674f842b0..19b26faf5 100644 --- a/scripts/addons/cam/puzzle_joinery.py +++ b/scripts/addons/cam/puzzle_joinery.py @@ -198,7 +198,7 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= simple.make_active('twist_keep_f') simple.rotate(-math.pi / 2) simple.move(x=-width / 2) - + simple.remove_multiple("_") # Remove temporary base and holes simple.remove_multiple("fingers") # Remove temporary base and holes @@ -755,4 +755,3 @@ def tile(diameter, tolerance, tile_x_amount, tile_y_amount, stem=1): simple.move(x=width/2) simple.difference('_', '_base') simple.active_name('tile_ ' + str(tile_x_amount) + '_' + str(tile_y_amount)) - diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index ff9419b62..fe9d4d8b5 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -20,7 +20,7 @@ # ***** END GPL LICENCE BLOCK ***** # Solves: No module named 'shapely' (even if it is installed) -#help('modules') +# help('modules') import math import sys @@ -256,6 +256,7 @@ def union(name): remove_multiple(name) rename('unionboolean', name) + def intersect(name): select_multiple(name) bpy.ops.object.curve_boolean(boolean_type='INTERSECT') @@ -263,6 +264,8 @@ def intersect(name): # boolean difference of objects starting with name result is object from basename. # all objects starting with name will be deleted and the result will be basename + + def difference(name, basename): # name is the series to select # basename is what the base you want to cut including name diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 436cd71c5..13b10be32 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -42,7 +42,8 @@ def createSimulationObject(name, operations, i): if oname in bpy.data.objects: ob = bpy.data.objects[oname] else: - bpy.ops.mesh.primitive_plane_add(align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0)) + bpy.ops.mesh.primitive_plane_add( + align='WORLD', enter_editmode=False, location=(0, 0, 0), rotation=(0, 0, 0)) ob = bpy.context.active_object ob.name = oname @@ -120,13 +121,13 @@ async def generateSimulationImage(operations, limits): resy = math.ceil(sy / simulation_detail) + 2 * borderwidth # create array in which simulation happens, similar to an image to be painted in. - si = np.full(shape=(resx,resy),fill_value=maxz,dtype=float) + si = np.full(shape=(resx, resy), fill_value=maxz, dtype=float) - num_operations=len(operations) + num_operations = len(operations) - start_time=time.time() + start_time = time.time() - for op_count,o in enumerate(operations): + for op_count, o in enumerate(operations): ob = bpy.data.objects["cam_path_{}".format(o.name)] m = ob.data verts = m.vertices @@ -160,8 +161,8 @@ async def generateSimulationImage(operations, limits): for i, vert in enumerate(verts): if perc != int(100 * i / vtotal): perc = int(100 * i / vtotal) - total_perc = (perc+ op_count*100) / num_operations - await progress_async(f'Simulation',int(total_perc)) + total_perc = (perc + op_count*100) / num_operations + await progress_async(f'Simulation', int(total_perc)) if i > 0: volume = 0 @@ -183,14 +184,17 @@ async def generateSimulationImage(operations, limits): lastxs = xs lastys = ys while v.length < l: - xs = int((lasts.x + v.x - minx) / simulation_detail + borderwidth + simulation_detail / 2) + xs = int((lasts.x + v.x - minx) / simulation_detail + + borderwidth + simulation_detail / 2) # -middle - ys = int((lasts.y + v.y - miny) / simulation_detail + borderwidth + simulation_detail / 2) + ys = int((lasts.y + v.y - miny) / simulation_detail + + borderwidth + simulation_detail / 2) # -middle z = lasts.z + v.z # print(z) if lastxs != xs or lastys != ys: - volume_partial = simCutterSpot(xs, ys, z, cutterArray, si, o.do_simulation_feedrate) + volume_partial = simCutterSpot( + xs, ys, z, cutterArray, si, o.do_simulation_feedrate) if o.do_simulation_feedrate: totalvolume += volume volume += volume_partial @@ -200,9 +204,12 @@ async def generateSimulationImage(operations, limits): dropped += 1 v.length += simulation_detail - xs = int((s.x - minx) / simulation_detail + borderwidth + simulation_detail / 2) # -middle - ys = int((s.y - miny) / simulation_detail + borderwidth + simulation_detail / 2) # -middle - volume_partial = simCutterSpot(xs, ys, s.z, cutterArray, si, o.do_simulation_feedrate) + xs = int((s.x - minx) / simulation_detail + + borderwidth + simulation_detail / 2) # -middle + ys = int((s.y - miny) / simulation_detail + + borderwidth + simulation_detail / 2) # -middle + volume_partial = simCutterSpot( + xs, ys, s.z, cutterArray, si, o.do_simulation_feedrate) if o.do_simulation_feedrate: # compute volumes and write data into shapekey. volume += volume_partial totalvolume += volume @@ -278,7 +285,7 @@ async def generateSimulationImage(operations, limits): si = si[borderwidth:-borderwidth, borderwidth:-borderwidth] si += -minz - await progress_async("Simulated:",time.time()-start_time,'s') + await progress_async("Simulated:", time.time()-start_time, 's') return si @@ -288,7 +295,7 @@ def getCutterArray(operation, pixsize): r = operation.cutter_diameter / 2 + operation.skin # /operation.pixsize res = math.ceil((r * 2) / pixsize) m = res / 2.0 - car = np.full(shape=(res,res),fill_value=-10.0,dtype=float) + car = np.full(shape=(res, res), fill_value=-10.0, dtype=float) v = mathutils.Vector((0, 0, 0)) ps = pixsize diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index 1b4639f5a..887da44b8 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -48,7 +48,7 @@ def slicing2d(ob, height): # April 2020 Alain Pelletier return True -def slicing3d(ob, start, end): # April 2020 Alain Pelletier +def slicing3d(ob, start, end): # April 2020 Alain Pelletier # let's slice things bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) bpy.ops.object.mode_set(mode='EDIT') # force edit mode @@ -111,7 +111,8 @@ def sliceObject(ob): # April 2020 Alain Pelletier obslice = bpy.context.view_layer.objects.active # attribute active object to obslice scollection.objects.link(obslice) # link obslice to scollecton if slice3d: - slicing3d(obslice, height, height + thickness) # slice 3d at desired height and stop at desired height + # slice 3d at desired height and stop at desired height + slicing3d(obslice, height, height + thickness) else: slicesuccess = slicing2d(obslice, height) # slice object at desired height diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 7b637c91f..5e36950ee 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -63,7 +63,7 @@ async def cutout(o): c_offset = -max_depth * math.tan(cutter_angle) + o.ball_radius elif o.cutter_type == 'BALLNOSE': r = o.cutter_diameter / 2 - print("cutter radius:", r," skin",o.skin) + print("cutter radius:", r, " skin", o.skin) if -max_depth < r: c_offset = math.sqrt(r ** 2 - (r + max_depth) ** 2) print("offset:", c_offset) @@ -106,7 +106,7 @@ async def cutout(o): path_distance = o.dist_between_paths if o.cut_type == "INSIDE": path_distance *= -1 - p = p.buffer(distance = path_distance, resolution=o.optimisation.circle_detail, join_style=join, + p = p.buffer(distance=path_distance, resolution=o.optimisation.circle_detail, join_style=join, mitre_limit=2) chunksFromCurve.extend(shapelyToChunks(p, -1)) @@ -267,7 +267,7 @@ async def proj_curve(s, o): for chi, ch in enumerate(pathSamples): cht = tsamples[chi].get_points() ch.depth = 0 - ch_points=ch.get_points() + ch_points = ch.get_points() for i, s in enumerate(ch_points): # move the points a bit ep = Vector(cht[i]) @@ -333,14 +333,16 @@ async def pocket(o): prest = p.buffer(-c_offset, o.optimisation.circle_detail) while not p.is_empty: if o.pocketToCurve: - polygon_utils_cam.shapelyToCurve('3dpocket', p, 0.0) # make a curve starting with _3dpocket + # make a curve starting with _3dpocket + polygon_utils_cam.shapelyToCurve('3dpocket', p, 0.0) nchunks = shapelyToChunks(p, o.min.z) # print("nchunks") pnew = p.buffer(-o.dist_between_paths, o.optimisation.circle_detail) if pnew.is_empty: - pt = p.buffer(-c_offset, o.optimisation.circle_detail) # test if the last curve will leave material + # test if the last curve will leave material + pt = p.buffer(-c_offset, o.optimisation.circle_detail) if not pt.is_empty: pnew = pt # print("pnew") @@ -403,7 +405,7 @@ async def pocket(o): for v in h: nhelix.append((2 * p[0] - v[0], v[1], v[2])) h = nhelix - ch.extend(h,at_index=0) + ch.extend(h, at_index=0) # ch.points = h + ch.points else: @@ -411,7 +413,8 @@ async def pocket(o): ch.closed = True ch.rampZigZag(l[0], l[1], o) # Arc retract here first try: - if o.movement.retract_tangential: # TODO: check for entry and exit point before actual computing... will be much better. + # TODO: check for entry and exit point before actual computing... will be much better. + if o.movement.retract_tangential: # TODO: fix this for CW and CCW! for chi, ch in enumerate(lchunks): # print(chunksFromCurve[chi]) @@ -437,7 +440,8 @@ async def pocket(o): p = (p.x, p.y, p.z) # progress(str((v1,v,p))) - h = Helix(o.movement.retract_radius, o.optimisation.circle_detail, p[2] + o.movement.retract_height, p, revolutions) + h = Helix(o.movement.retract_radius, o.optimisation.circle_detail, + p[2] + o.movement.retract_height, p, revolutions) e = Euler((0, 0, rotangle + pi)) # angle to rotate whole retract move rothelix = [] @@ -476,10 +480,10 @@ async def pocket(o): if o.movement.ramp: for ch in chunks: ch.rampZigZag(ch.zstart, ch.get_point(0)[2], o) - + if o.first_down: if o.pocket_option == "OUTSIDE": - chunks.reverse() + chunks.reverse() chunks = await utils.sortChunks(chunks, o) if o.pocketToCurve: # make curve instead of a path @@ -626,16 +630,16 @@ async def medial_axis(o): polys = utils.getOperationSilhouete(o) if isinstance(polys, list): - if len(polys)==1 and isinstance(polys[0],shapely.MultiPolygon): - mpoly=polys[0] + if len(polys) == 1 and isinstance(polys[0], shapely.MultiPolygon): + mpoly = polys[0] else: - mpoly=sgeometry.MultiPolygon(polys) - elif isinstance(polys,shapely.MultiPolygon): + mpoly = sgeometry.MultiPolygon(polys) + elif isinstance(polys, shapely.MultiPolygon): # just a multipolygon - mpoly=polys + mpoly = polys else: raise CamException("Failed getting object silhouette. Is input curve closed?") - + mpoly_boundary = mpoly.boundary ipol = 0 for poly in mpoly.geoms: @@ -671,7 +675,8 @@ async def medial_axis(o): vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] # vertsPts= [Point(vert[0], vert[1]) for vert in verts] - pts, edgesIdx = computeVoronoiDiagram(vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True) + pts, edgesIdx = computeVoronoiDiagram( + vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True) # pts=[[pt[0], pt[1], zPosition] for pt in pts] newIdx = 0 @@ -727,7 +732,8 @@ async def medial_axis(o): do = False if do: filteredEdgs.append((vertr[e[0]][1], vertr[e[1]][1])) - ledges.append(sgeometry.LineString((filteredPts[vertr[e[0]][1]], filteredPts[vertr[e[1]][1]]))) + ledges.append(sgeometry.LineString( + (filteredPts[vertr[e[0]][1]], filteredPts[vertr[e[1]][1]]))) # print(ledges[-1].has_z) bufpoly = poly.buffer(-new_cutter_diameter / 2, resolution=64) @@ -785,8 +791,8 @@ def getLayers(operation, startdepth, enddepth): """ if startdepth < enddepth: raise CamException("Start depth is lower than end depth. " - "If you have set a custom depth end, it must be lower than depth start, " - "and should usually be negative. Set this in the CAM Operation Area panel.") + "If you have set a custom depth end, it must be lower than depth start, " + "and should usually be negative. Set this in the CAM Operation Area panel.") if operation.use_layers: layers = [] n = math.ceil((startdepth - enddepth) / operation.stepdown) @@ -848,7 +854,8 @@ def chunksToMesh(chunks, o): ch = chunks[chi] # print(chunks) # print (ch) - if ch.count() > 0: # TODO: there is a case where parallel+layers+zigzag ramps send empty chunks here... + # TODO: there is a case where parallel+layers+zigzag ramps send empty chunks here... + if ch.count() > 0: # print(len(ch.points)) nverts = [] if o.optimisation.optimize: @@ -930,7 +937,8 @@ def chunksToMesh(chunks, o): print(len(shapek.data)) print(len(verts_rotations)) - for i, co in enumerate(verts_rotations): # TODO: optimize this. this is just rewritten too many times... + # TODO: optimize this. this is just rewritten too many times... + for i, co in enumerate(verts_rotations): shapek.data[i].co = co print(time.time() - t) diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 5c124c23c..0ad5bd99b 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -27,7 +27,8 @@ def addTestCurve(loc): - bpy.ops.curve.primitive_bezier_circle_add(radius=.05, align='WORLD', enter_editmode=False, location=loc) + bpy.ops.curve.primitive_bezier_circle_add( + radius=.05, align='WORLD', enter_editmode=False, location=loc) bpy.ops.object.editmode_toggle() bpy.ops.curve.duplicate() bpy.ops.transform.resize(value=(0.5, 0.5, 0.5), constraint_axis=(False, False, False), @@ -121,7 +122,7 @@ def testWaterline(pos): def testSimulation(): - pass; + pass def cleanUp(): diff --git a/scripts/addons/cam/tests/gcode_generator.py b/scripts/addons/cam/tests/gcode_generator.py index e3ad5bba2..a74adb8c2 100644 --- a/scripts/addons/cam/tests/gcode_generator.py +++ b/scripts/addons/cam/tests/gcode_generator.py @@ -17,4 +17,3 @@ bpy.ops.object.calculate_cam_path() sys.exit(0) - diff --git a/scripts/addons/cam/tests/install_addon.py b/scripts/addons/cam/tests/install_addon.py index 7384e25ce..fd55faa51 100644 --- a/scripts/addons/cam/tests/install_addon.py +++ b/scripts/addons/cam/tests/install_addon.py @@ -4,7 +4,7 @@ import pathlib import shutil -INSTALL_CODE=f""" +INSTALL_CODE = f""" import bpy bpy.ops.preferences.addon_install(filepath='{sys.argv[1]}') bpy.ops.preferences.addon_enable(module='cam') @@ -12,30 +12,29 @@ import cam """ -NUM_RETRIES=10 +NUM_RETRIES = 10 with tempfile.TemporaryDirectory() as td: - file=pathlib.Path(td,"install.py") - file.write_text(INSTALL_CODE) - - # blender 4.0 installing addon crashes sometimes on mac github actions... - for x in range(NUM_RETRIES): - try: - subprocess.run([shutil.which('blender'),'-b','-P',str(file)], shell=False, check=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,text=True) - print("installed addon okay") - sys.exit(0) - except subprocess.CalledProcessError as e: - print("Install addon failed, retrying:",e) - print("Command output:") - print("------------------------------") - print(e.output) - print("------------------------------") - for line in str(e.output): - if line.startswith("Writing: "): - crash_file=pathlib.Path(line[len("Writing: "):]) - if crash_file.exists(): - print("Crash log:\n================") - print(crash_file.read_text()) - print("============================") - + file = pathlib.Path(td, "install.py") + file.write_text(INSTALL_CODE) + # blender 4.0 installing addon crashes sometimes on mac github actions... + for x in range(NUM_RETRIES): + try: + subprocess.run([shutil.which('blender'), '-b', '-P', str(file)], shell=False, + check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + print("installed addon okay") + sys.exit(0) + except subprocess.CalledProcessError as e: + print("Install addon failed, retrying:", e) + print("Command output:") + print("------------------------------") + print(e.output) + print("------------------------------") + for line in str(e.output): + if line.startswith("Writing: "): + crash_file = pathlib.Path(line[len("Writing: "):]) + if crash_file.exists(): + print("Crash log:\n================") + print(crash_file.read_text()) + print("============================") diff --git a/scripts/addons/cam/tests/test_suite.py b/scripts/addons/cam/tests/test_suite.py index 0ad4c79e4..6fcbf386f 100644 --- a/scripts/addons/cam/tests/test_suite.py +++ b/scripts/addons/cam/tests/test_suite.py @@ -4,6 +4,7 @@ import os import sys + class BlenderCAMTest(unittest.TestCase): @classmethod def setUpClass(cls): @@ -66,25 +67,26 @@ def run_test_case(self, test_case): # Compare the generated and expected gcode for each operation for gcode_file in test_case['gcode_files']: with self.subTest(operation=f"{test_case['subdir_name']}/{gcode_file}"): - generated = self.get_gcode_from_file(gcode_file[1:]) + generated = self.get_gcode_from_file(gcode_file[1:]) expected = self.get_gcode_from_file(gcode_file) - if sys.platform=='darwin' and os.path.exists(gcode_file+".mac"): + if sys.platform == 'darwin' and os.path.exists(gcode_file+".mac"): # bullet physics gives slightly different results on mac sometimes... # this is something we can't fix, so compare against mac generated test # file - print("Using mac test file",len(expected),len(generated)) + print("Using mac test file", len(expected), len(generated)) expected = self.get_gcode_from_file(gcode_file+".mac") self.assertMultiLineEqual(generated, expected, - msg = "\n"+self.get_diff(gcode_file[1:], gcode_file+".mac")) + msg="\n"+self.get_diff(gcode_file[1:], gcode_file+".mac")) else: self.assertMultiLineEqual(generated, expected, - msg = "\n"+self.get_diff(gcode_file[1:], gcode_file)) + msg="\n"+self.get_diff(gcode_file[1:], gcode_file)) os.remove(gcode_file[1:]) # cleanup generated file unless test fails + if __name__ == '__main__': # Add a test method for each test case to the TestCase class for test_case in BlenderCAMTest.get_test_cases(): - test_func = lambda self, tc=test_case: self.run_test_case(tc) + def test_func(self, tc=test_case): return self.run_test_case(tc) setattr(BlenderCAMTest, f'test_{test_case["subdir_name"]}', test_func) unittest.main() diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index 165286227..ba094b310 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -34,22 +34,22 @@ from cam import gcodeimportparser, simple from cam.simple import * -from cam.ui_panels.buttons_panel import CAMButtonsPanel -from cam.ui_panels.interface import * -from cam.ui_panels.info import * -from cam.ui_panels.operations import * -from cam.ui_panels.cutter import * -from cam.ui_panels.machine import * -from cam.ui_panels.material import * -from cam.ui_panels.chains import * -from cam.ui_panels.op_properties import * -from cam.ui_panels.movement import * -from cam.ui_panels.feedrate import * -from cam.ui_panels.optimisation import * -from cam.ui_panels.area import * -from cam.ui_panels.gcode import * -from cam.ui_panels.pack import * -from cam.ui_panels.slice import * +from cam.ui_panels.buttons_panel import CAMButtonsPanel +from cam.ui_panels.interface import * +from cam.ui_panels.info import * +from cam.ui_panels.operations import * +from cam.ui_panels.cutter import * +from cam.ui_panels.machine import * +from cam.ui_panels.material import * +from cam.ui_panels.chains import * +from cam.ui_panels.op_properties import * +from cam.ui_panels.movement import * +from cam.ui_panels.feedrate import * +from cam.ui_panels.optimisation import * +from cam.ui_panels.area import * +from cam.ui_panels.gcode import * +from cam.ui_panels.pack import * +from cam.ui_panels.slice import * class CAM_UL_orientations(UIList): @@ -62,8 +62,6 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn layout.label(text="", icon_value=icon) - - # panel containing all tools class VIEW3D_PT_tools_curvetools(bpy.types.Panel): diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index c52456939..4f5b7db22 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -20,7 +20,8 @@ class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_use_layers(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return col = self.layout.column(align=True) row = col.row(align=True) row.prop(self.op, 'use_layers') @@ -29,14 +30,16 @@ def draw_use_layers(self): self.draw_first_down(col) def draw_first_down(self, col): - if not self.has_correct_level(): return - if self.op.strategy in ['CUTOUT','POCKET','MEDIAL_AXIS']: + if not self.has_correct_level(): + return + if self.op.strategy in ['CUTOUT', 'POCKET', 'MEDIAL_AXIS']: row = col.row(align=True) row.label(text="") row.prop(self.op, 'first_down') def draw_maxz(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'maxz') self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: @@ -45,7 +48,8 @@ def draw_maxz(self): self.layout.label(text='!ERROR! COLLISION!') def draw_minz(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: if self.op.strategy == 'CURVE': self.layout.label(text="cannot use depth from object using CURVES") @@ -75,7 +79,8 @@ def draw_minz(self): col.prop(self.op, 'source_image_crop_end_y', text='end y') def draw_ambient(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: self.layout.prop(self.op, 'ambient_behaviour') if self.op.ambient_behaviour == 'AROUND': @@ -83,7 +88,8 @@ def draw_ambient(self): self.layout.prop(self.op, "ambient_cutter_restrict") def draw_limit_curve(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: self.layout.prop(self.op, 'use_limit_curve') if self.op.use_limit_curve: @@ -97,4 +103,3 @@ def draw(self, context): self.draw_minz() self.draw_ambient() self.draw_limit_curve() - diff --git a/scripts/addons/cam/ui_panels/buttons_panel.py b/scripts/addons/cam/ui_panels/buttons_panel.py index e645085be..c3a118acf 100644 --- a/scripts/addons/cam/ui_panels/buttons_panel.py +++ b/scripts/addons/cam/ui_panels/buttons_panel.py @@ -2,6 +2,8 @@ import inspect # Panel definitions + + class CAMButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 663d0b247..fdd61e414 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -57,7 +57,8 @@ def draw(self, context): row = layout.row(align=True) if chain: - row.template_list("CAM_UL_operations", '', chain, "operations", chain, 'active_operation') + row.template_list("CAM_UL_operations", '', chain, + "operations", chain, 'active_operation') col = row.column(align=True) col.operator("scene.cam_chain_operation_add", icon='ADD', text="") col.operator("scene.cam_chain_operation_remove", icon='REMOVE', text="") @@ -66,13 +67,14 @@ def draw(self, context): col.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="") if not chain.computing: - layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode") + layout.operator("object.calculate_cam_paths_chain", + text="Calculate chain paths & Export Gcode") layout.operator("object.cam_export_paths_chain", text="Export chain gcode") layout.operator("object.cam_simulate_chain", text="Simulate this chain") - valid,reason=cam.isChainValid(chain,context) + valid, reason = cam.isChainValid(chain, context) if not valid: - layout.label(icon="ERROR",text=f"Can't compute chain - reason:\n") + layout.label(icon="ERROR", text=f"Can't compute chain - reason:\n") layout.label(text=reason) else: layout.label(text='chain is currently computing') diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index e4640a755..a74fa7da7 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -27,42 +27,50 @@ class CAM_CUTTER_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_cutter_preset_menu(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return row = self.layout.row(align=True) row.menu("CAM_CUTTER_MT_presets", text=bpy.types.CAM_CUTTER_MT_presets.bl_label) row.operator("render.cam_preset_cutter_add", text="", icon='ADD') row.operator("render.cam_preset_cutter_add", text="", icon='REMOVE').remove_active = True def draw_cutter_id(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'cutter_id') def draw_cutter_type(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'cutter_type') def draw_ball_radius(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['BALLCONE']: self.layout.prop(self.op, 'ball_radius') def draw_bull_radius(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['BULLNOSE']: self.layout.prop(self.op, 'bull_corner_radius') def draw_cylcone_diameter(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['CYLCONE']: self.layout.prop(self.op, 'cylcone_diameter') def draw_cutter_tip_angle(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['VCARVE', 'BALLCONE', 'BULLNOSE', 'CYLCONE']: self.layout.prop(self.op, 'cutter_tip_angle') def draw_laser(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['LASER']: self.layout.prop(self.op, 'Laser_on') self.layout.prop(self.op, 'Laser_off') @@ -70,7 +78,8 @@ def draw_laser(self): self.layout.prop(self.op, 'Laser_delay') def draw_plasma(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['PLASMA']: self.layout.prop(self.op, 'Plasma_on') self.layout.prop(self.op, 'Plasma_off') @@ -80,31 +89,39 @@ def draw_plasma(self): self.layout.prop(self.op, 'lead_out') def draw_custom(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['CUSTOM']: if self.op.optimisation.use_exact: - self.layout.label(text='Warning - only convex shapes are supported. ', icon='COLOR_RED') + self.layout.label( + text='Warning - only convex shapes are supported. ', icon='COLOR_RED') self.layout.label(text='If your custom cutter is concave,') self.layout.label(text='switch exact mode off.') self.layout.prop_search(self.op, "cutter_object_name", bpy.data, "objects") def draw_cutter_diameter(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'cutter_diameter') def draw_cutter_flutes(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type not in ['LASER', 'PLASMA']: self.layout.prop(self.op, 'cutter_flutes') def draw_cutter_description(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'cutter_description') def draw_engagement(self): - if not self.has_correct_level(): return - if self.op.cutter_type in ['LASER', 'PLASMA']: return - if self.op.strategy in ['CUTOUT']: return + if not self.has_correct_level(): + return + if self.op.cutter_type in ['LASER', 'PLASMA']: + return + if self.op.strategy in ['CUTOUT']: + return if self.op.cutter_type in ['BALLCONE']: engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) @@ -133,5 +150,3 @@ def draw(self, context): self.draw_cutter_flutes() self.draw_cutter_description() self.draw_engagement() - - diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index b71a3cfe1..88cdf330b 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -1,6 +1,7 @@ import bpy from cam.ui_panels.buttons_panel import CAMButtonsPanel + class CAM_FEEDRATE_Panel(CAMButtonsPanel, bpy.types.Panel): """CAM feedrate panel""" bl_label = "CAM feedrate" @@ -16,23 +17,28 @@ class CAM_FEEDRATE_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_feedrate(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'feedrate') def draw_sim_feedrate(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'do_simulation_feedrate') def draw_plunge_feedrate(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'plunge_feedrate') def draw_plunge_angle(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'plunge_angle') def draw_spindle_rpm(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'spindle_rpm') def draw(self, context): diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index 20c934100..9e1b20b48 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -18,33 +18,38 @@ class CAM_GCODE_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_output_header(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'output_header') if self.op.output_header: self.layout.prop(self.op, 'gcode_header') def draw_output_trailer(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'output_trailer') if self.op.output_trailer: self.layout.prop(self.op, 'gcode_trailer') def draw_enable_dust(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'enable_dust') if self.op.enable_dust: self.layout.prop(self.op, 'gcode_start_dust_cmd') self.layout.prop(self.op, 'gcode_stop_dust_cmd') def draw_enable_hold(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'enable_hold') if self.op.enable_hold: self.layout.prop(self.op, 'gcode_start_hold_cmd') self.layout.prop(self.op, 'gcode_stop_hold_cmd') def draw_enable_mist(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'enable_mist') if self.op.enable_mist: self.layout.prop(self.op, 'gcode_start_mist_cmd') diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index 313640cbc..b59f6a731 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -9,6 +9,7 @@ # Info panel # This panel gives general information about the current operation + class CAM_INFO_Properties(bpy.types.PropertyGroup): warnings: bpy.props.StringProperty( @@ -45,16 +46,19 @@ class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel): # Draw blendercam version (and whether there are updates available) def draw_blendercam_version(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.label(text=f'Blendercam version: {".".join([str(x) for x in cam_version])}') - if len(bpy.context.preferences.addons['cam'].preferences.new_version_available)>0: + if len(bpy.context.preferences.addons['cam'].preferences.new_version_available) > 0: self.layout.label(text=f"New version available:") - self.layout.label(text=f" {bpy.context.preferences.addons['cam'].preferences.new_version_available}") + self.layout.label( + text=f" {bpy.context.preferences.addons['cam'].preferences.new_version_available}") self.layout.operator("render.cam_update_now") # Display the OpenCamLib version def draw_opencamlib_version(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return opencamlib_version = cam.utils.opencamlib_version() if opencamlib_version is None: self.layout.label(text="Opencamlib is not installed") @@ -64,14 +68,16 @@ def draw_opencamlib_version(self): # Display warnings related to the current operation def draw_op_warnings(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return for line in self.op.info.warnings.rstrip("\n").split("\n"): if len(line) > 0: self.layout.label(text=line, icon='ERROR') # Display the time estimation for the current operation def draw_op_time(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if not int(self.op.info.duration * 60) > 0: return @@ -86,7 +92,8 @@ def draw_op_time(self): # Display the chipload (does this work ?) def draw_op_chipload(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if not self.op.info.chipload > 0: return @@ -95,7 +102,8 @@ def draw_op_chipload(self): # Display the current operation money cost def draw_op_money_cost(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if not int(self.op.info.duration * 60) > 0: return @@ -119,4 +127,4 @@ def draw(self, context): if self.op: self.draw_op_warnings() self.draw_op_time() - self.draw_op_money_cost() \ No newline at end of file + self.draw_op_money_cost() diff --git a/scripts/addons/cam/ui_panels/interface.py b/scripts/addons/cam/ui_panels/interface.py index f6fdf863f..56bccaf7a 100644 --- a/scripts/addons/cam/ui_panels/interface.py +++ b/scripts/addons/cam/ui_panels/interface.py @@ -6,9 +6,9 @@ import cam.constants -def update_interface(self,context): +def update_interface(self, context): # set default for new files - context.preferences.addons['cam'].preferences.default_interface_level=context.scene.interface.level + context.preferences.addons['cam'].preferences.default_interface_level = context.scene.interface.level bpy.ops.wm.save_userpref() diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index dec24a448..6ffe2c9f3 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -2,6 +2,7 @@ import bpy from cam.ui_panels.buttons_panel import CAMButtonsPanel + class CAM_MACHINE_Panel(CAMButtonsPanel, bpy.types.Panel): """CAM machine panel""" bl_label = "CAM Machine" @@ -26,28 +27,33 @@ class CAM_MACHINE_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_presets(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return row = self.layout.row(align=True) row.menu("CAM_MACHINE_MT_presets", text=bpy.types.CAM_MACHINE_MT_presets.bl_label) row.operator("render.cam_preset_machine_add", text="", icon='ADD') row.operator("render.cam_preset_machine_add", text="", icon='REMOVE').remove_active = True def draw_post_processor(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'post_processor') def draw_split_files(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'eval_splitting') if self.machine.eval_splitting: self.layout.prop(self.machine, 'split_limit') def draw_system(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(bpy.context.scene.unit_settings, 'system') def draw_position_definitions(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'use_position_definitions') if self.machine.use_position_definitions: self.layout.prop(self.machine, 'starting_position') @@ -55,17 +61,20 @@ def draw_position_definitions(self): self.layout.prop(self.machine, 'ending_position') def draw_working_area(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'working_area') def draw_feedrates(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'feedrate_min') self.layout.prop(self.machine, 'feedrate_max') self.layout.prop(self.machine, 'feedrate_default') def draw_splindle_speeds(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return # TODO: spindle default and feedrate default should become part of the cutter definition... self.layout.prop(self.machine, 'spindle_min') self.layout.prop(self.machine, 'spindle_max') @@ -73,30 +82,35 @@ def draw_splindle_speeds(self): self.layout.prop(self.machine, 'spindle_default') def draw_tool_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'output_tool_definitions') self.layout.prop(self.machine, 'output_tool_change') if self.machine.output_tool_change: self.layout.prop(self.machine, 'output_g43_on_tool_change') def draw_suplemental_axis(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'axis4') self.layout.prop(self.machine, 'axis5') def draw_collet_size(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'collet_size') def draw_block_numbers(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'output_block_numbers') if self.machine.output_block_numbers: self.layout.prop(self.machine, 'start_block_number') self.layout.prop(self.machine, 'block_number_increment') def draw_hourly_rate(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.machine, 'hourly_rate') def draw(self, context): @@ -116,4 +130,3 @@ def draw(self, context): self.draw_collet_size() self.draw_block_numbers() self.draw_hourly_rate() - diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 68856ee13..9400afafe 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -76,9 +76,11 @@ def execute(self, context): return {'FINISHED'} def draw(self, context): - if not self.interface_level <= int(self.context.scene.interface.level): return + if not self.interface_level <= int(self.context.scene.interface.level): + return self.layout.prop_search(self, "operation", bpy.context.scene, "cam_operations") + class CAM_MATERIAL_Panel(CAMButtonsPanel, bpy.types.Panel): bl_label = "CAM Material size and position" bl_idname = "WORLD_PT_CAM_MATERIAL" @@ -91,12 +93,14 @@ class CAM_MATERIAL_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_estimate_from_image(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.geometry_source not in ['OBJECT', 'COLLECTION']: self.layout.label(text='Estimated from image') def draw_estimate_from_object(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: self.layout.prop(self.op.material, 'estimate_from_model') if self.op.material.estimate_from_model: @@ -109,7 +113,8 @@ def draw_estimate_from_object(self): # Display Axis alignment section def draw_axis_alignment(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: row_axis = self.layout.row() row_axis.prop(self.op.material, 'center_x') diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index 302fa35f2..fbb96f73d 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -10,7 +10,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): # movement parallel_step_back type: bpy.props.EnumProperty(name='Movement type', items=( ('CONVENTIONAL', 'Conventional / Up milling', - 'cutter rotates against the direction of the feed'), + 'cutter rotates against the direction of the feed'), ('CLIMB', 'Climb / Down milling', 'cutter rotates with the direction of the feed'), ('MEANDER', 'Meander / Zig Zag', @@ -18,87 +18,90 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): description='movement type', default='CLIMB', update=cam.utils.update_operation) insideout: bpy.props.EnumProperty(name='Direction', - items=(('INSIDEOUT', 'Inside out', 'a'), ('OUTSIDEIN', 'Outside in', 'a')), - description='approach to the piece', default='INSIDEOUT', - update=cam.utils.update_operation) + items=(('INSIDEOUT', 'Inside out', 'a'), + ('OUTSIDEIN', 'Outside in', 'a')), + description='approach to the piece', default='INSIDEOUT', + update=cam.utils.update_operation) spindle_rotation: bpy.props.EnumProperty(name='Spindle rotation', - items=(('CW', 'Clock wise', 'a'), ('CCW', 'Counter clock wise', 'a')), - description='Spindle rotation direction', default='CW', - update=cam.utils.update_operation) + items=(('CW', 'Clock wise', 'a'), + ('CCW', 'Counter clock wise', 'a')), + description='Spindle rotation direction', default='CW', + update=cam.utils.update_operation) free_height: bpy.props.FloatProperty(name="Free movement height", - default=0.01, min=0.0000, max=32, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) + default=0.01, min=0.0000, max=32, + precision=cam.constants.PRECISION, unit="LENGTH", + update=cam.utils.update_operation) useG64: bpy.props.BoolProperty(name="G64 trajectory", - description='Use only if your machine supports G64 code. LinuxCNC and Mach3 do', - default=False, update=cam.utils.update_operation) + description='Use only if your machine supports G64 code. LinuxCNC and Mach3 do', + default=False, update=cam.utils.update_operation) G64: bpy.props.FloatProperty(name="Path Control Mode with Optional Tolerance", - default=0.0001, min=0.0000, max=0.005, - precision=cam.constants.PRECISION, unit="LENGTH", update=cam.utils.update_operation) + default=0.0001, min=0.0000, max=0.005, + precision=cam.constants.PRECISION, unit="LENGTH", update=cam.utils.update_operation) parallel_step_back: bpy.props.BoolProperty(name="Parallel step back", - description='For roughing and finishing in one pass: mills material in climb mode, then steps back and goes between 2 last chunks back', - default=False, update=cam.utils.update_operation) + description='For roughing and finishing in one pass: mills material in climb mode, then steps back and goes between 2 last chunks back', + default=False, update=cam.utils.update_operation) helix_enter: bpy.props.BoolProperty(name="Helix enter - EXPERIMENTAL", - description="Enter material in helix", - default=False, update=cam.utils.update_operation) + description="Enter material in helix", + default=False, update=cam.utils.update_operation) ramp_in_angle: bpy.props.FloatProperty(name="Ramp in angle", default=math.pi / 6, - min=0, max=math.pi * 0.4999, - precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) + min=0, max=math.pi * 0.4999, + precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) helix_diameter: bpy.props.FloatProperty(name='Helix diameter - % of cutter diameter', - default=90, min=10, max=100, - precision=1, subtype='PERCENTAGE', update=cam.utils.update_operation) + default=90, min=10, max=100, + precision=1, subtype='PERCENTAGE', update=cam.utils.update_operation) ramp: bpy.props.BoolProperty(name="Ramp in - EXPERIMENTAL", - description="Ramps down the whole contour, so the cutline looks like helix", - default=False, update=cam.utils.update_operation) + description="Ramps down the whole contour, so the cutline looks like helix", + default=False, update=cam.utils.update_operation) ramp_out: bpy.props.BoolProperty(name="Ramp out - EXPERIMENTAL", - description="Ramp out to not leave mark on surface", default=False, - update=cam.utils.update_operation) + description="Ramp out to not leave mark on surface", default=False, + update=cam.utils.update_operation) ramp_out_angle: bpy.props.FloatProperty(name="Ramp out angle", - default=math.pi / 6, min=0, max=math.pi * 0.4999, - precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) + default=math.pi / 6, min=0, max=math.pi * 0.4999, + precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) retract_tangential: bpy.props.BoolProperty(name="Retract tangential - EXPERIMENTAL", - description="Retract from material in circular motion", default=False, - update=cam.utils.update_operation) + description="Retract from material in circular motion", default=False, + update=cam.utils.update_operation) retract_radius: bpy.props.FloatProperty(name='Retract arc radius', - default=0.001, min=0.000001, max=100, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) + default=0.001, min=0.000001, max=100, + precision=cam.constants.PRECISION, unit="LENGTH", + update=cam.utils.update_operation) retract_height: bpy.props.FloatProperty(name='Retract arc height', - default=0.001, min=0.00000, max=100, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) + default=0.001, min=0.00000, max=100, + precision=cam.constants.PRECISION, unit="LENGTH", + update=cam.utils.update_operation) stay_low: bpy.props.BoolProperty(name="Stay low if possible", - default=True, update=cam.utils.update_operation) + default=True, update=cam.utils.update_operation) merge_dist: bpy.props.FloatProperty(name="Merge distance - EXPERIMENTAL", - default=0.0, min=0.0000, max=0.1, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) + default=0.0, min=0.0000, max=0.1, + precision=cam.constants.PRECISION, unit="LENGTH", + update=cam.utils.update_operation) protect_vertical: bpy.props.BoolProperty(name="Protect vertical", - description="The path goes only vertically next to steep areas", - default=True, - update=cam.utils.update_operation) + description="The path goes only vertically next to steep areas", + default=True, + update=cam.utils.update_operation) protect_vertical_limit: bpy.props.FloatProperty(name="Verticality limit", - description="What angle is allready considered vertical", - default=math.pi / 45, min=0, max=math.pi * 0.5, precision=0, - subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) + description="What angle is allready considered vertical", + default=math.pi / 45, min=0, max=math.pi * 0.5, precision=0, + subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) + class CAM_MOVEMENT_Panel(CAMButtonsPanel, bpy.types.Panel): """CAM movement panel""" @@ -120,37 +123,43 @@ class CAM_MOVEMENT_Panel(CAMButtonsPanel, bpy.types.Panel): } def draw_cut_type(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.movement, 'type') if self.op.movement.type in ['BLOCK', 'SPIRAL', 'CIRCLES']: self.layout.prop(self.op.movement, 'insideout') def draw_spindle_rotation(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.movement, 'spindle_rotation') def draw_free_height(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: self.layout.label(text='Depth start > Free movement') self.layout.label(text='POSSIBLE COLLISION') def draw_use_g64(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.context.scene.cam_machine.post_processor not in cam.constants.G64_INCOMPATIBLE_MACHINES: self.layout.prop(self.op.movement, 'useG64') if self.op.movement.useG64: self.layout.prop(self.op.movement, 'G64') def draw_parallel_stepback(self): - if not self.has_correct_level(): return - if self.op.strategy in ['PARALLEL','CROSS']: + if not self.has_correct_level(): + return + if self.op.strategy in ['PARALLEL', 'CROSS']: if not self.op.movement.ramp: self.layout.prop(self.op.movement, 'parallel_step_back') def draw_helix_enter(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['POCKET']: self.layout.prop(self.op.movement, 'helix_enter') if self.op.movement.helix_enter: @@ -158,7 +167,8 @@ def draw_helix_enter(self): self.layout.prop(self.op.movement, 'helix_diameter') def draw_ramp(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.movement, 'ramp') if self.op.movement.ramp: self.layout.prop(self.op.movement, 'ramp_in_angle') @@ -167,7 +177,8 @@ def draw_ramp(self): self.layout.prop(self.op.movement, 'ramp_out_angle') def draw_retract_tangential(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['POCKET']: self.layout.prop(self.op.movement, 'retract_tangential') if self.op.movement.retract_tangential: @@ -175,13 +186,15 @@ def draw_retract_tangential(self): self.layout.prop(self.op.movement, 'retract_height') def draw_stay_low(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.movement, 'stay_low') if self.op.movement.stay_low: self.layout.prop(self.op.movement, 'merge_dist') def draw_protect_vertical(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type not in ['BALLCONE']: self.layout.prop(self.op.movement, 'protect_vertical') if self.op.movement.protect_vertical: @@ -200,4 +213,3 @@ def draw(self, context): self.draw_retract_tangential() self.draw_stay_low() self.draw_protect_vertical() - diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index 40d0996ec..05f343bb0 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -34,7 +34,8 @@ class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, bpy.types.Panel): # Displays percentage of the cutter which is engaged with the material # Displays a warning for engagements greater than 50% def draw_cutter_engagement(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.cutter_type in ['BALLCONE']: engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) @@ -47,11 +48,13 @@ def draw_cutter_engagement(self): self.layout.label(text=f"Cutter engagement: {engagement}%") def draw_machine_axis(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'machine_axes') def draw_strategy(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.machine_axes == '4': self.layout.prop(self.op, 'strategy4axis') if self.op.strategy4axis == 'INDEXED': @@ -67,7 +70,8 @@ def draw_strategy(self): self.layout.prop(self.op, 'strategy') def draw_enable_A_B_axis(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'enable_A') if self.op.enable_A: self.layout.prop(self.op, 'rotation_A') @@ -81,26 +85,30 @@ def draw_enable_A_B_axis(self): if self.op.enable_B: self.layout.prop(self.op, 'rotation_B') - def draw_cutout_type(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'cut_type') def draw_overshoot(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'straight') def draw_startpoint(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'profile_start') def draw_lead_in_out(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'lead_in') self.layout.prop(self.op, 'lead_out') def draw_outlines(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'outlines_count') if self.op.outlines_count > 1: self.layout.prop(self.op, 'dist_between_paths') @@ -108,11 +116,13 @@ def draw_outlines(self): self.layout.prop(self.op.movement, 'insideout') def draw_merge(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'dont_merge') def draw_cutout_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['CUTOUT']: self.draw_cutout_type() self.draw_overshoot() @@ -124,9 +134,9 @@ def draw_cutout_options(self): self.draw_outlines() self.draw_merge() - def draw_waterline_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['WATERLINE']: self.layout.label(text="OCL doesn't support fill areas") if not self.op.optimisation.use_opencamlib: @@ -138,29 +148,31 @@ def draw_waterline_options(self): self.layout.label(text="Waterline needs a skin margin") def draw_carve_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['CARVE']: self.layout.prop(self.op, 'carve_depth') self.layout.prop(self.op, 'dist_along_paths') def draw_medial_axis_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['MEDIAL_AXIS']: self.layout.prop(self.op, 'medial_axis_threshold') self.layout.prop(self.op, 'medial_axis_subdivision') self.layout.prop(self.op, 'add_pocket_for_medial') self.layout.prop(self.op, 'add_mesh_for_medial') - def draw_drill_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['DRILL']: self.layout.prop(self.op, 'drill_type') self.draw_enable_A_B_axis() - def draw_pocket_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy in ['POCKET']: self.layout.prop(self.op, 'pocket_option') self.layout.prop(self.op, 'pocketToCurve') @@ -168,20 +180,21 @@ def draw_pocket_options(self): self.draw_cutter_engagement() self.draw_enable_A_B_axis() - def draw_default_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy not in ['CUTOUT', 'CURVE', 'WATERLINE', 'CARVE', 'MEDIAL_AXIS', 'DRILL', 'POCKET']: self.layout.prop(self.op, 'dist_between_paths') self.draw_cutter_engagement() self.layout.prop(self.op, 'dist_along_paths') - if self.op.strategy in ['PARALLEL','CROSS']: + if self.op.strategy in ['PARALLEL', 'CROSS']: self.layout.prop(self.op, 'parallel_angle') self.draw_enable_A_B_axis() self.layout.prop(self.op, 'inverse') def draw_bridges_options(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy not in ['POCKET', 'DRILL', 'CURVE', 'MEDIAL_AXIS']: self.layout.prop(self.op, 'use_bridges') if self.op.use_bridges: @@ -192,11 +205,13 @@ def draw_bridges_options(self): self.layout.operator("scene.cam_bridges_add", text="Autogenerate bridges") def draw_skin(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'skin') def draw_array(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.machine_axes == '3': self.layout.prop(self.op, 'array') if self.op.array: @@ -205,7 +220,6 @@ def draw_array(self): self.layout.prop(self.op, 'array_y_count') self.layout.prop(self.op, 'array_y_distance') - def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index 11cf591c7..3ac0a0ede 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -11,6 +11,7 @@ # # For each operation, generate the corresponding gcode and export the gcode file + class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel): """CAM operations panel""" bl_label = "CAM operations" @@ -33,7 +34,8 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel): # create, delete, duplicate, reorder def draw_operations_list(self): row = self.layout.row() - row.template_list("CAM_UL_operations", '', bpy.context.scene, "cam_operations", bpy.context.scene, 'cam_active_operation') + row.template_list("CAM_UL_operations", '', bpy.context.scene, + "cam_operations", bpy.context.scene, 'cam_active_operation') col = row.column(align=True) col.operator("scene.cam_operation_add", icon='ADD', text="") col.operator("scene.cam_operation_copy", icon='COPYDOWN', text="") @@ -42,18 +44,19 @@ def draw_operations_list(self): col.operator("scene.cam_operation_move", icon='TRIA_UP', text="").direction = 'UP' col.operator("scene.cam_operation_move", icon='TRIA_DOWN', text="").direction = 'DOWN' - # Draw the list of preset operations, and preset add and remove buttons + def draw_presets(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return row = self.layout.row(align=True) row.menu("CAM_OPERATION_MT_presets", text=bpy.types.CAM_OPERATION_MT_presets.bl_label) row.operator("render.cam_preset_operation_add", text="", icon='ADD') row.operator("render.cam_preset_operation_add", text="", icon='REMOVE').remove_active = True - def draw_calculate_path(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.maxz > self.op.movement.free_height: self.layout.label(text='!ERROR! COLLISION!') self.layout.label(text='Depth start > Free movement height') @@ -62,11 +65,12 @@ def draw_calculate_path(self): if not self.op.valid: self.layout.label(text="Select a valid object to calculate the path.") - # will be disable if not valid + # will be disable if not valid self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") def draw_export_gcode(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.valid: if self.op.name is not None: name = f"cam_path_{self.op.name}" @@ -74,24 +78,28 @@ def draw_export_gcode(self): self.layout.operator("object.cam_export", text="Export Gcode ") def draw_simulate_op(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.valid: self.layout.operator("object.cam_simulate", text="Simulate this operation") def draw_op_name(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'name') def draw_op_filename(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'filename') - # Draw a list of objects which will be used as the source of the operation # FIXME Right now, cameras or lights may be used, which crashes # The user should only be able to choose meshes and curves + def draw_operation_source(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'geometry_source') if self.op.strategy == 'CURVE': @@ -117,15 +125,14 @@ def draw_operation_source(self): if self.op.strategy == 'PROJECTED_CURVE': self.layout.prop_search(self.op, "curve_object1", bpy.data, "objects") - - def draw(self, context): self.context = context self.draw_presets() self.draw_operations_list() - if self.op is None: return + if self.op is None: + return self.draw_calculate_path() self.draw_export_gcode() @@ -133,5 +140,3 @@ def draw(self, context): self.draw_op_name() self.draw_op_filename() self.draw_operation_source() - - diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index db0e7700c..3c5d8cccd 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -53,16 +53,17 @@ class CAM_OPTIMISATION_Panel(CAMButtonsPanel, bpy.types.Panel): bl_idname = "WORLD_PT_CAM_OPTIMISATION" panel_interface_level = 2 - def draw_optimize(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.optimisation, 'optimize') if self.op.optimisation.optimize: self.layout.prop(self.op.optimisation, 'optimize_threshold') def draw_exact_mode(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if not self.op.geometry_source == 'OBJECT' or self.op.geometry_source == 'COLLECTION': return @@ -87,7 +88,8 @@ def draw_exact_mode(self): self.layout.label(text=resolution) def draw_use_opencamlib(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if not (self.exact_possible and self.op.optimisation.use_exact): return @@ -101,14 +103,15 @@ def draw_use_opencamlib(self): self.layout.prop(self.op.optimisation, 'use_opencamlib') def draw_simulation_detail(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op.optimisation, 'simulation_detail') self.layout.prop(self.op.optimisation, 'circle_detail') - def draw_simplify_gcode(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.strategy not in ['DRILL']: self.layout.prop(self.op, 'remove_redundant_points') @@ -117,16 +120,19 @@ def draw_simplify_gcode(self): self.layout.prop(self.op, 'simplify_tol') def draw_use_modifiers(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: self.layout.prop(self.op, 'use_modifiers') def draw_hide_all_others(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'hide_all_others') def draw_parent_path_to_object(self): - if not self.has_correct_level(): return + if not self.has_correct_level(): + return self.layout.prop(self.op, 'parent_path_to_object') def draw(self, context): diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index 602da9c65..b8fab4c78 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -21,4 +21,3 @@ def draw(self, context): layout.prop(settings, 'slice_above0') layout.prop(settings, 'slice_3d') layout.prop(settings, 'indexes') - diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 33284d65b..3314de7ce 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -61,31 +61,31 @@ def update_material(self, context): addMaterialAreaObject() + def update_operation(self, context): from . import updateRest active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] updateRest(active_op, bpy.context) + def update_exact_mode(self, context): from . import updateExact active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] updateExact(active_op, bpy.context) + def update_opencamlib(self, context): from . import updateOpencamlib active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] updateOpencamlib(active_op, bpy.context) + def update_zbuffer_image(self, context): from . import updateZbufferImage active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] updateZbufferImage(active_op, bpy.context) - - - - # Import OpencamLib # Return available OpenCamLib version on success, None otherwise def opencamlib_version(): @@ -98,6 +98,7 @@ def opencamlib_version(): return return(ocl.version()) + def positionObject(operation): ob = bpy.data.objects[operation.object_name] bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') @@ -179,7 +180,7 @@ def getBoundsWorldspace(obs, use_modifiers=False): bpy.ops.object.delete() bpy.ops.outliner.orphans_purge() else: - if not hasattr(ob.data,"splines"): + if not hasattr(ob.data, "splines"): raise CamException("Can't do CAM operation on the selected object type") # for coord in bb: for c in ob.data.splines: @@ -339,7 +340,7 @@ def getBounds(o): s = bpy.context.scene m = s.cam_machine # make sure this message only shows once and goes away once fixed - o.info.warnings.replace('Operation exceeds your machine limits\n','') + o.info.warnings.replace('Operation exceeds your machine limits\n', '') if o.max.x - o.min.x > m.working_area.x or o.max.y - o.min.y > m.working_area.y \ or o.max.z - o.min.z > m.working_area.z: o.info.warnings += 'Operation exceeds your machine limits\n' @@ -456,7 +457,6 @@ async def sampleChunks(o, pathSamples, layers): print(f"Total sample points {totlen}") - n = 0 last_percent = -1 # timing for optimisation @@ -475,9 +475,9 @@ async def sampleChunks(o, pathSamples, layers): # threads_count=4 # for t in range(0,threads): - our_points=patternchunk.get_points_np() - ambient_contains=shapely.contains(o.ambient,shapely.points(our_points[:,0:2])) - for s,in_ambient in zip(our_points,ambient_contains): + our_points = patternchunk.get_points_np() + ambient_contains = shapely.contains(o.ambient, shapely.points(our_points[:, 0:2])) + for s, in_ambient in zip(our_points, ambient_contains): if o.strategy != 'WATERLINE' and int(100 * n / totlen) != last_percent: last_percent = int(100 * n / totlen) await progress_async('sampling paths ', last_percent) @@ -500,7 +500,8 @@ async def sampleChunks(o, pathSamples, layers): z = getSampleBullet(cutter, x, y, cutterdepth, 1, lastsample[2] - o.dist_along_paths) # first try to the last sample if z < minz - 1: - z = getSampleBullet(cutter, x, y, cutterdepth, lastsample[2] - o.dist_along_paths, minz) + z = getSampleBullet(cutter, x, y, cutterdepth, + lastsample[2] - o.dist_along_paths, minz) else: z = getSampleBullet(cutter, x, y, cutterdepth, 1, minz) @@ -534,7 +535,8 @@ async def sampleChunks(o, pathSamples, layers): lastlayer = i2 currentlayer = i - if lastlayer is not None and lastlayer != currentlayer: # and lastsample[2]!=newsample[2]: + # and lastsample[2]!=newsample[2]: + if lastlayer is not None and lastlayer != currentlayer: # #sampling for sorted paths in layers- to go to the border of the sampled layer at least... # there was a bug here, but should be fixed. if currentlayer < lastlayer: @@ -566,7 +568,8 @@ async def sampleChunks(o, pathSamples, layers): if growing: if li > 0: - layeractivechunks[ls].points.insert(-1, betweensample.to_tuple()) + layeractivechunks[ls].points.insert(-1, + betweensample.to_tuple()) else: layeractivechunks[ls].points.append(betweensample.to_tuple()) layeractivechunks[ls + 1].points.append(betweensample.to_tuple()) @@ -587,17 +590,16 @@ async def sampleChunks(o, pathSamples, layers): if terminatechunk: if len(ch.points) > 0: - as_chunk=ch.to_chunk() + as_chunk = ch.to_chunk() layerchunks[i].append(as_chunk) thisrunchunks[i].append(as_chunk) layeractivechunks[i] = camPathChunkBuilder([]) lastsample = newsample - for i, l in enumerate(layers): ch = layeractivechunks[i] if len(ch.points) > 0: - as_chunk=ch.to_chunk() + as_chunk = ch.to_chunk() layerchunks[i].append(as_chunk) thisrunchunks[i].append(as_chunk) layeractivechunks[i] = camPathChunkBuilder([]) @@ -627,7 +629,8 @@ async def sampleChunks(o, pathSamples, layers): if not ch1.parents: children.append(ch1) - parentChild(parents, children, o) # parent only last and first chunk, before it did this for all. + # parent only last and first chunk, before it did this for all. + parentChild(parents, children, o) timingadd(sortingtime) chunks = [] @@ -675,7 +678,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): lastrunchunks.append([]) n = 0 - last_percent=-1 + last_percent = -1 lastz = minz for patternchunk in pathSamples: # print (patternchunk.endpoints) @@ -694,10 +697,10 @@ async def sampleChunksNAxis(o, pathSamples, layers): # #TODO: seems we are writing into the source chunk , # and that is why we need to write endpoints everywhere too? - percent=int(100 * n / totlen) - if percent!=last_percent: + percent = int(100 * n / totlen) + if percent != last_percent: await progress_async('sampling paths', percent) - last_percent=percent + last_percent = percent n += 1 sampled = False # print(si) @@ -777,14 +780,16 @@ async def sampleChunksNAxis(o, pathSamples, layers): betweenrotation = tuple_add(lastrotation, tuple_mul(tuple_sub(rotation, lastrotation), ratio)) # startpoint = retract point, it has to be always available... - betweenstartpoint = laststartpoint + (startp - laststartpoint) * ratio + betweenstartpoint = laststartpoint + \ + (startp - laststartpoint) * ratio # here, we need to have also possible endpoints always.. betweenendpoint = lastendpoint + (endp - lastendpoint) * ratio if growing: if li > 0: layeractivechunks[ls].points.insert(-1, betweensample) layeractivechunks[ls].rotations.insert(-1, betweenrotation) - layeractivechunks[ls].startpoints.insert(-1, betweenstartpoint) + layeractivechunks[ls].startpoints.insert( + -1, betweenstartpoint) layeractivechunks[ls].endpoints.insert(-1, betweenendpoint) else: layeractivechunks[ls].points.append(betweensample) @@ -836,11 +841,11 @@ async def sampleChunksNAxis(o, pathSamples, layers): laststartpoint = startp lastendpoint = endp - # convert everything to actual chunks - # rather than chunkBuilders + # convert everything to actual chunks + # rather than chunkBuilders for i, l in enumerate(layers): - layeractivechunks[i]=layeractivechunks[i].to_chunk() if layeractivechunks[i] is not None else None - + layeractivechunks[i] = layeractivechunks[i].to_chunk( + ) if layeractivechunks[i] is not None else None for i, l in enumerate(layers): ch = layeractivechunks[i] @@ -964,7 +969,7 @@ def polygonConvexHull(context): bpy.ops.object.duplicate() bpy.ops.object.join() - bpy.context.object.data.dimensions = '3D' # force curve to be a 3D curve + bpy.context.object.data.dimensions = '3D' # force curve to be a 3D curve bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) bpy.context.active_object.name = "_tmp" @@ -1061,7 +1066,7 @@ async def connectChunksLow(chunks, o): pos = lastch.get_point(-1) if o.optimisation.use_opencamlib and o.optimisation.use_exact and o.strategy != 'CUTOUT' and o.strategy != 'POCKET' and o.strategy != 'WATERLINE': - await oclResampleChunks(o, chunks_to_resample,use_cached_mesh=True) + await oclResampleChunks(o, chunks_to_resample, use_cached_mesh=True) return connectedchunks @@ -1085,22 +1090,23 @@ def getClosest(o, pos, chunks): return ch -async def sortChunks(chunks, o,last_pos=None): +async def sortChunks(chunks, o, last_pos=None): if o.strategy != 'WATERLINE': await progress_async('sorting paths') - sys.setrecursionlimit(100000) # the getNext() function of CamPathChunk was running out of recursion limits. + # the getNext() function of CamPathChunk was running out of recursion limits. + sys.setrecursionlimit(100000) sortedchunks = [] chunks_to_resample = [] lastch = None - last_progress_time=time.time() - total= len(chunks) + last_progress_time = time.time() + total = len(chunks) i = len(chunks) pos = (0, 0, 0) if last_pos is None else last_pos - while len(chunks) > 0: - if o.strategy != 'WATERLINE' and time.time()-last_progress_time>0.1: - await progress_async("Sorting paths",100.0*(total-len(chunks))/total) - last_progress_time=time.time() + while len(chunks) > 0: + if o.strategy != 'WATERLINE' and time.time()-last_progress_time > 0.1: + await progress_async("Sorting paths", 100.0*(total-len(chunks))/total) + last_progress_time = time.time() ch = None if len(sortedchunks) == 0 or len( lastch.parents) == 0: # first chunk or when there are no parents -> parents come after children here... @@ -1166,7 +1172,8 @@ def getVectorRight(lastv, verts): # most right vector from a set regarding angl def cleanUpDict(ndict): - print('removing lonely points') # now it should delete all junk first, iterate over lonely verts. + # now it should delete all junk first, iterate over lonely verts. + print('removing lonely points') # found_solitaires=True # while found_solitaires: found_solitaires = False @@ -1246,7 +1253,8 @@ def getOperationSilhouete(operation): # #the small number solves issue with totally flat meshes, which people tend to mill instead of # proper pockets. then the minimum was also maximum, and it didn't detect contour. else: - i = samples > numpy.min(operation.zbuffer_image) # this fixes another numeric imprecision. + # this fixes another numeric imprecision. + i = samples > numpy.min(operation.zbuffer_image) chunks = imageToChunks(operation, i) operation.silhouete = chunksToShapely(chunks) @@ -1348,7 +1356,8 @@ def getAmbient(o): if o.ambient_behaviour == 'AROUND': r = o.ambient_radius - m - o.ambient = getObjectOutline(r, o, True) # in this method we need ambient from silhouete + # in this method we need ambient from silhouete + o.ambient = getObjectOutline(r, o, True) else: o.ambient = spolygon.Polygon(((o.min.x + m, o.min.y + m), (o.min.x + m, o.max.y - m), (o.max.x - m, o.max.y - m), (o.max.x - m, o.min.y + m))) @@ -1360,7 +1369,8 @@ def getAmbient(o): o.limit_poly = shapely.ops.unary_union(polys) if o.ambient_cutter_restrict: - o.limit_poly = o.limit_poly.buffer(o.cutter_diameter / 2, resolution=o.optimisation.circle_detail) + o.limit_poly = o.limit_poly.buffer( + o.cutter_diameter / 2, resolution=o.optimisation.circle_detail) o.ambient = o.ambient.intersection(o.limit_poly) o.update_ambient_tag = False @@ -1394,7 +1404,8 @@ def getObjectOutline(radius, o, Offset): # FIXME: make this one operation indep # print(p1.type, len(polygons)) i += 1 if radius > 0: - p1 = p1.buffer(radius * offset, resolution=o.optimisation.circle_detail, join_style=join, mitre_limit=2) + p1 = p1.buffer(radius * offset, resolution=o.optimisation.circle_detail, + join_style=join, mitre_limit=2) outlines.append(p1) # print(outlines) @@ -1473,7 +1484,8 @@ def addMachineAreaObject(): # need to be in metric units when adding machine mesh object # in order for location to work properly s.unit_settings.system = 'METRIC' - bpy.ops.mesh.primitive_cube_add(align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) + bpy.ops.mesh.primitive_cube_add( + align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) o = bpy.context.active_object o.name = 'CAM_machine' o.data.name = 'CAM_machine' @@ -1487,7 +1499,8 @@ def addMachineAreaObject(): fractal_along_normal=0, seed=0) bpy.ops.mesh.select_nth(nth=2, offset=0) bpy.ops.mesh.delete(type='EDGE') - bpy.ops.mesh.primitive_cube_add(align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) + bpy.ops.mesh.primitive_cube_add( + align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) bpy.ops.object.editmode_toggle() # addTranspMat(o, "violet_transparent", (0.800000, 0.530886, 0.725165), 0.1) @@ -1506,6 +1519,7 @@ def addMachineAreaObject(): # else: # bpy.context.scene.objects.active = None + def addMaterialAreaObject(): s = bpy.context.scene operation = s.cam_operations[s.cam_active_operation] @@ -1516,7 +1530,8 @@ def addMaterialAreaObject(): if s.objects.get('CAM_material') is not None: o = s.objects['CAM_material'] else: - bpy.ops.mesh.primitive_cube_add(align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) + bpy.ops.mesh.primitive_cube_add( + align='WORLD', enter_editmode=False, location=(1, 1, -1), rotation=(0, 0, 0)) o = bpy.context.active_object o.name = 'CAM_material' o.data.name = 'CAM_material' @@ -1569,7 +1584,8 @@ def unique(L): # deleting duplicates as you go nDupli = 0 nZcolinear = 0 - L.sort() # sort() brings the equal elements together; then duplicates are easy to weed out in a single pass. + # sort() brings the equal elements together; then duplicates are easy to weed out in a single pass. + L.sort() last = L[-1] for i in range(len(L) - 2, -1, -1): if last[:2] == L[i][:2]: # XY coordinates compararison @@ -1650,7 +1666,8 @@ def cleanupIndexed(operation): path.rotation_euler = ori.rotation_euler print(ori.matrix_world, operation.orientation_matrix) - for i, ob in enumerate(operation.objects): # TODO: fix this here wrong order can cause objects out of place + # TODO: fix this here wrong order can cause objects out of place + for i, ob in enumerate(operation.objects): ob.parent = operation.parents[i] for i, ob in enumerate(operation.objects): ob.matrix_world = operation.matrices[i] diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index e1e36fa62..714c3d665 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,4) \ No newline at end of file +__version__ = (1, 0, 4) diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index f15a0a9c4..58cc84d41 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -82,8 +82,10 @@ def __init__(self): self.extent = () # tuple (xmin, xmax, ymin, ymax) self.triangulate = False self.vertices = [] # list of vertex 2-tuples: (x,y) - self.lines = [] # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c - self.edges = [] # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) if either vertex index is -1, the edge extends to infinity + # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c + self.lines = [] + # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) if either vertex index is -1, the edge extends to infinity + self.edges = [] self.triangles = [] # 3-tuple of vertex indices self.polygons = {} # a dict of site:[edges] pairs @@ -228,7 +230,8 @@ def orderPts(self, edges): pts.extend([pt for pt in edge]) # try to get start & end point try: - startPt, endPt = [pt for pt in pts if pts.count(pt) < 2] # start and end point aren't duplicate + # start and end point aren't duplicate + startPt, endPt = [pt for pt in pts if pts.count(pt) < 2] except: # all points are duplicate --> polygon is complete --> append some or other edge points complete = True firstIdx = 0 @@ -291,7 +294,8 @@ def outVertex(self, s): def outTriple(self, s1, s2, s3): self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum)) if self.debug: - print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + print("circle through left=%d right=%d bottom=%d" % + (s1.sitenum, s2.sitenum, s3.sitenum)) elif self.triangulate and self.doPrint: print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum)) @@ -299,7 +303,7 @@ def outBisector(self, edge): self.lines.append((edge.a, edge.b, edge.c)) if self.debug: print("line(%d) %gx+%gy=%g, bisecting %d %d" % ( - edge.edgenum, edge.a, edge.b, edge.c, edge.reg[0].sitenum, edge.reg[1].sitenum)) + edge.edgenum, edge.a, edge.b, edge.c, edge.reg[0].sitenum, edge.reg[1].sitenum)) elif self.doPrint: print("l %f %f %f" % (edge.a, edge.b, edge.c)) @@ -648,7 +652,8 @@ def isPointRightOf(self, pt): fast = 1 if not fast: dxs = topsite.x - (e.reg[0]).x - above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b) + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * \ + (1.0 + 2.0 * dxp / dxs + e.b * e.b) if e.b < 0.0: above = not above else: # e.b == 1.0 From 343d0642e17a82b6f4aed055587b27c4a4bcd980 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 08:33:03 -0400 Subject: [PATCH 002/100] Autopep8 format, author & version update Ran Autopep8 on all files in repo, they are otherwise untouched with one exception: In the __init__ file, I had to manually rearrange the import statements to avoid triggering a circular import error. This will be addressed in a future commit. Updated authors in bl_info to: 'Vilem Novak & Contributors' - this can be changed as required Updated version to: 1.0.5 --- scripts/addons/cam/opencamlib/oclSample.py | 42 ++++++++------- scripts/addons/cam/opencamlib/opencamlib.py | 54 ++++++++++--------- .../presets/cam_cutters/BALLCONE_1.00mm.py | 2 +- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index b9036bf2a..2d9a1baf1 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -17,38 +17,41 @@ OCL_SCALE = 1000.0 -_PREVIOUS_OCL_MESH=None +_PREVIOUS_OCL_MESH = None + def get_oclSTL(operation): me = None oclSTL = ocl.STLSurf() - found_mesh=False + found_mesh = False for collision_object in operation.objects: activate(collision_object) - if collision_object.type == "MESH" or collision_object.type== "CURVE" or collision_object.type== "FONT" or collision_object.type== "SURFACE": - found_mesh=True + if collision_object.type == "MESH" or collision_object.type == "CURVE" or collision_object.type == "FONT" or collision_object.type == "SURFACE": + found_mesh = True global_matrix = mathutils.Matrix.Identity(4) - faces = blender_utils.faces_from_mesh(collision_object, global_matrix, operation.use_modifiers) + faces = blender_utils.faces_from_mesh( + collision_object, global_matrix, operation.use_modifiers) for face in faces: t = ocl.Triangle(ocl.Point(face[0][0]*OCL_SCALE, face[0][1]*OCL_SCALE, (face[0][2]+operation.skin)*OCL_SCALE), - ocl.Point(face[1][0]*OCL_SCALE, face[1][1]*OCL_SCALE, (face[1][2]+operation.skin)*OCL_SCALE), - ocl.Point(face[2][0]*OCL_SCALE, face[2][1]*OCL_SCALE, (face[2][2]+operation.skin)*OCL_SCALE)) + ocl.Point(face[1][0]*OCL_SCALE, face[1][1]*OCL_SCALE, + (face[1][2]+operation.skin)*OCL_SCALE), + ocl.Point(face[2][0]*OCL_SCALE, face[2][1]*OCL_SCALE, (face[2][2]+operation.skin)*OCL_SCALE)) oclSTL.addTriangle(t) # FIXME needs to work with collections if not found_mesh: - raise CamException("This operation requires a mesh or curve object or equivalent (e.g. text, volume).") + raise CamException( + "This operation requires a mesh or curve object or equivalent (e.g. text, volume).") return oclSTL -async def ocl_sample(operation, chunks,use_cached_mesh = False): +async def ocl_sample(operation, chunks, use_cached_mesh=False): global _PREVIOUS_OCL_MESH - op_cutter_type = operation.cutter_type op_cutter_diameter = operation.cutter_diameter op_minz = operation.minz op_cutter_tip_angle = math.radians(operation.cutter_tip_angle)/2 - if op_cutter_type == "VCARVE": + if op_cutter_type == "VCARVE": cutter_length = (op_cutter_diameter/math.tan(op_cutter_tip_angle))/2 else: cutter_length = 10 @@ -60,24 +63,27 @@ async def ocl_sample(operation, chunks,use_cached_mesh = False): elif op_cutter_type == 'BALLNOSE': cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': - cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) - elif op_cutter_type =='CYLCONE': - cutter = ocl.CylConeCutter((operation.cylcone_diameter/2+operation.skin)*2000,(op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle) + cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) + * 1000, op_cutter_tip_angle, cutter_length) + elif op_cutter_type == 'CYLCONE': + cutter = ocl.CylConeCutter((operation.cylcone_diameter/2+operation.skin)*2000, + (op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle) elif op_cutter_type == 'BALLCONE': cutter = ocl.BallConeCutter((operation.ball_radius + operation.skin) * 2000, (op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle) - elif op_cutter_type =='BULLNOSE': - cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) * 1000,operation.bull_corner_radius*1000, cutter_length) + elif op_cutter_type == 'BULLNOSE': + cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) * + 1000, operation.bull_corner_radius*1000, cutter_length) else: print("Cutter unsupported: {0}\n".format(op_cutter_type)) quit() bdc = ocl.BatchDropCutter() if use_cached_mesh and _PREVIOUS_OCL_MESH is not None: - oclSTL=_PREVIOUS_OCL_MESH + oclSTL = _PREVIOUS_OCL_MESH else: oclSTL = get_oclSTL(operation) - _PREVIOUS_OCL_MESH=oclSTL + _PREVIOUS_OCL_MESH = oclSTL bdc.setSTL(oclSTL) bdc.setCutter(cutter) diff --git a/scripts/addons/cam/opencamlib/opencamlib.py b/scripts/addons/cam/opencamlib/opencamlib.py index 5ecbb3ef4..e7c507a24 100644 --- a/scripts/addons/cam/opencamlib/opencamlib.py +++ b/scripts/addons/cam/opencamlib/opencamlib.py @@ -28,18 +28,20 @@ PYTHON_BIN = None + def pointSamplesFromOCL(points, samples): for index, point in enumerate(points): point[2] = samples[index].z / OCL_SCALE + def chunkPointSamplesFromOCL(chunks, samples): s_index = 0 for ch in chunks: - ch_points=ch.count() - z_vals=np.array([p.z for p in samples[s_index:s_index+ch_points]]) + ch_points = ch.count() + z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]]) z_vals /= OCL_SCALE ch.setZ(z_vals) - s_index+=ch_points + s_index += ch_points # p_index = 0 # for point in ch.points: # if len(point) == 2 or point[2] != 2: @@ -51,14 +53,15 @@ def chunkPointSamplesFromOCL(chunks, samples): # p_index += 1 # s_index += 1 + def chunkPointsResampleFromOCL(chunks, samples): s_index = 0 for ch in chunks: - ch_points=ch.count() - z_vals=np.array([p.z for p in samples[s_index:s_index+ch_points]]) + ch_points = ch.count() + z_vals = np.array([p.z for p in samples[s_index:s_index+ch_points]]) z_vals /= OCL_SCALE ch.setZ(z_vals) - s_index+=ch_points + s_index += ch_points # s_index = 0 # for ch in chunks: @@ -105,21 +108,21 @@ async def oclSample(operation, chunks): chunkPointSamplesFromOCL(chunks, samples) -async def oclResampleChunks(operation, chunks_to_resample,use_cached_mesh): +async def oclResampleChunks(operation, chunks_to_resample, use_cached_mesh): tmp_chunks = list() tmp_chunks.append(camPathChunk(inpoints=[])) for chunk, i_start, i_length in chunks_to_resample: tmp_chunks[0].extend(chunk.get_points_np()[i_start:i_start+i_length]) - print(i_start,i_length,len(tmp_chunks[0].points)) - - samples = await ocl_sample(operation, tmp_chunks,use_cached_mesh=use_cached_mesh) + print(i_start, i_length, len(tmp_chunks[0].points)) + + samples = await ocl_sample(operation, tmp_chunks, use_cached_mesh=use_cached_mesh) sample_index = 0 for chunk, i_start, i_length in chunks_to_resample: z = np.array([p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE pts = chunk.get_points_np() - pt_z = pts[i_start:i_start+i_length,2] - pt_z = np.where(z>pt_z,z,pt_z) + pt_z = pts[i_start:i_start+i_length, 2] + pt_z = np.where(z > pt_z, z, pt_z) sample_index += i_length # for p_index in range(i_start, i_start + i_length): @@ -141,6 +144,7 @@ def oclWaterlineLayerHeights(operation): layers.append(l_last) return layers + def oclGetMedialAxis(operation, chunks): oclWaterlineHeightsToOCL(operation) operationSettingsToOCL(operation) @@ -160,42 +164,42 @@ async def oclGetWaterline(operation, chunks): op_cutter_tip_angle = operation['cutter_tip_angle'] cutter = None - cutter_length = 150 #TODO: automatically determine necessary cutter length depending on object size + cutter_length = 150 # TODO: automatically determine necessary cutter length depending on object size if op_cutter_type == 'END': cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'BALLNOSE': cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': - cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) + cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) + * 1000, op_cutter_tip_angle, cutter_length) else: print("Cutter unsupported: {0}\n".format(op_cutter_type)) quit() - waterline = ocl.Waterline() waterline.setSTL(oclSTL) waterline.setCutter(cutter) - waterline.setSampling(0.1)#TODO: add sampling setting to UI - last_pos=[0,0,0] - for count,height in enumerate(layers): - layer_chunks=[] - await progress_async("Waterline",int((100*count)/len(layers))) + waterline.setSampling(0.1) # TODO: add sampling setting to UI + last_pos = [0, 0, 0] + for count, height in enumerate(layers): + layer_chunks = [] + await progress_async("Waterline", int((100*count)/len(layers))) waterline.reset() waterline.setZ(height * OCL_SCALE) waterline.run2() wl_loops = waterline.getLoops() for l in wl_loops: - inpoints=[] + inpoints = [] for p in l: inpoints.append((p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE)) inpoints.append(inpoints[0]) - chunk=camPathChunk(inpoints=inpoints) + chunk = camPathChunk(inpoints=inpoints) chunk.closed = True layer_chunks.append(chunk) # sort chunks so that ordering is stable - chunks.extend(await utils.sortChunks(layer_chunks,operation,last_pos=last_pos)) - if len(chunks)>0: - last_pos=chunks[-1].get_point(-1) + chunks.extend(await utils.sortChunks(layer_chunks, operation, last_pos=last_pos)) + if len(chunks) > 0: + last_pos = chunks[-1].get_point(-1) # def oclFillMedialAxis(operation): diff --git a/scripts/addons/cam/presets/cam_cutters/BALLCONE_1.00mm.py b/scripts/addons/cam/presets/cam_cutters/BALLCONE_1.00mm.py index 8c244cfac..acdd2c874 100644 --- a/scripts/addons/cam/presets/cam_cutters/BALLCONE_1.00mm.py +++ b/scripts/addons/cam/presets/cam_cutters/BALLCONE_1.00mm.py @@ -6,4 +6,4 @@ d.ball_cone_flute = 0.03 d.cutter_diameter = 0.006 d.cutter_length = 25.0 -d.cutter_tip_angle = 60.0 \ No newline at end of file +d.cutter_tip_angle = 60.0 From 9e9cd3438fda07dd92038f217d91894b8d91c018 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 08:36:34 -0400 Subject: [PATCH 003/100] Autopep8 format, author & version update Ran Autopep8 on all files in repo, they are otherwise untouched with one exception: In the __init__ file, I had to manually rearrange the import statements to avoid triggering a circular import error. This will be addressed in a future commit. Updated authors in bl_info to: 'Vilem Novak & Contributors' - this can be changed as required Updated version to: 1.0.5 --- scripts/addons/cam/nc/anilam_crusader_m.py | 43 +- .../addons/cam/nc/anilam_crusader_m_read.py | 1 + scripts/addons/cam/nc/attach.py | 36 +- scripts/addons/cam/nc/cad_iso_read.py | 95 +- scripts/addons/cam/nc/cad_nc_read.py | 53 +- scripts/addons/cam/nc/cad_read.py | 142 +- scripts/addons/cam/nc/centroid1.py | 41 +- scripts/addons/cam/nc/centroid1_read.py | 1 + scripts/addons/cam/nc/drag_knife.py | 24 +- scripts/addons/cam/nc/emc2.py | 337 +- scripts/addons/cam/nc/emc2_read.py | 1 + scripts/addons/cam/nc/emc2b.py | 17 +- scripts/addons/cam/nc/emc2b_crc.py | 3 + scripts/addons/cam/nc/emc2b_crc_read.py | 2 + scripts/addons/cam/nc/emc2b_read.py | 2 + scripts/addons/cam/nc/emc2tap.py | 40 +- scripts/addons/cam/nc/emc2tap_read.py | 2 + scripts/addons/cam/nc/fadal.py | 100 +- scripts/addons/cam/nc/format.py | 30 +- scripts/addons/cam/nc/gantry_router.py | 18 +- scripts/addons/cam/nc/gantry_router_read.py | 2 + scripts/addons/cam/nc/gravos.py | 98 +- scripts/addons/cam/nc/grbl.py | 72 +- scripts/addons/cam/nc/heiden.py | 2347 +++++++------- scripts/addons/cam/nc/heiden530.py | 30 +- scripts/addons/cam/nc/heiden_read.py | 24 +- scripts/addons/cam/nc/hm50.py | 21 +- scripts/addons/cam/nc/hm50_read.py | 2 + scripts/addons/cam/nc/hpgl2d.py | 19 +- scripts/addons/cam/nc/hpgl2d_read.py | 29 +- scripts/addons/cam/nc/hpgl2dv.py | 6 +- scripts/addons/cam/nc/hpgl2dv_read.py | 1 + scripts/addons/cam/nc/hpgl3d.py | 10 +- scripts/addons/cam/nc/hpgl3d_read.py | 11 +- scripts/addons/cam/nc/hxml_writer.py | 88 +- scripts/addons/cam/nc/iso.py | 2714 +++++++++-------- scripts/addons/cam/nc/iso_codes.py | 3 +- scripts/addons/cam/nc/iso_crc.py | 3 + scripts/addons/cam/nc/iso_crc_read.py | 2 + scripts/addons/cam/nc/iso_modal.py | 3 + scripts/addons/cam/nc/iso_modal_read.py | 2 + scripts/addons/cam/nc/iso_read.py | 61 +- scripts/addons/cam/nc/lathe1.py | 149 +- scripts/addons/cam/nc/lathe1_read.py | 80 +- scripts/addons/cam/nc/lynx_otter_o.py | 63 +- scripts/addons/cam/nc/mach3.py | 22 +- scripts/addons/cam/nc/mach3_read.py | 2 + scripts/addons/cam/nc/makerbotHBP.py | 107 +- scripts/addons/cam/nc/makerbotHBP_read.py | 2 + scripts/addons/cam/nc/makerbot_codes.py | 229 +- scripts/addons/cam/nc/nc.py | 973 +++--- scripts/addons/cam/nc/nc_read.py | 62 +- scripts/addons/cam/nc/nclathe_read.py | 136 +- scripts/addons/cam/nc/num_reader.py | 4 +- scripts/addons/cam/nc/printbot3d.py | 7 +- scripts/addons/cam/nc/printbot3d_read.py | 1 + scripts/addons/cam/nc/recreator.py | 80 +- scripts/addons/cam/nc/rez2.py | 555 ++-- scripts/addons/cam/nc/rez2_read.py | 366 +-- scripts/addons/cam/nc/series1.py | 5 +- scripts/addons/cam/nc/series1_read.py | 1 + scripts/addons/cam/nc/shopbot_mtc.py | 395 ++- scripts/addons/cam/nc/siegkx1.py | 2 + scripts/addons/cam/nc/siegkx1_read.py | 2 + scripts/addons/cam/nc/tnc151.py | 9 +- scripts/addons/cam/nc/tnc151_read.py | 2 + scripts/addons/cam/nc/winpc.py | 42 +- 67 files changed, 5218 insertions(+), 4614 deletions(-) diff --git a/scripts/addons/cam/nc/anilam_crusader_m.py b/scripts/addons/cam/nc/anilam_crusader_m.py index 89cee5532..994d64ed0 100644 --- a/scripts/addons/cam/nc/anilam_crusader_m.py +++ b/scripts/addons/cam/nc/anilam_crusader_m.py @@ -5,6 +5,7 @@ from . import nc from . import iso + class Creator(iso.Creator): def __init__(self): iso.Creator.__init__(self) @@ -16,10 +17,10 @@ def SPACE_STR(self): return(' ') # This version of COMMENT removes comments from the resultant GCode # Note: The Anilam hates comments when importing code. - def COMMENT(self,comment): return('') + def COMMENT(self, comment): return('') def program_begin(self, id, comment): - self.write('%\n'); # Start of file token that Anilam Crusader M likes + self.write('%\n') # Start of file token that Anilam Crusader M likes # No Comments for the Anilam crusaher M, please...... #self.write( ('(' + comment + ')' + '\n') ) @@ -28,41 +29,41 @@ def program_end(self): self.write('%\n') # EOF signal for Anilam Crusader M ############################################################################ - ## Settings + # Settings def imperial(self): - self.write( self.IMPERIAL() + '\n') + self.write(self.IMPERIAL() + '\n') self.fmt.number_of_decimal_places = 4 def metric(self): - self.write( self.METRIC() + '\n' ) + self.write(self.METRIC() + '\n') self.fmt.number_of_decimal_places = 3 def absolute(self): - self.write( self.ABSOLUTE() + '\n') + self.write(self.ABSOLUTE() + '\n') def incremental(self): - self.write( self.INCREMENTAL() + '\n' ) + self.write(self.INCREMENTAL() + '\n') def polar(self, on=True): - if (on) : - self.write(self.POLAR_ON() + '\n' ) - else : - self.write(self.POLAR_OFF() + '\n' ) + if (on): + self.write(self.POLAR_ON() + '\n') + else: + self.write(self.POLAR_OFF() + '\n') def set_plane(self, plane): - if (plane == 0) : + if (plane == 0): self.write('G17\n') - elif (plane == 1) : + elif (plane == 1): self.write('G18\n') - elif (plane == 2) : + elif (plane == 2): self.write('G19\n') def comment(self, text): - pass + pass ############################################################################ - ## Tools + # Tools def tool_change(self, id): self.write(('T%i' % id) + '\n') @@ -83,11 +84,13 @@ def tool_defn(self, id, name='', params=None): # These are selected by values from 1 to 9 inclusive. def workplane(self, id): if ((id >= 1) and (id <= 6)): - self.write( (self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + '\n') + self.write((self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + '\n') if ((id >= 7) and (id <= 9)): - self.write( ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6))) + '\n') + self.write(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6))) + '\n') + + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + self.write( + '(Canned drill cycle ops are not yet supported here on this Anilam Crusader M postprocessor)') - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance = None): - self.write('(Canned drill cycle ops are not yet supported here on this Anilam Crusader M postprocessor)') nc.creator = Creator() diff --git a/scripts/addons/cam/nc/anilam_crusader_m_read.py b/scripts/addons/cam/nc/anilam_crusader_m_read.py index 8e03a13c8..d8a688a5b 100644 --- a/scripts/addons/cam/nc/anilam_crusader_m_read.py +++ b/scripts/addons/cam/nc/anilam_crusader_m_read.py @@ -7,6 +7,7 @@ # Override some iso parser methods to interpret arc centers as relative to origin, not relative to start of arc. + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/attach.py b/scripts/addons/cam/nc/attach.py index 27c2d0c86..2da4f263a 100644 --- a/scripts/addons/cam/nc/attach.py +++ b/scripts/addons/cam/nc/attach.py @@ -16,6 +16,8 @@ units = 1.0 ################################################################################ + + class Creator(recreator.Redirector): def __init__(self, original): @@ -44,10 +46,11 @@ def z2(self, z): # use a line with no length path.append(ocl.Line(ocl.Point(self.x, self.y, self.z), ocl.Point(self.x, self.y, self.z))) self.setPdcfIfNotSet() - if (self.z>self.minz): - self.pdcf.setZ(self.z) # Adjust Z if we have gotten a higher limit (Fix pocketing loosing steps when using attach?) + if (self.z > self.minz): + # Adjust Z if we have gotten a higher limit (Fix pocketing loosing steps when using attach?) + self.pdcf.setZ(self.z) else: - self.pdcf.setZ(self.minz/units) # Else use minz + self.pdcf.setZ(self.minz/units) # Else use minz self.pdcf.setPath(path) self.pdcf.run() plist = self.pdcf.getCLPoints() @@ -55,13 +58,15 @@ def z2(self, z): return p.z + self.material_allowance/units def cut_path(self): - if self.path == None: return + if self.path == None: + return self.setPdcfIfNotSet() - if (self.z>self.minz): - self.pdcf.setZ(self.z) # Adjust Z if we have gotten a higher limit (Fix pocketing loosing steps when using attach?) + if (self.z > self.minz): + # Adjust Z if we have gotten a higher limit (Fix pocketing loosing steps when using attach?) + self.pdcf.setZ(self.z) else: - self.pdcf.setZ(self.minz/units) # Else use minz + self.pdcf.setZ(self.minz/units) # Else use minz # get the points on the surface self.pdcf.setPath(self.path) @@ -69,7 +74,7 @@ def cut_path(self): self.pdcf.run() plist = self.pdcf.getCLPoints() - #refine the points + # refine the points f = ocl.LineCLFilter() f.setTolerance(0.005) for p in plist: @@ -85,7 +90,7 @@ def cut_path(self): self.path = ocl.Path() - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if z != None: if z < self.z: return @@ -102,24 +107,28 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): return # add a line to the path - if self.path == None: self.path = ocl.Path() + if self.path == None: + self.path = ocl.Path() self.path.append(ocl.Line(ocl.Point(px, py, pz), ocl.Point(self.x, self.y, self.z))) - def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw = True): + def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw=True): px = self.x py = self.y pz = self.z recreator.Redirector.arc(self, x, y, z, i, j, k, r, ccw) # add an arc to the path - if self.path == None: self.path = ocl.Path() - self.path.append(ocl.Arc(ocl.Point(px, py, pz), ocl.Point(self.x, self.y, self.z), ocl.Point(i, j, pz), ccw)) + if self.path == None: + self.path = ocl.Path() + self.path.append(ocl.Arc(ocl.Point(px, py, pz), ocl.Point( + self.x, self.y, self.z), ocl.Point(i, j, pz), ccw)) def set_ocl_cutter(self, cutter): self.cutter = cutter ################################################################################ + def attach_begin(): global attached if attached == True: @@ -130,6 +139,7 @@ def attach_begin(): nc.creator.pdcf = None nc.creator.path = None + def attach_end(): global attached nc.creator.cut_path() diff --git a/scripts/addons/cam/nc/cad_iso_read.py b/scripts/addons/cam/nc/cad_iso_read.py index 2ca104fec..b3a2511f6 100644 --- a/scripts/addons/cam/nc/cad_iso_read.py +++ b/scripts/addons/cam/nc/cad_iso_read.py @@ -16,17 +16,18 @@ import sys ################################################################################ -class Parser(nc.Parser): +class Parser(nc.Parser): def __init__(self, writer): nc.Parser.__init__(self, writer) - self.pattern_main = re.compile('([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') + self.pattern_main = re.compile( + '([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') - #if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point - # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal + # if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point + # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal # add your character here > [(!;] for comments char # then look for the 'comment' function towards the end of the file and add another elif @@ -81,7 +82,7 @@ def ParseWord(self, word): self.path_col = "feed" self.col = "feed" elif (word == 'G82' or word == 'g82'): - self.drill = True; + self.drill = True self.no_move = True self.path_col = "feed" self.col = "feed" @@ -94,7 +95,8 @@ def ParseWord(self, word): self.absolute() elif (word == 'G91' or word == 'g91'): self.incremental() - elif (word[0] == 'G') : col = "prep" + elif (word[0] == 'G'): + col = "prep" elif (word[0] == 'I' or word[0] == 'i'): self.col = "axis" self.i = eval(word[1:]) @@ -107,19 +109,22 @@ def ParseWord(self, word): self.col = "axis" self.k = eval(word[1:]) self.move = True - elif (word[0] == 'M') : self.col = "misc" - elif (word[0] == 'N') : self.col = "blocknum" - elif (word[0] == 'O') : self.col = "program" + elif (word[0] == 'M'): + self.col = "misc" + elif (word[0] == 'N'): + self.col = "blocknum" + elif (word[0] == 'O'): + self.col = "program" elif (word[0] == 'P' or word[0] == 'p'): - if (self.no_move != True): - self.col = "axis" - self.p = eval(word[1:]) - self.move = True + if (self.no_move != True): + self.col = "axis" + self.p = eval(word[1:]) + self.move = True elif (word[0] == 'Q' or word[0] == 'q'): - if (self.no_move != True): - self.col = "axis" - self.q = eval(word[1:]) - self.move = True + if (self.no_move != True): + self.col = "axis" + self.q = eval(word[1:]) + self.move = True elif (word[0] == 'R' or word[0] == 'r'): self.col = "axis" self.r = eval(word[1:]) @@ -128,9 +133,9 @@ def ParseWord(self, word): self.col = "axis" self.s = eval(word[1:]) self.move = True - elif (word[0] == 'T') : + elif (word[0] == 'T'): self.col = "tool" - self.set_tool( eval(word[1:]) ) + self.set_tool(eval(word[1:])) elif (word[0] == 'X' or word[0] == 'x'): self.col = "axis" self.x = eval(word[1:]) @@ -143,21 +148,27 @@ def ParseWord(self, word): self.col = "axis" self.z = eval(word[1:]) self.move = True - elif (word[0] == '(') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == '!') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == ';') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == '#') : self.col = "variable" - elif (word[0] == ':') : self.col = "blocknum" - elif (ord(word[0]) <= 32) : self.cdata = True + elif (word[0] == '('): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == '!'): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == ';'): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == '#'): + self.col = "variable" + elif (word[0] == ':'): + self.col = "blocknum" + elif (ord(word[0]) <= 32): + self.cdata = True def Parse(self, name, oname=None): - self.files_open(name,oname) + self.files_open(name, oname) - #self.begin_ncblock() - #self.begin_path(None) - #self.add_line(z=500) - #self.end_path() - #self.end_ncblock() + # self.begin_ncblock() + # self.begin_path(None) + # self.add_line(z=500) + # self.end_path() + # self.end_ncblock() self.path_col = None self.f = None @@ -179,7 +190,7 @@ def Parse(self, name, oname=None): self.y = None self.z = None - #self.begin_ncblock() + # self.begin_ncblock() self.move = False self.drill = False @@ -207,12 +218,15 @@ def Parse(self, name, oname=None): else: if (self.move and not self.no_move): self.begin_path(self.path_col) - if (self.arc==-1): - self.add_arc(self.x, self.y, self.z, self.i, self.j, self.k, self.r, self.arc) - elif (self.arc==1): - #self.add_arc(x, y, z, i, j, k, -r, arc) #if you want to use arcs with R values uncomment the first part of this line and comment the next one - self.add_arc(self.x, self.y, self.z, self.i, self.j, self.k, self.r, self.arc) - else : self.add_line(self.x, self.y, self.z, self.a, self.b, self.c) + if (self.arc == -1): + self.add_arc(self.x, self.y, self.z, self.i, + self.j, self.k, self.r, self.arc) + elif (self.arc == 1): + # self.add_arc(x, y, z, i, j, k, -r, arc) #if you want to use arcs with R values uncomment the first part of this line and comment the next one + self.add_arc(self.x, self.y, self.z, self.i, + self.j, self.k, self.r, self.arc) + else: + self.add_line(self.x, self.y, self.z, self.a, self.b, self.c) self.end_path() self.end_ncblock() @@ -221,9 +235,10 @@ def Parse(self, name, oname=None): ################################################################################ + if __name__ == '__main__': parser = ParserIso() - if len(sys.argv)>2: - parser.Parse(sys.argv[1],sys.argv[2]) + if len(sys.argv) > 2: + parser.Parse(sys.argv[1], sys.argv[2]) else: parser.Parse(sys.argv[1]) diff --git a/scripts/addons/cam/nc/cad_nc_read.py b/scripts/addons/cam/nc/cad_nc_read.py index 15ef23ece..4203fcdb3 100644 --- a/scripts/addons/cam/nc/cad_nc_read.py +++ b/scripts/addons/cam/nc/cad_nc_read.py @@ -12,14 +12,14 @@ def __init__(self): self.currentx = -1.0 self.currenty = 0.0 self.currentz = 0.0 - x,y,z = 0.0,0.0,0.0 + x, y, z = 0.0, 0.0, 0.0 self.absolute_flag = True ############################################################################ - ## Internals + # Internals def files_open(self, name, oname=None): - if (oname == None ): + if (oname == None): oname = (name+'.scr') self.file_in = open(name, 'r') self.file_out = open(oname, 'w') @@ -60,52 +60,61 @@ def begin_path(self, col=None): if (col != None): if col == 'rapid': self.file_out.write('-color Red\n') - #self.file_out.write('') + # self.file_out.write('') self.file_out.write('-linetype set dashed\n') self.file_out.write('\n') else: self.file_out.write('-color Green\n') - #self.file_out.write('') + # self.file_out.write('') self.file_out.write('-linetype set continuous\n') self.file_out.write('\n') - else : self.file_out.write('\n') + else: + self.file_out.write('\n') def end_path(self): self.file_out.write('\n') def add_line(self, x=None, y=None, z=None, a=None, b=None, c=None): - if (x == None and y == None and z == None and a == None and b == None and c == None) : return + if (x == None and y == None and z == None and a == None and b == None and c == None): + return #self.file_out.write('line %s,%s %s,%s' %(self.currentx,self.currenty,x,y)) - if (x == None) : x = self.currentx - if (y == None) : y = self.currenty - if (z == None) : z = self.currentz - self.file_out.write('line %s,%s,%s %s,%s,%s\n' %(self.currentx,self.currenty,self.currentz,x,y,z)) + if (x == None): + x = self.currentx + if (y == None): + y = self.currenty + if (z == None): + z = self.currentz + self.file_out.write('line %s,%s,%s %s,%s,%s\n' % + (self.currentx, self.currenty, self.currentz, x, y, z)) self.currentx = x self.currenty = y self.currentz = z def add_arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, d=None): - if (x == None and y == None and z == None and i == None and j == None and k == None and r == None and d == None) : return + if (x == None and y == None and z == None and i == None and j == None and k == None and r == None and d == None): + return z = self.currentz - if (x == None) : x = self.currentx - if (y == None) : y = self.currenty - if (z == None) : z = self.currentz + if (x == None): + x = self.currentx + if (y == None): + y = self.currenty + if (z == None): + z = self.currentz if (d == 1): - self.file_out.write('arc %s,%s,%s\n' %(self.currentx,self.currenty,self.currentz)) + self.file_out.write('arc %s,%s,%s\n' % (self.currentx, self.currenty, self.currentz)) self.file_out.write('c\n') - self.file_out.write('%s,%s,%s\n' %(self.currentx+i,self.currenty+j,self.currentz)) - self.file_out.write('%s,%s,%s' %(x,y,z)) + self.file_out.write('%s,%s,%s\n' % (self.currentx+i, self.currenty+j, self.currentz)) + self.file_out.write('%s,%s,%s' % (x, y, z)) else: - self.file_out.write('arc %s,%s,%s\n' %(x,y,z)) + self.file_out.write('arc %s,%s,%s\n' % (x, y, z)) self.file_out.write('c\n') - self.file_out.write('%s,%s,%s\n' %(self.currentx+i,self.currenty+j,self.currentz)) - self.file_out.write('%s,%s,%s' %(self.currentx,self.currenty,self.currentz)) + self.file_out.write('%s,%s,%s\n' % (self.currentx+i, self.currenty+j, self.currentz)) + self.file_out.write('%s,%s,%s' % (self.currentx, self.currenty, self.currentz)) self.currentx = x self.currenty = y self.currentz = z - def incremental(self): self.absolute_flag = False diff --git a/scripts/addons/cam/nc/cad_read.py b/scripts/addons/cam/nc/cad_read.py index 40efded39..faf31bbe8 100644 --- a/scripts/addons/cam/nc/cad_read.py +++ b/scripts/addons/cam/nc/cad_read.py @@ -12,12 +12,11 @@ import sys - # Override some iso parser methods to interpret arc centers as relative to origin, not relative to start of arc. -#def write_layer(name,number): - #FILE.write('-LAYER New %s%s \n' %(name,number)) - #FILE.write('-LAYER Set %s%s \n' %(name,number)) +# def write_layer(name,number): +#FILE.write('-LAYER New %s%s \n' %(name,number)) +#FILE.write('-LAYER Set %s%s \n' %(name,number)) class CAD_backplot(iso.Parser): @@ -26,13 +25,13 @@ def __init__(self): iso.Parser.__init__(self) def Parse(self, name, oname=None): - self.files_open(name,oname) + self.files_open(name, oname) - #self.begin_ncblock() - #self.begin_path(None) - #self.add_line(z=500) - #self.end_path() - #self.end_ncblock() + # self.begin_ncblock() + # self.begin_path(None) + # self.add_line(z=500) + # self.end_path() + # self.end_ncblock() path_col = None f = None @@ -45,7 +44,7 @@ def Parse(self, name, oname=None): oldz = 0.0 movelist = [] while (self.readline()): - # self.readline returns false if the line is empty - the parsing stops if the line is empty. + # self.readline returns false if the line is empty - the parsing stops if the line is empty. a = None b = None c = None @@ -64,7 +63,7 @@ def Parse(self, name, oname=None): jout = None kout = None tool = 0 - #self.begin_ncblock() + # self.begin_ncblock() move = False #arc = 0 @@ -129,7 +128,7 @@ def Parse(self, name, oname=None): path_col = "feed" col = "feed" elif (word == 'G82' or word == 'g82'): - drill = True; + drill = True no_move = True path_col = "feed" col = "feed" @@ -138,7 +137,8 @@ def Parse(self, name, oname=None): no_move = True path_col = "feed" col = "feed" - elif (word[0] == 'G') : col = "prep" + elif (word[0] == 'G'): + col = "prep" elif (word[0] == 'I' or word[0] == 'i'): col = "axis" i = eval(word[1:]) @@ -151,9 +151,12 @@ def Parse(self, name, oname=None): col = "axis" k = eval(word[1:]) move = True - elif (word[0] == 'M') : col = "misc" - elif (word[0] == 'N') : col = "blocknum" - elif (word[0] == 'O') : col = "program" + elif (word[0] == 'M'): + col = "misc" + elif (word[0] == 'N'): + col = "blocknum" + elif (word[0] == 'O'): + col = "program" elif (word[0] == 'P' or word[0] == 'p'): col = "axis" p = eval(word[1:]) @@ -170,10 +173,10 @@ def Parse(self, name, oname=None): col = "axis" s = eval(word[1:]) move = True - elif (word[0] == 'T') : + elif (word[0] == 'T'): col = "tool" - self.set_tool( eval(word[1:]) ) - tool = eval(word[1:]) + self.set_tool(eval(word[1:])) + tool = eval(word[1:]) elif (word[0] == 'X' or word[0] == 'x'): col = "axis" @@ -187,12 +190,18 @@ def Parse(self, name, oname=None): col = "axis" z = eval(word[1:]) move = True - elif (word[0] == '(') : (col, cdata) = ("comment", True) - elif (word[0] == '!') : (col, cdata) = ("comment", True) - elif (word[0] == ';') : (col, cdata) = ("comment", True) - elif (word[0] == '#') : col = "variable" - elif (word[0] == ':') : col = "blocknum" - elif (ord(word[0]) <= 32) : cdata = True + elif (word[0] == '('): + (col, cdata) = ("comment", True) + elif (word[0] == '!'): + (col, cdata) = ("comment", True) + elif (word[0] == ';'): + (col, cdata) = ("comment", True) + elif (word[0] == '#'): + col = "variable" + elif (word[0] == ':'): + col = "blocknum" + elif (ord(word[0]) <= 32): + cdata = True #self.add_text(word, col, cdata) if (drill): @@ -207,55 +216,63 @@ def Parse(self, name, oname=None): self.begin_path("feed") self.add_line(x, y, r) self.end_path() - #elif (tool): - #write_layer('T',tool) - + # elif (tool): + # write_layer('T',tool) else: if (move and not no_move): self.begin_path(path_col) - #use absolute arc centers for IJK params. + # use absolute arc centers for IJK params. # Subtract old XYZ off to get relative centers as expected: - #if path_col == 'rapid': - #FILE.write('-color Red\n') - #else: - #FILE.write('-color Green\n') + # if path_col == 'rapid': + #FILE.write('-color Red\n') + # else: + #FILE.write('-color Green\n') - if (arc) : + if (arc): z = oldz - if (x != None) and (oldx != None) and (i != None): iout = i - if (y != None) and (oldy != None) and (j != None): jout = j - if (z != None) and (oldz != None) and (k != None): kout = k + if (x != None) and (oldx != None) and (i != None): + iout = i + if (y != None) and (oldy != None) and (j != None): + jout = j + if (z != None) and (oldz != None) and (k != None): + kout = k self.add_arc(x, y, z, iout, jout, kout, r, arc) - #if (arc == -1): - ##FILE.write('arc %s,%s,%s\n' %(x,y,z)) - ##FILE.write('c\n') - ##FILE.write('%s,%s,%s\n' %(oldx+i,oldy+j,oldz)) - ##FILE.write('%s,%s,%s\n' %(oldx,oldy,z)) - - #else: - ##FILE.write('arc %s,%s,%s\n' %(oldx,oldy,z)) - ##FILE.write('c\n') - ##FILE.write('%s,%s,%s\n' %(oldx+i,oldy+j,oldz)) - ##FILE.write('%s,%s,%s\n' %(x,y,z)) + # if (arc == -1): + ##FILE.write('arc %s,%s,%s\n' %(x,y,z)) + # FILE.write('c\n') + ##FILE.write('%s,%s,%s\n' %(oldx+i,oldy+j,oldz)) + ##FILE.write('%s,%s,%s\n' %(oldx,oldy,z)) + + # else: + ##FILE.write('arc %s,%s,%s\n' %(oldx,oldy,z)) + # FILE.write('c\n') + ##FILE.write('%s,%s,%s\n' %(oldx+i,oldy+j,oldz)) + ##FILE.write('%s,%s,%s\n' %(x,y,z)) else: self.add_line(x, y, z, a, b, c) - if (x == None) : x = oldx - if (y == None) : y = oldy - if (z == None) : z = oldz - scr_line = ('line %s,%s,%s %s,%s,%s \n' %(oldx,oldy,oldz,x,y,z)) - #print scr_line + if (x == None): + x = oldx + if (y == None): + y = oldy + if (z == None): + z = oldz + scr_line = ('line %s,%s,%s %s,%s,%s \n' % (oldx, oldy, oldz, x, y, z)) + # print scr_line - ##FILE.write(scr_line) + # FILE.write(scr_line) self.end_path() - if (x != None) : oldx = x - if (y != None) : oldy = y - if (z != None) : oldz = z + if (x != None): + oldx = x + if (y != None): + oldy = y + if (z != None): + oldz = z #oldx = x #oldy = y @@ -263,13 +280,14 @@ def Parse(self, name, oname=None): self.end_ncblock() self.files_close() - #FILE.write('\n') - #FILE.close() + # FILE.write('\n') + # FILE.close() + ################################################################################ if __name__ == '__main__': parser = CAD_backplot() - if len(sys.argv)>2: - parser.Parse(sys.argv[1],sys.argv[2]) + if len(sys.argv) > 2: + parser.Parse(sys.argv[1], sys.argv[2]) else: parser.Parse(sys.argv[1]) diff --git a/scripts/addons/cam/nc/centroid1.py b/scripts/addons/cam/nc/centroid1.py index 214d01254..9fb899a22 100644 --- a/scripts/addons/cam/nc/centroid1.py +++ b/scripts/addons/cam/nc/centroid1.py @@ -15,7 +15,6 @@ now = datetime.datetime.now() - ################################################################################ class Creator(iso_modal.Creator): @@ -26,23 +25,25 @@ def __init__(self): self.useCrcCenterline = True self.absolute_flag = True self.prev_g91 = '' - self.safe_z =None + self.safe_z = None + def SPINDLE(self, format, speed): return(self.SPACE() + 'S' + (format % speed)) ################################################################################ -#cutter comp +# cutter comp - #def crc_on(self): + # def crc_on(self): # self.useCrc = True # self.useCrcCenterline = True - #def crc_off(self): + # def crc_off(self): # self.useCrc = False ################################################################################ # general def comment(self, text): - self.write(';' + text +'\n') + self.write(';' + text + '\n') + def write_blocknum(self): pass @@ -69,7 +70,6 @@ def program_begin(self, id, name=''): self.write(';time:'+str(now)+'\n') self.write('G17 G20 G80 G40 G90\n') - def program_end(self): self.write('M05\n') self.write('M25\n') @@ -79,9 +79,9 @@ def program_end(self): def program_stop(self, optional=False): self.write_blocknum() - if (optional) : + if (optional): self.write(self.STOP_OPTIONAL() + '\n') - else : + else: self.write('M05\n') self.write('M25\n') self.write(self.STOP() + '\n') @@ -94,16 +94,18 @@ def workplane(self, id): if ((id >= 1) and (id <= 6)): self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) if ((id >= 7) and (id <= 9)): - self.g_list.append(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) + self.g_list.append( + ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) self.prev_g0123 = '' ################################################################################ # clearance plane - def clearanceplane(self,z=None): + def clearanceplane(self, z=None): self.safe_z = z ################################################################################ # return to home + def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): """Rapid relative to home position""" self.write('M05\n') @@ -121,14 +123,13 @@ def tool_change(self, id): self.t = id self.write('M25\n') if self.safe_z == None: - self.write('G43 H'+ str(id) + ' Z') + self.write('G43 H' + str(id) + ' Z') self.write('1.0') - self.write ('\n') + self.write('\n') else: - self.write('G43 H'+ str(id) + ' Z') + self.write('G43 H' + str(id) + ' Z') self.write(str(self.safe_z)) - self.write ('\n') - + self.write('\n') def tool_defn(self, id, name='', params=None): #self.write('G43 \n') @@ -137,7 +138,6 @@ def tool_defn(self, id, name='', params=None): def write_spindle(self): pass - def spindle(self, s, clockwise): if s < 0: clockwise = not clockwise @@ -146,12 +146,12 @@ def spindle(self, s, clockwise): self.s = self.SPINDLE(self.FORMAT_ANG(), s) if clockwise: #self.s = self.SPINDLE_CW() + self.s - self.s = self.SPINDLE_CW() - self.write(self.s + '\n') + self.s = self.SPINDLE_CW() + self.write(self.s + '\n') self.write('G04 P2.0 \n') else: - self.s = self.SPINDLE_CCW() + self.s + self.s = self.SPINDLE_CCW() + self.s def end_canned_cycle(self): self.write_blocknum() @@ -165,4 +165,5 @@ def end_canned_cycle(self): self.write('M25\n') self.write('G00 X-1.0 Y1.0\n') + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/centroid1_read.py b/scripts/addons/cam/nc/centroid1_read.py index a8040bec1..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/centroid1_read.py +++ b/scripts/addons/cam/nc/centroid1_read.py @@ -3,6 +3,7 @@ # just use the iso reader + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/drag_knife.py b/scripts/addons/cam/nc/drag_knife.py index afc8da612..5288b8e3d 100644 --- a/scripts/addons/cam/nc/drag_knife.py +++ b/scripts/addons/cam/nc/drag_knife.py @@ -5,13 +5,15 @@ # # Dan Heeks 26th April 2012 +import area +import nc +from kurve_funcs import cut_curve as cut_curve from . import recreator dragging = False -from kurve_funcs import cut_curve as cut_curve -import nc -import area ################################################################################ + + class Creator(recreator.Redirector): def __init__(self, original, drag_distance): @@ -21,7 +23,8 @@ def __init__(self, original, drag_distance): self.path = None def cut_path(self): - if self.path == None: return + if self.path == None: + return print self.drag_distance self.path.OffsetForward(self.drag_distance, False) @@ -48,15 +51,19 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): return # add a line to the path - if self.path == None: self.path = area.Curve() + if self.path == None: + self.path = area.Curve() self.path.append(area.Point(self.x, self.y)) - def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw = True): + def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw=True): recreator.Redirector.arc(self, x, y, z, i, j, k, r, ccw) # add an arc to the path - if self.path == None: self.path = area.Curve() - self.path.append(area.Vertex(1 if ccw else -1, area.Point(self.x, self.y), area.Point(i, j))) + if self.path == None: + self.path = area.Curve() + self.path.append(area.Vertex(1 if ccw else -1, + area.Point(self.x, self.y), area.Point(i, j))) + def drag_begin(drag_distance): global dragging @@ -65,6 +72,7 @@ def drag_begin(drag_distance): nc.creator = Creator(nc.creator, drag_distance) dragging = True + def drag_end(): global dragging nc.creator.cut_path() diff --git a/scripts/addons/cam/nc/emc2.py b/scripts/addons/cam/nc/emc2.py index b9ca331c7..db3a311dc 100644 --- a/scripts/addons/cam/nc/emc2.py +++ b/scripts/addons/cam/nc/emc2.py @@ -2,230 +2,235 @@ from . import iso import math -class Creator(iso.Creator): - def __init__(self): - iso.Creator.__init__(self) - def SPACE(self): return('') - def TAP(self): return('G33.1') - def TAP_DEPTH(self, format, depth): return(self.SPACE() + 'K' + (format.string(depth))) - def BORE_FEED_OUT(self): return('G85') - def BORE_SPINDLE_STOP_RAPID_OUT(self): return('G86') - def BORE_DWELL_FEED_OUT(self, format, dwell): return('G89') + self.SPACE() + (format.string(dwell)) +class Creator(iso.Creator): + def __init__(self): + iso.Creator.__init__(self) - def program_begin(self, id, comment): - self.write( ('(' + comment + ')' + '\n') ) + def SPACE(self): return('') + def TAP(self): return('G33.1') + def TAP_DEPTH(self, format, depth): return(self.SPACE() + 'K' + (format.string(depth))) + def BORE_FEED_OUT(self): return('G85') + def BORE_SPINDLE_STOP_RAPID_OUT(self): return('G86') - ############################################################################ - ## Settings + def BORE_DWELL_FEED_OUT(self, format, dwell): return( + 'G89') + self.SPACE() + (format.string(dwell)) - def imperial(self): - self.write( self.IMPERIAL() + '\t (Imperial Values)\n') - self.fmt.number_of_decimal_places = 4 + def program_begin(self, id, comment): + self.write(('(' + comment + ')' + '\n')) - def metric(self): - self.fmt.number_of_decimal_places = 3 - self.write( self.METRIC() + '\t (Metric Values)\n' ) + ############################################################################ + # Settings - def absolute(self): - self.write( self.ABSOLUTE() + '\t (Absolute Coordinates)\n') + def imperial(self): + self.write(self.IMPERIAL() + '\t (Imperial Values)\n') + self.fmt.number_of_decimal_places = 4 - def incremental(self): - self.write( self.INCREMENTAL() + '\t (Incremental Coordinates)\n' ) + def metric(self): + self.fmt.number_of_decimal_places = 3 + self.write(self.METRIC() + '\t (Metric Values)\n') - def polar(self, on=True): - if (on) : - self.write(self.POLAR_ON() + '\t (Polar ON)\n' ) - else : - self.write(self.POLAR_OFF() + '\t (Polar OFF)\n' ) + def absolute(self): + self.write(self.ABSOLUTE() + '\t (Absolute Coordinates)\n') - def set_plane(self, plane): - if (plane == 0) : - self.write(self.PLANE_XY() + '\t (Select XY Plane)\n') - elif (plane == 1) : - self.write(self.PLANE_XZ() + '\t (Select XZ Plane)\n') - elif (plane == 2) : - self.write(self.PLANE_YZ() + '\t (Select YZ Plane)\n') + def incremental(self): + self.write(self.INCREMENTAL() + '\t (Incremental Coordinates)\n') + def polar(self, on=True): + if (on): + self.write(self.POLAR_ON() + '\t (Polar ON)\n') + else: + self.write(self.POLAR_OFF() + '\t (Polar OFF)\n') - # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 - # These are selected by values from 1 to 9 inclusive. - def workplane(self, id): - if ((id >= 1) and (id <= 6)): - self.write( (self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + '\t (Select Relative Coordinate System)\n') - if ((id >= 7) and (id <= 9)): - self.write( ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6))) + '\t (Select Relative Coordinate System)\n') + def set_plane(self, plane): + if (plane == 0): + self.write(self.PLANE_XY() + '\t (Select XY Plane)\n') + elif (plane == 1): + self.write(self.PLANE_XZ() + '\t (Select XZ Plane)\n') + elif (plane == 2): + self.write(self.PLANE_YZ() + '\t (Select YZ Plane)\n') - def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): - if (xml_file_name != None): - self.comment('Generate an XML document describing the probed coordinates found'); - self.write('(LOGOPEN,') - self.write(xml_file_name) - self.write(')\n') + # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 + # These are selected by values from 1 to 9 inclusive. - self.write('(LOG,)\n') + def workplane(self, id): + if ((id >= 1) and (id <= 6)): + self.write((self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + + '\t (Select Relative Coordinate System)\n') + if ((id >= 7) and (id <= 9)): + self.write(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % + (id - 6))) + '\t (Select Relative Coordinate System)\n') - if ((x1 != None) or (y1 != None) or (z1 != None)): - self.write('(LOG,)\n') + def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): + if (xml_file_name != None): + self.comment('Generate an XML document describing the probed coordinates found') + self.write('(LOGOPEN,') + self.write(xml_file_name) + self.write(')\n') - if (x1 != None): - self.write('#<_value>=[' + x1 + ']\n') - self.write('(LOG,#<_value>)\n') + self.write('(LOG,)\n') - if (y1 != None): - self.write('#<_value>=[' + y1 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x1 != None) or (y1 != None) or (z1 != None)): + self.write('(LOG,)\n') - if (z1 != None): - self.write('#<_value>=[' + z1 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x1 != None): + self.write('#<_value>=[' + x1 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x1 != None) or (y1 != None) or (z1 != None)): - self.write('(LOG,)\n') + if (y1 != None): + self.write('#<_value>=[' + y1 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x2 != None) or (y2 != None) or (z2 != None)): - self.write('(LOG,)\n') + if (z1 != None): + self.write('#<_value>=[' + z1 + ']\n') + self.write('(LOG,#<_value>)\n') - if (x2 != None): - self.write('#<_value>=[' + x2 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x1 != None) or (y1 != None) or (z1 != None)): + self.write('(LOG,)\n') - if (y2 != None): - self.write('#<_value>=[' + y2 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x2 != None) or (y2 != None) or (z2 != None)): + self.write('(LOG,)\n') - if (z2 != None): - self.write('#<_value>=[' + z2 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x2 != None): + self.write('#<_value>=[' + x2 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x2 != None) or (y2 != None) or (z2 != None)): - self.write('(LOG,)\n') + if (y2 != None): + self.write('#<_value>=[' + y2 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x3 != None) or (y3 != None) or (z3 != None)): - self.write('(LOG,)\n') + if (z2 != None): + self.write('#<_value>=[' + z2 + ']\n') + self.write('(LOG,#<_value>)\n') - if (x3 != None): - self.write('#<_value>=[' + x3 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x2 != None) or (y2 != None) or (z2 != None)): + self.write('(LOG,)\n') - if (y3 != None): - self.write('#<_value>=[' + y3 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x3 != None) or (y3 != None) or (z3 != None)): + self.write('(LOG,)\n') - if (z3 != None): - self.write('#<_value>=[' + z3 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x3 != None): + self.write('#<_value>=[' + x3 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x3 != None) or (y3 != None) or (z3 != None)): - self.write('(LOG,)\n') + if (y3 != None): + self.write('#<_value>=[' + y3 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x4 != None) or (y4 != None) or (z4 != None)): - self.write('(LOG,)\n') + if (z3 != None): + self.write('#<_value>=[' + z3 + ']\n') + self.write('(LOG,#<_value>)\n') - if (x4 != None): - self.write('#<_value>=[' + x4 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x3 != None) or (y3 != None) or (z3 != None)): + self.write('(LOG,)\n') - if (y4 != None): - self.write('#<_value>=[' + y4 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x4 != None) or (y4 != None) or (z4 != None)): + self.write('(LOG,)\n') - if (z4 != None): - self.write('#<_value>=[' + z4 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x4 != None): + self.write('#<_value>=[' + x4 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x4 != None) or (y4 != None) or (z4 != None)): - self.write('(LOG,)\n') + if (y4 != None): + self.write('#<_value>=[' + y4 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x5 != None) or (y5 != None) or (z5 != None)): - self.write('(LOG,)\n') + if (z4 != None): + self.write('#<_value>=[' + z4 + ']\n') + self.write('(LOG,#<_value>)\n') - if (x5 != None): - self.write('#<_value>=[' + x5 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x4 != None) or (y4 != None) or (z4 != None)): + self.write('(LOG,)\n') - if (y5 != None): - self.write('#<_value>=[' + y5 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x5 != None) or (y5 != None) or (z5 != None)): + self.write('(LOG,)\n') - if (z5 != None): - self.write('#<_value>=[' + z5 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x5 != None): + self.write('#<_value>=[' + x5 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x5 != None) or (y5 != None) or (z5 != None)): - self.write('(LOG,)\n') + if (y5 != None): + self.write('#<_value>=[' + y5 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x6 != None) or (y6 != None) or (z6 != None)): - self.write('(LOG,)\n') + if (z5 != None): + self.write('#<_value>=[' + z5 + ']\n') + self.write('(LOG,#<_value>)\n') - if (x6 != None): - self.write('#<_value>=[' + x6 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x5 != None) or (y5 != None) or (z5 != None)): + self.write('(LOG,)\n') - if (y6 != None): - self.write('#<_value>=[' + y6 + ']\n') - self.write('(LOG,#<_value>)\n') + if ((x6 != None) or (y6 != None) or (z6 != None)): + self.write('(LOG,)\n') - if (z6 != None): - self.write('#<_value>=[' + z6 + ']\n') - self.write('(LOG,#<_value>)\n') + if (x6 != None): + self.write('#<_value>=[' + x6 + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x6 != None) or (y6 != None) or (z6 != None)): - self.write('(LOG,)\n') + if (y6 != None): + self.write('#<_value>=[' + y6 + ']\n') + self.write('(LOG,#<_value>)\n') - self.write('(LOG,)\n') + if (z6 != None): + self.write('#<_value>=[' + z6 + ']\n') + self.write('(LOG,#<_value>)\n') - if (xml_file_name != None): - self.write('(LOGCLOSE)\n') + if ((x6 != None) or (y6 != None) or (z6 != None)): + self.write('(LOG,)\n') - def open_log_file(self, xml_file_name=None ): - self.write('(LOGOPEN,') - self.write(xml_file_name) - self.write(')\n') + self.write('(LOG,)\n') - def close_log_file(self): - self.write('(LOGCLOSE)\n') + if (xml_file_name != None): + self.write('(LOGCLOSE)\n') - def log_coordinate(self, x=None, y=None, z=None): - if ((x != None) or (y != None) or (z != None)): - self.write('(LOG,)\n') + def open_log_file(self, xml_file_name=None): + self.write('(LOGOPEN,') + self.write(xml_file_name) + self.write(')\n') - if (x != None): - self.write('#<_value>=[' + x + ']\n') - self.write('(LOG,#<_value>)\n') + def close_log_file(self): + self.write('(LOGCLOSE)\n') - if (y != None): - self.write('#<_value>=[' + y + ']\n') - self.write('(LOG,#<_value>)\n') + def log_coordinate(self, x=None, y=None, z=None): + if ((x != None) or (y != None) or (z != None)): + self.write('(LOG,)\n') - if (z != None): - self.write('#<_value>=[' + z + ']\n') - self.write('(LOG,#<_value>)\n') + if (x != None): + self.write('#<_value>=[' + x + ']\n') + self.write('(LOG,#<_value>)\n') - if ((x != None) or (y != None) or (z != None)): - self.write('(LOG,)\n') + if (y != None): + self.write('#<_value>=[' + y + ']\n') + self.write('(LOG,#<_value>)\n') - def log_message(self, message=None ): - self.write('(LOG,' + message + ')\n') + if (z != None): + self.write('#<_value>=[' + z + ']\n') + self.write('(LOG,#<_value>)\n') - def start_CRC(self, left = True, radius = 0.0): - if self.t == None: - raise "No tool specified for start_CRC()" - if left: - self.write(('G41' + self.SPACE() + 'D%i') % self.t + '\t (start left cutter radius compensation)\n' ) - else: - self.write(('G42' + self.SPACE() + 'D%i') % self.t + '\t (start right cutter radius compensation)\n' ) + if ((x != None) or (y != None) or (z != None)): + self.write('(LOG,)\n') - def end_CRC(self): - self.g = 'G40' - self.write_preps() - self.write_misc() - self.write('\t (end cutter radius compensation)\n') + def log_message(self, message=None): + self.write('(LOG,' + message + ')\n') + def start_CRC(self, left=True, radius=0.0): + if self.t == None: + raise "No tool specified for start_CRC()" + if left: + self.write(('G41' + self.SPACE() + 'D%i') % + self.t + '\t (start left cutter radius compensation)\n') + else: + self.write(('G42' + self.SPACE() + 'D%i') % + self.t + '\t (start right cutter radius compensation)\n') + def end_CRC(self): + self.g = 'G40' + self.write_preps() + self.write_misc() + self.write('\t (end cutter radius compensation)\n') + def tool_defn(self, id, name='', params=None): + pass - def tool_defn(self, id, name='', params=None): - pass nc.creator = Creator() diff --git a/scripts/addons/cam/nc/emc2_read.py b/scripts/addons/cam/nc/emc2_read.py index a8040bec1..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/emc2_read.py +++ b/scripts/addons/cam/nc/emc2_read.py @@ -3,6 +3,7 @@ # just use the iso reader + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/emc2b.py b/scripts/addons/cam/nc/emc2b.py index bd82c58f0..7e7b00b84 100644 --- a/scripts/addons/cam/nc/emc2b.py +++ b/scripts/addons/cam/nc/emc2b.py @@ -6,6 +6,7 @@ now = datetime.datetime.now() + class Creator(iso_modal.Creator): def __init__(self): iso_modal.Creator.__init__(self) @@ -21,24 +22,26 @@ def SPACE(self): return ' ' def PROGRAM(self): return None + def PROGRAM_END(self): if self.output_tool_change: - return( 'T0' + self.SPACE() + 'M06' + self.SPACE() + 'M02') + return('T0' + self.SPACE() + 'M06' + self.SPACE() + 'M02') else: return('M02') def dwell(self, t): - self.write('\n') - iso_modal.Creator.dwell(self, t) + self.write('\n') + iso_modal.Creator.dwell(self, t) ############################################################################ -## Begin Program - +# Begin Program def program_begin(self, id, comment): if (self.useCrc == False): - self.write( ('(Created with emc2b post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) + self.write(('(Created with emc2b post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) else: - self.write( ('(Created with emc2b Cutter Radius Compensation post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) + self.write(('(Created with emc2b Cutter Radius Compensation post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) iso_modal.Creator.program_begin(self, id, comment) diff --git a/scripts/addons/cam/nc/emc2b_crc.py b/scripts/addons/cam/nc/emc2b_crc.py index 5a5d03784..96fbebc2e 100644 --- a/scripts/addons/cam/nc/emc2b_crc.py +++ b/scripts/addons/cam/nc/emc2b_crc.py @@ -10,6 +10,8 @@ import math ################################################################################ + + class Creator(emc2b.Creator): def __init__(self): @@ -18,4 +20,5 @@ def __init__(self): ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/emc2b_crc_read.py b/scripts/addons/cam/nc/emc2b_crc_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/emc2b_crc_read.py +++ b/scripts/addons/cam/nc/emc2b_crc_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/emc2b_read.py b/scripts/addons/cam/nc/emc2b_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/emc2b_read.py +++ b/scripts/addons/cam/nc/emc2b_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/emc2tap.py b/scripts/addons/cam/nc/emc2tap.py index dd675fe70..0c8a2f1b2 100644 --- a/scripts/addons/cam/nc/emc2tap.py +++ b/scripts/addons/cam/nc/emc2tap.py @@ -3,16 +3,15 @@ from . import emc2 - class CodesEMC2(iso_codes.Codes): def SPACE(self): return(' ') def TAP(self): return('G33.1') def TAP_DEPTH(self, format, depth): return(self.SPACE() + 'K' + (format % depth)) - # This version of COMMENT removes comments from the resultant GCode #def COMMENT(self,comment): return('') + iso_codes.codes = CodesEMC2() @@ -20,9 +19,9 @@ class CreatorEMC2tap(emc2.CreatorEMC2): def init(self): iso.CreatorEMC2.init(self) - # G33.1 tapping with EMC for now # unsynchronized (chuck) taps NIY (tap_mode = 1) + def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): # mystery parameters: # zretract=None, dwell_bottom=None,pitch=None, stoppos=None, spin_in=None, spin_out=None): @@ -32,11 +31,11 @@ def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, # This is a bad thing. All the drilling cycles need a retraction (and starting) height. return if (z == None): - return # We need a Z value as well. This input parameter represents the top of the hole + return # We need a Z value as well. This input parameter represents the top of the hole if (pitch == None): - return # We need a pitch value. + return # We need a pitch value. if (direction == None): - return # We need a direction value. + return # We need a direction value. if (tap_mode != 0): self.comment('only rigid tapping currently supported') @@ -54,32 +53,31 @@ def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, # unsure if this is needed: if self.z != retract_height: - self.rapid(z = retract_height) + self.rapid(z=retract_height) # then continue to x,y if given if (x != None) or (y != None): - self.write_blocknum() - self.write(iso_codes.codes.RAPID() ) + self.write_blocknum() + self.write(iso_codes.codes.RAPID()) - if (x != None): - self.write(iso_codes.codes.X() + (self.fmt % x)) - self.x = x + if (x != None): + self.write(iso_codes.codes.X() + (self.fmt % x)) + self.x = x - if (y != None): - self.write(iso_codes.codes.Y() + (self.fmt % y)) - self.y = y - self.write('\n') + if (y != None): + self.write(iso_codes.codes.Y() + (self.fmt % y)) + self.y = y + self.write('\n') self.write_blocknum() - self.write( iso_codes.codes.TAP() ) - self.write( iso_codes.codes.TAP_DEPTH(self.ffmt,pitch) + iso_codes.codes.SPACE() ) - self.write(iso_codes.codes.Z() + (self.fmt % (z - depth))) # This is the 'z' value for the bottom of the tap. + self.write(iso_codes.codes.TAP()) + self.write(iso_codes.codes.TAP_DEPTH(self.ffmt, pitch) + iso_codes.codes.SPACE()) + # This is the 'z' value for the bottom of the tap. + self.write(iso_codes.codes.Z() + (self.fmt % (z - depth))) self.write_misc() self.write('\n') self.z = retract_height # this cycle returns to the start position, so remember that as z value - nc.creator = CreatorEMC2tap() - diff --git a/scripts/addons/cam/nc/emc2tap_read.py b/scripts/addons/cam/nc/emc2tap_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/emc2tap_read.py +++ b/scripts/addons/cam/nc/emc2tap_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/fadal.py b/scripts/addons/cam/nc/fadal.py index 2dd8a9eb9..2fc4608cc 100644 --- a/scripts/addons/cam/nc/fadal.py +++ b/scripts/addons/cam/nc/fadal.py @@ -11,54 +11,58 @@ from . import iso from .format import Format + class Creator(iso.Creator): - def __init__(self): - iso.Creator.__init__(self) - - # internal variables - - self.fmt = Format(add_trailing_zeros = True) - - ############################################################################ - ## Codes - - def SPACE_STR(self): return ' ' - - ############################################################################ - ## Programs - - def program_begin(self, id, name=''): - if self.use_this_program_id: - id = self.use_this_program_id - if self.PROGRAM() != None: - self.write('%') - self.write('\n') - self.writem([(self.PROGRAM() % id), self.SPACE(), (self.COMMENT(name))]) - self.write('\n') - self.program_id = id - self.program_name = name - - def program_end(self): - if self.z_for_g53 != None: - self.write(self.SPACE() + self.MACHINE_COORDINATES() + self.SPACE() + 'Z' + self.fmt.string(self.z_for_g53) + '\n') - self.write(self.SPACE() + self.PROGRAM_END() + '\n') - self.write('%') - - if self.temp_file_to_append_on_close != None: - f_in = open(self.temp_file_to_append_on_close, 'r') - while (True): - line = f_in.readline() - if (len(line) == 0) : break - self.write(line) - f_in.close() - - self.file_close() - - if self.output_block_numbers: - # number every line of the file afterwards - self.number_file(self.filename) - - for f in self.subroutine_files: - self.number_file(f) + def __init__(self): + iso.Creator.__init__(self) + + # internal variables + + self.fmt = Format(add_trailing_zeros=True) + + ############################################################################ + # Codes + + def SPACE_STR(self): return ' ' + + ############################################################################ + # Programs + + def program_begin(self, id, name=''): + if self.use_this_program_id: + id = self.use_this_program_id + if self.PROGRAM() != None: + self.write('%') + self.write('\n') + self.writem([(self.PROGRAM() % id), self.SPACE(), (self.COMMENT(name))]) + self.write('\n') + self.program_id = id + self.program_name = name + + def program_end(self): + if self.z_for_g53 != None: + self.write(self.SPACE() + self.MACHINE_COORDINATES() + + self.SPACE() + 'Z' + self.fmt.string(self.z_for_g53) + '\n') + self.write(self.SPACE() + self.PROGRAM_END() + '\n') + self.write('%') + + if self.temp_file_to_append_on_close != None: + f_in = open(self.temp_file_to_append_on_close, 'r') + while (True): + line = f_in.readline() + if (len(line) == 0): + break + self.write(line) + f_in.close() + + self.file_close() + + if self.output_block_numbers: + # number every line of the file afterwards + self.number_file(self.filename) + + for f in self.subroutine_files: + self.number_file(f) + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/format.py b/scripts/addons/cam/nc/format.py index e93507f91..95d28bcee 100644 --- a/scripts/addons/cam/nc/format.py +++ b/scripts/addons/cam/nc/format.py @@ -1,10 +1,13 @@ import math + class Format: - def __init__(self, number_of_decimal_places = 3, add_leading_zeros = 1, add_trailing_zeros = False, dp_wanted = True, add_plus = False, no_minus = False, round_down = False): + def __init__(self, number_of_decimal_places=3, add_leading_zeros=1, add_trailing_zeros=False, dp_wanted=True, add_plus=False, no_minus=False, round_down=False): self.number_of_decimal_places = number_of_decimal_places - self.add_leading_zeros = add_leading_zeros # fill the start of the number with zeros, so there are at least this number of digits before the decimal point - self.add_trailing_zeros = add_trailing_zeros # fill the end of the number with zeros, as defined by "number_of_decimal_places" + # fill the start of the number with zeros, so there are at least this number of digits before the decimal point + self.add_leading_zeros = add_leading_zeros + # fill the end of the number with zeros, as defined by "number_of_decimal_places" + self.add_trailing_zeros = add_trailing_zeros self.dp_wanted = dp_wanted self.add_plus = add_plus self.no_minus = no_minus @@ -17,8 +20,10 @@ def string(self, number): s = format(f, 'f') if self.round_down == False: - if f < 0: f = f - .5 - else: f = f + .5 + if f < 0: + f = f - .5 + else: + f = f + .5 s = format(float(number), 'f') if math.fabs(f) < 1.0: @@ -52,13 +57,15 @@ def string(self, number): s += '+' s += before_dp if len(after_dp): - if self.dp_wanted: s += '.' + if self.dp_wanted: + s += '.' s += after_dp return s + class Address: - def __init__(self, text, fmt = Format(), modal = True): + def __init__(self, text, fmt=Format(), modal=True): self.text = text self.fmt = fmt self.modal = modal @@ -69,7 +76,8 @@ def set(self, number): self.str = self.text + self.fmt.string(number) def write(self, writer): - if self.str == None: return '' + if self.str == None: + return '' if self.modal: if self.str != self.previous: writer.write(writer.SPACE() + self.str) @@ -78,8 +86,9 @@ def write(self, writer): writer.write(writer.SPACE() + self.str) self.str = None + class AddressPlusMinus(Address): - def __init__(self, text, fmt = Format(), modal = True): + def __init__(self, text, fmt=Format(), modal=True): Address.__init__(self, text, fmt, modal) self.str2 = None self.previous2 = None @@ -93,7 +102,8 @@ def set(self, number, text_plus, text_minus): def write(self, writer): Address.write(self, writer) - if self.str2 == None: return '' + if self.str2 == None: + return '' if self.modal: if self.str2 != self.previous2: writer.write(writer.SPACE() + self.str2) diff --git a/scripts/addons/cam/nc/gantry_router.py b/scripts/addons/cam/nc/gantry_router.py index 80e5392db..86e1ac436 100644 --- a/scripts/addons/cam/nc/gantry_router.py +++ b/scripts/addons/cam/nc/gantry_router.py @@ -1,17 +1,19 @@ from . import nc from . import emc2 + class Creator(emc2.Creator): - def init(self): - emc2.Creator.init(self) + def init(self): + emc2.Creator.init(self) + + def program_begin(self, id, comment): + self.write(('(' + comment + ')' + '\n')) - def program_begin(self, id, comment): - self.write( ('(' + comment + ')' + '\n') ) + def tool_defn(self, id, name='', params=None): + pass - def tool_defn(self, id, name='', params=None): - pass + def spindle(self, s, clockwise): + pass - def spindle(self, s, clockwise): - pass nc.creator = Creator() diff --git a/scripts/addons/cam/nc/gantry_router_read.py b/scripts/addons/cam/nc/gantry_router_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/gantry_router_read.py +++ b/scripts/addons/cam/nc/gantry_router_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/gravos.py b/scripts/addons/cam/nc/gravos.py index 33a659f55..8dbf91f63 100644 --- a/scripts/addons/cam/nc/gravos.py +++ b/scripts/addons/cam/nc/gravos.py @@ -3,55 +3,55 @@ class Creator(iso.Creator): - def __init__(self): - iso.Creator.__init__(self) - - def SPACE_STR(self): return ' ' - def COMMENT(self, comment): return( (';%s' % comment ) ) - def PROGRAM(self): return(None) - - def program_begin(self, id, comment): - self.write( (';' + comment + '\n') ) - def TIME(self): return('X') - - def SPINDLE_OFF(self): return('M05') - # optimize - def RAPID(self): return('G0') - def FEED(self): return('G1') - - # def SPINDLE_DWELL(self,dwell): - # w='\n'+self.BLOCK() % self.n+ self.DWELL() % dwell - # return w - # - # def SPINDLE_CW(self,dwell): - # return('M03' + self.SPINDLE_DWELL(dwell) ) - # - # def SPINDLE_CCW(self,dwell): - # return('M04' + self.SPINDLE_DWELL(dwell)) - # - # def write_spindle(self): - # #self.write('\n') - # #self.write_blocknum() - # self.s.write(self) - - - def tool_change(self, id): - #print(self.SPACE()) - #print(self.TOOL()) - self.write(self.SPACE() + (self.TOOL() % id) + '\n') - #self.write('\n') - self.flush_nc() - self.t = id - - #def write_spindle(self): - # if self.s.str!=None: - # self.write(self.s.str) - # self.s.str = None - - def PROGRAM_END(self): return( 'M30') - - def program_end(self): - self.write(self.SPACE() + self.SPINDLE_OFF() + self.SPACE() + self.PROGRAM_END() + '\n') + def __init__(self): + iso.Creator.__init__(self) + + def SPACE_STR(self): return ' ' + def COMMENT(self, comment): return((';%s' % comment)) + def PROGRAM(self): return(None) + + def program_begin(self, id, comment): + self.write((';' + comment + '\n')) + + def TIME(self): return('X') + + def SPINDLE_OFF(self): return('M05') + # optimize + def RAPID(self): return('G0') + def FEED(self): return('G1') + + # def SPINDLE_DWELL(self,dwell): + # w='\n'+self.BLOCK() % self.n+ self.DWELL() % dwell + # return w + # + # def SPINDLE_CW(self,dwell): + # return('M03' + self.SPINDLE_DWELL(dwell) ) + # + # def SPINDLE_CCW(self,dwell): + # return('M04' + self.SPINDLE_DWELL(dwell)) + # + # def write_spindle(self): + # #self.write('\n') + # #self.write_blocknum() + # self.s.write(self) + + def tool_change(self, id): + # print(self.SPACE()) + # print(self.TOOL()) + self.write(self.SPACE() + (self.TOOL() % id) + '\n') + # self.write('\n') + self.flush_nc() + self.t = id + + # def write_spindle(self): + # if self.s.str!=None: + # self.write(self.s.str) + # self.s.str = None + + def PROGRAM_END(self): return('M30') + + def program_end(self): + self.write(self.SPACE() + self.SPINDLE_OFF() + self.SPACE() + self.PROGRAM_END() + '\n') nc.creator = Creator() diff --git a/scripts/addons/cam/nc/grbl.py b/scripts/addons/cam/nc/grbl.py index ff71bdd05..57d87c850 100644 --- a/scripts/addons/cam/nc/grbl.py +++ b/scripts/addons/cam/nc/grbl.py @@ -6,53 +6,57 @@ now = datetime.datetime.now() + class Creator(iso_modal.Creator): - def __init__(self): - iso_modal.Creator.__init__(self) - self.absolute_flag = True - self.prev_g91 = '' - self.useCrc = False - self.start_of_line = True - self.output_block_numbers = False - self.output_tool_definitions = False - - def PROGRAM_END(self): return ' ' - #optimize - def RAPID(self): return('G0') - def FEED(self): return('G1') + def __init__(self): + iso_modal.Creator.__init__(self) + self.absolute_flag = True + self.prev_g91 = '' + self.useCrc = False + self.start_of_line = True + self.output_block_numbers = False + self.output_tool_definitions = False + + def PROGRAM_END(self): return ' ' + # optimize + def RAPID(self): return('G0') + def FEED(self): return('G1') ############################################################################ -## Begin Program - - - def program_begin(self, id, comment): - if (self.useCrc == False): - self.write( ('(Created with grbl post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) - else: - self.write( ('(Created with grbl Cutter Radius Compensation post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) - +# Begin Program + def program_begin(self, id, comment): + if (self.useCrc == False): + self.write(('(Created with grbl post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) + else: + self.write(('(Created with grbl Cutter Radius Compensation post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) ############################################################################ -## Settings +# Settings - def tool_defn(self, id, name='', params=None): - pass + def tool_defn(self, id, name='', params=None): + pass - def tool_change(self, id): - pass + def tool_change(self, id): + pass # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 # These are selected by values from 1 to 9 inclusive. - def workplane(self, id): - if ((id >= 1) and (id <= 6)): - self.write_blocknum() - self.write( (self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + '\t (Select Relative Coordinate System)\n') - if ((id >= 7) and (id <= 9)): - self.write_blocknum() - self.write( ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6))) + '\t (Select Relative Coordinate System)\n') + + + def workplane(self, id): + if ((id >= 1) and (id <= 6)): + self.write_blocknum() + self.write((self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + + '\t (Select Relative Coordinate System)\n') + if ((id >= 7) and (id <= 9)): + self.write_blocknum() + self.write(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % + (id - 6))) + '\t (Select Relative Coordinate System)\n') nc.creator = Creator() diff --git a/scripts/addons/cam/nc/heiden.py b/scripts/addons/cam/nc/heiden.py index bb9ff4a30..f75989f1a 100644 --- a/scripts/addons/cam/nc/heiden.py +++ b/scripts/addons/cam/nc/heiden.py @@ -1,1155 +1,1220 @@ # heiden.py, just copied from iso.py, to start with, but needs to be modified to make this sort of output -#1 BEGIN PGM 0011 MM -#2 BLK FORM 0.1 Z X-262.532 Y-262.55 Z-75.95 -#3 BLK FORM 0.2 X262.532 Y262.55 Z0.05 -#4 TOOL CALL 3 Z S3263 DL+0.0 DR+0.0 -#5 TOOL CALL 3 Z S3263 DL+0.0 DR+0.0 -#6 L X-80.644 Y-95.2 Z+100.0 R0 F237 M3 -#7 L Z-23.222 F333 -#8 L X-80.627 Y-95.208 Z-23.5 F326 -#49 L X-73.218 Y-88.104 Z-26.747 F229 -#50 L X-73.529 Y-87.795 Z-26.769 F227 -#51 L X-74.09 Y-87.326 Z-25.996 F279 -#52 M30 -#53 END PGM 0011 MM - - -from . import iso, nc,emc2 +# 1 BEGIN PGM 0011 MM +# 2 BLK FORM 0.1 Z X-262.532 Y-262.55 Z-75.95 +# 3 BLK FORM 0.2 X262.532 Y262.55 Z0.05 +# 4 TOOL CALL 3 Z S3263 DL+0.0 DR+0.0 +# 5 TOOL CALL 3 Z S3263 DL+0.0 DR+0.0 +# 6 L X-80.644 Y-95.2 Z+100.0 R0 F237 M3 +# 7 L Z-23.222 F333 +# 8 L X-80.627 Y-95.208 Z-23.5 F326 +# 49 L X-73.218 Y-88.104 Z-26.747 F229 +# 50 L X-73.529 Y-87.795 Z-26.769 F227 +# 51 L X-74.09 Y-87.326 Z-25.996 F279 +# 52 M30 +# 53 END PGM 0011 MM + + +from . import iso, nc, emc2 import math from .format import Format from .format import * ################################################################################ + + class Creator(nc.Creator): - def __init__(self): - nc.Creator.__init__(self) - - self.a = 0 - self.b = 0 - self.c = 0 - self.f = Address('F', fmt = Format(number_of_decimal_places = 2)) - self.fh = None - self.fv = None - self.fhv = False - self.g_plane = Address('G', fmt = Format(number_of_decimal_places = 0)) - self.g_list = [] - self.i = 0 - self.j = 0 - self.k = 0 - self.m = [] - self.n = 10 - self.r = 0 - self.s = AddressPlusMinus('S', fmt = Format(number_of_decimal_places = 2), modal = False) - self.t = None - self.x = 0 - self.y = 0 - self.z = 500 - self.g0123_modal = False - self.drill_modal = False - self.prev_f = '' - self.prev_g0123 = '' - self.prev_drill = '' - self.prev_retract = '' - self.prev_z = '' - self.useCrc = False - self.useCrcCenterline = False - self.gCRC = '' - self.fmt = Format() - self.absolute_flag = True - self.ffmt = Format(number_of_decimal_places = 2) - self.sfmt = Format(number_of_decimal_places = 1) - self.arc_centre_absolute = False - self.arc_centre_positive = False - self.in_quadrant_splitting = False - self.drillExpanded = False - self.can_do_helical_arcs = True - self.shift_x = 0.0 - self.shift_y = 0.0 - self.shift_z = 0.0 - ############################################################################ - ## Codes - - def SPACE(self): return('') - def FORMAT_FEEDRATE(self): return('%.2f') - def FEEDRATE(self): return((self.SPACE() + 'F')) - def FORMAT_ANG(self): return('%.1f') - def FORMAT_TIME(self): return('%.2f') - def FORMAT_DWELL(self): return('P%f') - - def BLOCK(self): return('%i') - def COMMENT(self,comment): return( ('(%s)' % comment ) ) - def VARIABLE(self): return( '#%i') - def VARIABLE_SET(self): return( '=%.3f') - - def SUBPROG_CALL(self): return( 'M98' + self.SPACE() + 'P%i') - def SUBPROG_END(self): return( 'M99') - - def STOP_OPTIONAL(self): return('M01') - def STOP(self): return('M00') - - def IMPERIAL(self): return('G20') - def METRIC(self): return('G21') - def ABSOLUTE(self): return('G90') - def INCREMENTAL(self): return('G91') - def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92') - def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1') - def POLAR_ON(self): return('G16') - def POLAR_OFF(self): return('G15') - def PLANE_XY(self): return('17') - def PLANE_XZ(self): return('18') - def PLANE_YZ(self): return('19') - - def TOOL(self): return('T%i' + self.SPACE() + 'M06') - def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1') - - def WORKPLANE(self): return('G%i') - def WORKPLANE_BASE(self): return(53) - - def SPINDLE_CW(self): return('M03') - def SPINDLE_CCW(self): return('M04') - def COOLANT_OFF(self): return('M09') - def COOLANT_MIST(self): return('M07') - def COOLANT_FLOOD(self): return('M08') - def GEAR_OFF(self): return('?') - def GEAR(self): return('M%i') - def GEAR_BASE(self): return(37) - - def RAPID(self): return('G00') - def FEED(self): return('G01') - def ARC_CW(self): return('G02') - def ARC_CCW(self): return('G03') - def DWELL(self): return('G04') - def DRILL(self): return('G81') - def DRILL_WITH_DWELL(self, format, dwell): return('G82' + self.SPACE() + (format.string(dwell))) - def PECK_DRILL(self): return('G83') - def PECK_DEPTH(self, format, depth): return(self.SPACE() + 'Q' + (format.string(depth))) - def RETRACT(self, format, height): return(self.SPACE() + 'R' + (format.string(height))) - def END_CANNED_CYCLE(self): return('G80') - def TAP(self): return('G84') - def TAP_DEPTH(self, format, depth): return(self.SPACE() + 'K' + (format.string(depth))) - - def X(self): return('X') - def Y(self): return('Y') - def Z(self): return('Z') - def A(self): return('A') - def B(self): return('B') - def C(self): return('C') - def CENTRE_X(self): return('I') - def CENTRE_Y(self): return('J') - def CENTRE_Z(self): return('K') - def RADIUS(self): return('R') - def TIME(self): return('P') - - def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2') - def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3') - def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4') - def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5') - - def MACHINE_COORDINATES(self): return('G53') - - def EXACT_PATH_MODE(self): return('G61') - def EXACT_STOP_MODE(self): return('G61.1') - - ############################################################################ - ## Internals - - def write_feedrate(self): - self.f.write(self) - - def write_preps(self): - self.g_plane.write(self) - for g in self.g_list: - self.write(self.SPACE() + g) - self.g_list = [] - - def write_misc(self): - if (len(self.m)) : self.write(self.m.pop()) - - def write_blocknum(self): - self.write(self.BLOCK() % self.n) - self.n += 1 - - def write_spindle(self): - self.s.write(self) - - ############################################################################ - ## Programs - - def program_begin(self, id, name=''): - #1 BEGIN PGM 0011 MM - self.write_blocknum() - self.program_id = id - self.write(self.SPACE() + ('BEGIN PGM %i MM' % id)) - self.write('\n') - - def program_stop(self, optional=False): - self.write_blocknum() - if (optional) : - self.write(self.SPACE() + self.STOP_OPTIONAL() + '\n') - self.prev_g0123 = '' - else : - self.write(self.STOP() + '\n') - self.prev_g0123 = '' - - - def program_end(self): - self.write_blocknum() - self.write(self.SPACE() + ('END PGM %i MM' % self.program_id) + '\n') - - def flush_nc(self): - if len(self.g_list) == 0 and len(self.m) == 0: return - self.write_blocknum() - self.write_preps() - self.write_misc() - self.write('\n') - - ############################################################################ - ## Subprograms - - def sub_begin(self, id, name=''): - self.write((self.PROGRAM() % id) + self.SPACE() + (self.COMMENT(name))) - self.write('\n') - - def sub_call(self, id): - self.write_blocknum() - self.write(self.SPACE() + (self.SUBPROG_CALL() % id) + '\n') - - def sub_end(self): - self.write_blocknum() - self.write(self.SPACE() + self.SUBPROG_END() + '\n') - - ############################################################################ - ## Settings - - def imperial(self): - self.g_list.append(self.IMPERIAL()) - self.fmt.number_of_decimal_places = 4 - - def metric(self): - self.g_list.append(self.METRIC()) - self.fmt.number_of_decimal_places = 3 - - def absolute(self): - self.g_list.append(self.ABSOLUTE()) - self.absolute_flag = True - - def incremental(self): - self.g_list.append(self.INCREMENTAL()) - self.absolute_flag = False - - def polar(self, on=True): - if (on) : self.g_list.append(self.POLAR_ON()) - else : self.g_list.append(self.POLAR_OFF()) - - def set_plane(self, plane): - if (plane == 0) : self.g_plane.set(self.PLANE_XY()) - elif (plane == 1) : self.g_plane.set(self.PLANE_XZ()) - elif (plane == 2) : self.g_plane.set(self.PLANE_YZ()) - - def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): - self.write_blocknum() - self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM())) - if (x != None): self.write( self.SPACE() + 'X ' + (self.fmt.string(x + self.shift_x)) ) - if (y != None): self.write( self.SPACE() + 'Y ' + (self.fmt.string(y + self.shift_y)) ) - if (z != None): self.write( self.SPACE() + 'Z ' + (self.fmt.string(z + self.shift_z)) ) - if (a != None): self.write( self.SPACE() + 'A ' + (self.fmt.string(a)) ) - if (b != None): self.write( self.SPACE() + 'B ' + (self.fmt.string(b)) ) - if (c != None): self.write( self.SPACE() + 'C ' + (self.fmt.string(c)) ) - self.write('\n') - - def remove_temporary_origin(self): - self.write_blocknum() - self.write(self.SPACE() + (self.REMOVE_TEMPORARY_COORDINATE_SYSTEM())) - self.write('\n') - ############################################################################ - ## new graphics origin- make a new coordinate system and snap it onto the geometry - ## the toolpath generated should be translated - def translate(self,x=None, y=None, z=None): - self.shift_x = -x - self.shift_y = -y - self.shift_z = -z - - ############################################################################ - ## Tools - - def tool_change(self, id): - self.write_blocknum() - self.write(self.SPACE() + (self.TOOL() % id) + '\n') - self.t = id - - def tool_defn(self, id, name='', params=None): - self.write_blocknum() - self.write(self.SPACE() + self.TOOL_DEFINITION()) - self.write(self.SPACE() + ('P%i' % id) + ' ') - - if (radius != None): - self.write(self.SPACE() + ('R%.3f' % (float(params['diameter'])/2))) - - if (length != None): - self.write(self.SPACE() + 'Z%.3f' % float(params['cutting edge height'])) - - self.write('\n') - - def offset_radius(self, id, radius=None): - pass - - def offset_length(self, id, length=None): - pass - - def current_tool(self): - return self.t - - ############################################################################ - ## Datums - - def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 - # These are selected by values from 1 to 9 inclusive. - def workplane(self, id): - if ((id >= 1) and (id <= 6)): - self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) - if ((id >= 7) and (id <= 9)): - self.g_list.append(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) - - - ############################################################################ - ## Rates + Modes - - def feedrate(self, f): - self.f.set(f) - self.fhv = False - - def feedrate_hv(self, fh, fv): - self.fh = fh - self.fv = fv - self.fhv = True - - def calc_feedrate_hv(self, h, v): - if math.fabs(v) > math.fabs(h * 2): - # some horizontal, so it should be fine to use the horizontal feed rate - self.f.set(self.fv) - else: - # not much, if any horizontal component, so use the vertical feed rate - self.f.set(self.fh) - - def spindle(self, s, clockwise): - if clockwise == True: - self.s.set(s, self.SPACE() + self.SPINDLE_CW(), self.SPACE() + self.SPINDLE_CCW()) - else: - self.s.set(s, self.SPACE() + self.SPINDLE_CCW(), self.SPACE() + self.SPINDLE_CW()) - - def coolant(self, mode=0): - if (mode <= 0) : self.m.append(self.SPACE() + self.COOLANT_OFF()) - elif (mode == 1) : self.m.append(self.SPACE() + self.COOLANT_MIST()) - elif (mode == 2) : self.m.append(self.SPACE() + self.COOLANT_FLOOD()) - - def gearrange(self, gear=0): - if (gear <= 0) : self.m.append(self.SPACE() + self.GEAR_OFF()) - elif (gear <= 4) : self.m.append(self.SPACE() + self.GEAR() % (gear + GEAR_BASE())) - - ############################################################################ - ## Moves - - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): - self.write_blocknum() - - if self.g0123_modal: - if self.prev_g0123 != self.RAPID(): - self.write(self.SPACE() + self.RAPID()) - self.prev_g0123 = self.RAPID() - else: - self.write(self.SPACE() + self.RAPID()) - self.write_preps() - if (x != None): - dx = x - self.x - if (self.absolute_flag ): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - else: - self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) - self.x = x - if (y != None): - dy = y - self.y - if (self.absolute_flag ): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - else: - self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) - - self.y = y - if (z != None): - dz = z - self.z - if (self.absolute_flag ): - self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) - - self.z = z - - if (a != None): - da = a - self.a - if (self.absolute_flag ): - self.write(self.SPACE() + self.A() + (self.fmt.string(a))) - else: - self.write(self.SPACE() + self.A() + (self.fmt.string(da))) - self.a = a - - if (b != None): - db = b - self.b - if (self.absolute_flag ): - self.write(self.SPACE() + self.B() + (self.fmt.string(b))) - else: - self.write(self.SPACE() + self.B() + (self.fmt.string(db))) - self.b = b - - if (c != None): - dc = c - self.c - if (self.absolute_flag ): - self.write(self.SPACE() + self.C() + (self.fmt.string(c))) - else: - self.write(self.SPACE() + self.C() + (self.fmt.string(dc))) - self.c = c - self.write_spindle() - self.write_misc() - self.write('\n') - - def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - if self.same_xyz(x, y, z): return - self.write_blocknum() - if self.g0123_modal: - if self.prev_g0123 != self.FEED(): - self.write(self.SPACE() + self.FEED()) - self.prev_g0123 = self.FEED() - else: - self.write(self.FEED()) - self.write_preps() - dx = dy = dz = 0 - if (x != None): - dx = x - self.x - if (self.absolute_flag ): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - else: - self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) - self.x = x - if (y != None): - dy = y - self.y - if (self.absolute_flag ): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - else: - self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) - - self.y = y - if (z != None): - dz = z - self.z - if (self.absolute_flag ): - self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) - - self.z = z - if (self.fhv) : self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - - def same_xyz(self, x=None, y=None, z=None): - if (x != None): - if (self.fmt.string(x + self.shift_x)) != (self.fmt.string(self.x)): - return False - if (y != None): - if (self.fmt.string(y + self.shift_y)) != (self.fmt.string(self.y)): - return False - if (z != None): - if (self.fmt.string(z + self.shift_z)) != (self.fmt.string(self.z)): - return False - - return True - - - def get_quadrant(self, dx, dy): - if dx < 0: - if dy < 0: - return 2 - else: - return 1 - - else: - if dy < 0: - return 3 - else: - return 0 - - def quadrant_start(self, q, i, j, rad): - while q > 3: q = q - 4 - if q == 0: - return i + rad, j - if q == 1: - return i, j + rad - if q == 2: - return i - rad, j - return i, j - rad - - def quadrant_end(self, q, i, j, rad): - return self.quadrant_start(q + 1, i, j, rad) - - def get_arc_angle(self, sdx, sdy, edx, edy, cw): - angle_s = math.atan2(sdy, sdx); - angle_e = math.atan2(edy, edx); - if cw: - if angle_s < angle_e: angle_s = angle_s + 2 * math.pi - else: - if angle_e < angle_s: angle_e = angle_e + 2 * math.pi - return angle_e - angle_s - - def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - if self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z)): - # split the helical arc into little line feed moves - if x == None: x = self.x - if y == None: y = self.y - sdx = self.x - i - sdy = self.y - j - edx = x - i - edy = y - j - radius = math.sqrt(sdx*sdx + sdy*sdy) - arc_angle = self.get_arc_angle(sdx, sdy, edx, edy, cw) - angle_start = math.atan2(sdy, sdx); - tolerance = 0.02 - angle_step = 2.0 * math.atan( math.sqrt ( tolerance /(radius - tolerance) )) - segments = int(math.fabs(arc_angle / angle_step) + 1) - angle_step = arc_angle / segments - angle = angle_start - z_step = float(z - self.z)/segments - next_z = self.z - for p in range(0, segments): - angle = angle + angle_step - next_x = i + radius * math.cos(angle) - next_y = j + radius * math.sin(angle) - next_z = next_z + z_step - self.feed(next_x, next_y, next_z) - return - - if self.arc_centre_positive == True and self.in_quadrant_splitting == False: - # split in to quadrant arcs - self.in_quadrant_splitting = True - - if x == None: x = self.x - if y == None: y = self.y - sdx = self.x - i - sdy = self.y - j - edx = x - i - edy = y - j - - qs = self.get_quadrant(sdx, sdy) - qe = self.get_quadrant(edx, edy) - - if qs == qe: - arc_angle = math.fabs(self.get_arc_angle(sdx, sdy, edx, edy, cw)) - # arc_angle will be either less than pi/2 or greater than 3pi/2 - if arc_angle > 3.14: - if cw: - qs = qs + 4 - else: - qe = qe + 4 - - if qs == qe: - self.arc(cw, x, y, z, i, j, k, r) - else: - rad = math.sqrt(sdx * sdx + sdy * sdy) - if cw: - if qs < qe: qs = qs + 4 - else: - if qe < qs: qe = qe + 4 - - q = qs - while 1: - x1 = x - y1 = y - if q != qe: - if cw: - x1, y1 = self.quadrant_start(q, i, j, rad) - else: - x1, y1 = self.quadrant_end(q, i, j, rad) - - if ((math.fabs(x1 - self.x) > 0.000001) or (math.fabs(y1 - self.y) > 0.000001)) and ((self.fmt.string(x1) != self.fmt.string(self.x)) or (self.fmt.string(y1) != self.fmt.string(self.y))): - self.arc(cw, x1, y1, z, i, j, k, r) - if q == qe: - break - if cw: - q = q - 1 - else: - q = q + 1 - - self.in_quadrant_splitting = False - return - - #if self.same_xyz(x, y, z): return - self.write_blocknum() - arc_g_code = '' - if cw: arc_g_code = self.ARC_CW() - else: arc_g_code = self.ARC_CCW() - if self.g0123_modal: - if self.prev_g0123 != arc_g_code: - self.write(arc_g_code) - self.prev_g0123 = arc_g_code - else: - self.write(arc_g_code) - self.write_preps() - if (x != None): - dx = x - self.x - if (self.absolute_flag ): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - else: - self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) - if (y != None): - dy = y - self.y - if (self.absolute_flag ): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - else: - self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) - if (z != None): - dz = z - self.z - if (self.absolute_flag ): - self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) - if (i != None): - if self.arc_centre_absolute == False: - i = i - self.x - s = self.fmt.string(i) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_X() + s) - if (j != None): - if self.arc_centre_absolute == False: - j = j - self.y - s = self.fmt.string(j) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_Y() + s) - if (k != None): - if self.arc_centre_absolute == False: - k = k - self.z - s = self.fmt.string(k) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_Z() + s) - if (r != None): - s = self.fmt.string(r) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.RADIUS() + s) + def __init__(self): + nc.Creator.__init__(self) + + self.a = 0 + self.b = 0 + self.c = 0 + self.f = Address('F', fmt=Format(number_of_decimal_places=2)) + self.fh = None + self.fv = None + self.fhv = False + self.g_plane = Address('G', fmt=Format(number_of_decimal_places=0)) + self.g_list = [] + self.i = 0 + self.j = 0 + self.k = 0 + self.m = [] + self.n = 10 + self.r = 0 + self.s = AddressPlusMinus('S', fmt=Format(number_of_decimal_places=2), modal=False) + self.t = None + self.x = 0 + self.y = 0 + self.z = 500 + self.g0123_modal = False + self.drill_modal = False + self.prev_f = '' + self.prev_g0123 = '' + self.prev_drill = '' + self.prev_retract = '' + self.prev_z = '' + self.useCrc = False + self.useCrcCenterline = False + self.gCRC = '' + self.fmt = Format() + self.absolute_flag = True + self.ffmt = Format(number_of_decimal_places=2) + self.sfmt = Format(number_of_decimal_places=1) + self.arc_centre_absolute = False + self.arc_centre_positive = False + self.in_quadrant_splitting = False + self.drillExpanded = False + self.can_do_helical_arcs = True + self.shift_x = 0.0 + self.shift_y = 0.0 + self.shift_z = 0.0 + ############################################################################ + # Codes + + def SPACE(self): return('') + def FORMAT_FEEDRATE(self): return('%.2f') + def FEEDRATE(self): return((self.SPACE() + 'F')) + def FORMAT_ANG(self): return('%.1f') + def FORMAT_TIME(self): return('%.2f') + def FORMAT_DWELL(self): return('P%f') + + def BLOCK(self): return('%i') + def COMMENT(self, comment): return(('(%s)' % comment)) + def VARIABLE(self): return('#%i') + def VARIABLE_SET(self): return('=%.3f') + + def SUBPROG_CALL(self): return('M98' + self.SPACE() + 'P%i') + def SUBPROG_END(self): return('M99') + + def STOP_OPTIONAL(self): return('M01') + def STOP(self): return('M00') + + def IMPERIAL(self): return('G20') + def METRIC(self): return('G21') + def ABSOLUTE(self): return('G90') + def INCREMENTAL(self): return('G91') + def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92') + def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1') + def POLAR_ON(self): return('G16') + def POLAR_OFF(self): return('G15') + def PLANE_XY(self): return('17') + def PLANE_XZ(self): return('18') + def PLANE_YZ(self): return('19') + + def TOOL(self): return('T%i' + self.SPACE() + 'M06') + def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1') + + def WORKPLANE(self): return('G%i') + def WORKPLANE_BASE(self): return(53) + + def SPINDLE_CW(self): return('M03') + def SPINDLE_CCW(self): return('M04') + def COOLANT_OFF(self): return('M09') + def COOLANT_MIST(self): return('M07') + def COOLANT_FLOOD(self): return('M08') + def GEAR_OFF(self): return('?') + def GEAR(self): return('M%i') + def GEAR_BASE(self): return(37) + + def RAPID(self): return('G00') + def FEED(self): return('G01') + def ARC_CW(self): return('G02') + def ARC_CCW(self): return('G03') + def DWELL(self): return('G04') + def DRILL(self): return('G81') + def DRILL_WITH_DWELL(self, format, dwell): return('G82' + self.SPACE() + (format.string(dwell))) + def PECK_DRILL(self): return('G83') + def PECK_DEPTH(self, format, depth): return(self.SPACE() + 'Q' + (format.string(depth))) + def RETRACT(self, format, height): return(self.SPACE() + 'R' + (format.string(height))) + def END_CANNED_CYCLE(self): return('G80') + def TAP(self): return('G84') + def TAP_DEPTH(self, format, depth): return(self.SPACE() + 'K' + (format.string(depth))) + + def X(self): return('X') + def Y(self): return('Y') + def Z(self): return('Z') + def A(self): return('A') + def B(self): return('B') + def C(self): return('C') + def CENTRE_X(self): return('I') + def CENTRE_Y(self): return('J') + def CENTRE_Z(self): return('K') + def RADIUS(self): return('R') + def TIME(self): return('P') + + def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2') + def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3') + def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4') + def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5') + + def MACHINE_COORDINATES(self): return('G53') + + def EXACT_PATH_MODE(self): return('G61') + def EXACT_STOP_MODE(self): return('G61.1') + + ############################################################################ + # Internals + + def write_feedrate(self): + self.f.write(self) + + def write_preps(self): + self.g_plane.write(self) + for g in self.g_list: + self.write(self.SPACE() + g) + self.g_list = [] + + def write_misc(self): + if (len(self.m)): + self.write(self.m.pop()) + + def write_blocknum(self): + self.write(self.BLOCK() % self.n) + self.n += 1 + + def write_spindle(self): + self.s.write(self) + + ############################################################################ + # Programs + + def program_begin(self, id, name=''): + # 1 BEGIN PGM 0011 MM + self.write_blocknum() + self.program_id = id + self.write(self.SPACE() + ('BEGIN PGM %i MM' % id)) + self.write('\n') + + def program_stop(self, optional=False): + self.write_blocknum() + if (optional): + self.write(self.SPACE() + self.STOP_OPTIONAL() + '\n') + self.prev_g0123 = '' + else: + self.write(self.STOP() + '\n') + self.prev_g0123 = '' + + def program_end(self): + self.write_blocknum() + self.write(self.SPACE() + ('END PGM %i MM' % self.program_id) + '\n') + + def flush_nc(self): + if len(self.g_list) == 0 and len(self.m) == 0: + return + self.write_blocknum() + self.write_preps() + self.write_misc() + self.write('\n') + + ############################################################################ + # Subprograms + + def sub_begin(self, id, name=''): + self.write((self.PROGRAM() % id) + self.SPACE() + (self.COMMENT(name))) + self.write('\n') + + def sub_call(self, id): + self.write_blocknum() + self.write(self.SPACE() + (self.SUBPROG_CALL() % id) + '\n') + + def sub_end(self): + self.write_blocknum() + self.write(self.SPACE() + self.SUBPROG_END() + '\n') + + ############################################################################ + # Settings + + def imperial(self): + self.g_list.append(self.IMPERIAL()) + self.fmt.number_of_decimal_places = 4 + + def metric(self): + self.g_list.append(self.METRIC()) + self.fmt.number_of_decimal_places = 3 + + def absolute(self): + self.g_list.append(self.ABSOLUTE()) + self.absolute_flag = True + + def incremental(self): + self.g_list.append(self.INCREMENTAL()) + self.absolute_flag = False + + def polar(self, on=True): + if (on): + self.g_list.append(self.POLAR_ON()) + else: + self.g_list.append(self.POLAR_OFF()) + + def set_plane(self, plane): + if (plane == 0): + self.g_plane.set(self.PLANE_XY()) + elif (plane == 1): + self.g_plane.set(self.PLANE_XZ()) + elif (plane == 2): + self.g_plane.set(self.PLANE_YZ()) + + def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): + self.write_blocknum() + self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM())) + if (x != None): + self.write(self.SPACE() + 'X ' + (self.fmt.string(x + self.shift_x))) + if (y != None): + self.write(self.SPACE() + 'Y ' + (self.fmt.string(y + self.shift_y))) + if (z != None): + self.write(self.SPACE() + 'Z ' + (self.fmt.string(z + self.shift_z))) + if (a != None): + self.write(self.SPACE() + 'A ' + (self.fmt.string(a))) + if (b != None): + self.write(self.SPACE() + 'B ' + (self.fmt.string(b))) + if (c != None): + self.write(self.SPACE() + 'C ' + (self.fmt.string(c))) + self.write('\n') + + def remove_temporary_origin(self): + self.write_blocknum() + self.write(self.SPACE() + (self.REMOVE_TEMPORARY_COORDINATE_SYSTEM())) + self.write('\n') + ############################################################################ + # new graphics origin- make a new coordinate system and snap it onto the geometry + # the toolpath generated should be translated + + def translate(self, x=None, y=None, z=None): + self.shift_x = -x + self.shift_y = -y + self.shift_z = -z + + ############################################################################ + # Tools + + def tool_change(self, id): + self.write_blocknum() + self.write(self.SPACE() + (self.TOOL() % id) + '\n') + self.t = id + + def tool_defn(self, id, name='', params=None): + self.write_blocknum() + self.write(self.SPACE() + self.TOOL_DEFINITION()) + self.write(self.SPACE() + ('P%i' % id) + ' ') + + if (radius != None): + self.write(self.SPACE() + ('R%.3f' % (float(params['diameter'])/2))) + + if (length != None): + self.write(self.SPACE() + 'Z%.3f' % float(params['cutting edge height'])) + + self.write('\n') + + def offset_radius(self, id, radius=None): + pass + + def offset_length(self, id, length=None): + pass + + def current_tool(self): + return self.t + + ############################################################################ + # Datums + + def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 + # These are selected by values from 1 to 9 inclusive. + def workplane(self, id): + if ((id >= 1) and (id <= 6)): + self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + if ((id >= 7) and (id <= 9)): + self.g_list.append( + ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) + + ############################################################################ + ## Rates + Modes + + def feedrate(self, f): + self.f.set(f) + self.fhv = False + + def feedrate_hv(self, fh, fv): + self.fh = fh + self.fv = fv + self.fhv = True + + def calc_feedrate_hv(self, h, v): + if math.fabs(v) > math.fabs(h * 2): + # some horizontal, so it should be fine to use the horizontal feed rate + self.f.set(self.fv) + else: + # not much, if any horizontal component, so use the vertical feed rate + self.f.set(self.fh) + + def spindle(self, s, clockwise): + if clockwise == True: + self.s.set(s, self.SPACE() + self.SPINDLE_CW(), self.SPACE() + self.SPINDLE_CCW()) + else: + self.s.set(s, self.SPACE() + self.SPINDLE_CCW(), self.SPACE() + self.SPINDLE_CW()) + + def coolant(self, mode=0): + if (mode <= 0): + self.m.append(self.SPACE() + self.COOLANT_OFF()) + elif (mode == 1): + self.m.append(self.SPACE() + self.COOLANT_MIST()) + elif (mode == 2): + self.m.append(self.SPACE() + self.COOLANT_FLOOD()) + + def gearrange(self, gear=0): + if (gear <= 0): + self.m.append(self.SPACE() + self.GEAR_OFF()) + elif (gear <= 4): + self.m.append(self.SPACE() + self.GEAR() % (gear + GEAR_BASE())) + + ############################################################################ + # Moves + + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): + self.write_blocknum() + + if self.g0123_modal: + if self.prev_g0123 != self.RAPID(): + self.write(self.SPACE() + self.RAPID()) + self.prev_g0123 = self.RAPID() + else: + self.write(self.SPACE() + self.RAPID()) + self.write_preps() + if (x != None): + dx = x - self.x + if (self.absolute_flag): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + else: + self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) + self.x = x + if (y != None): + dy = y - self.y + if (self.absolute_flag): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + else: + self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) + + self.y = y + if (z != None): + dz = z - self.z + if (self.absolute_flag): + self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) + else: + self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) + + self.z = z + + if (a != None): + da = a - self.a + if (self.absolute_flag): + self.write(self.SPACE() + self.A() + (self.fmt.string(a))) + else: + self.write(self.SPACE() + self.A() + (self.fmt.string(da))) + self.a = a + + if (b != None): + db = b - self.b + if (self.absolute_flag): + self.write(self.SPACE() + self.B() + (self.fmt.string(b))) + else: + self.write(self.SPACE() + self.B() + (self.fmt.string(db))) + self.b = b + + if (c != None): + dc = c - self.c + if (self.absolute_flag): + self.write(self.SPACE() + self.C() + (self.fmt.string(c))) + else: + self.write(self.SPACE() + self.C() + (self.fmt.string(dc))) + self.c = c + self.write_spindle() + self.write_misc() + self.write('\n') + + def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): + if self.same_xyz(x, y, z): + return + self.write_blocknum() + if self.g0123_modal: + if self.prev_g0123 != self.FEED(): + self.write(self.SPACE() + self.FEED()) + self.prev_g0123 = self.FEED() + else: + self.write(self.FEED()) + self.write_preps() + dx = dy = dz = 0 + if (x != None): + dx = x - self.x + if (self.absolute_flag): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + else: + self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) + self.x = x + if (y != None): + dy = y - self.y + if (self.absolute_flag): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + else: + self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) + + self.y = y + if (z != None): + dz = z - self.z + if (self.absolute_flag): + self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) + else: + self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) + + self.z = z + if (self.fhv): + self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + + def same_xyz(self, x=None, y=None, z=None): + if (x != None): + if (self.fmt.string(x + self.shift_x)) != (self.fmt.string(self.x)): + return False + if (y != None): + if (self.fmt.string(y + self.shift_y)) != (self.fmt.string(self.y)): + return False + if (z != None): + if (self.fmt.string(z + self.shift_z)) != (self.fmt.string(self.z)): + return False + + return True + + def get_quadrant(self, dx, dy): + if dx < 0: + if dy < 0: + return 2 + else: + return 1 + + else: + if dy < 0: + return 3 + else: + return 0 + + def quadrant_start(self, q, i, j, rad): + while q > 3: + q = q - 4 + if q == 0: + return i + rad, j + if q == 1: + return i, j + rad + if q == 2: + return i - rad, j + return i, j - rad + + def quadrant_end(self, q, i, j, rad): + return self.quadrant_start(q + 1, i, j, rad) + + def get_arc_angle(self, sdx, sdy, edx, edy, cw): + angle_s = math.atan2(sdy, sdx) + angle_e = math.atan2(edy, edx) + if cw: + if angle_s < angle_e: + angle_s = angle_s + 2 * math.pi + else: + if angle_e < angle_s: + angle_e = angle_e + 2 * math.pi + return angle_e - angle_s + + def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + if self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z)): + # split the helical arc into little line feed moves + if x == None: + x = self.x + if y == None: + y = self.y + sdx = self.x - i + sdy = self.y - j + edx = x - i + edy = y - j + radius = math.sqrt(sdx*sdx + sdy*sdy) + arc_angle = self.get_arc_angle(sdx, sdy, edx, edy, cw) + angle_start = math.atan2(sdy, sdx) + tolerance = 0.02 + angle_step = 2.0 * math.atan(math.sqrt(tolerance / (radius - tolerance))) + segments = int(math.fabs(arc_angle / angle_step) + 1) + angle_step = arc_angle / segments + angle = angle_start + z_step = float(z - self.z)/segments + next_z = self.z + for p in range(0, segments): + angle = angle + angle_step + next_x = i + radius * math.cos(angle) + next_y = j + radius * math.sin(angle) + next_z = next_z + z_step + self.feed(next_x, next_y, next_z) + return + + if self.arc_centre_positive == True and self.in_quadrant_splitting == False: + # split in to quadrant arcs + self.in_quadrant_splitting = True + + if x == None: + x = self.x + if y == None: + y = self.y + sdx = self.x - i + sdy = self.y - j + edx = x - i + edy = y - j + + qs = self.get_quadrant(sdx, sdy) + qe = self.get_quadrant(edx, edy) + + if qs == qe: + arc_angle = math.fabs(self.get_arc_angle(sdx, sdy, edx, edy, cw)) + # arc_angle will be either less than pi/2 or greater than 3pi/2 + if arc_angle > 3.14: + if cw: + qs = qs + 4 + else: + qe = qe + 4 + + if qs == qe: + self.arc(cw, x, y, z, i, j, k, r) + else: + rad = math.sqrt(sdx * sdx + sdy * sdy) + if cw: + if qs < qe: + qs = qs + 4 + else: + if qe < qs: + qe = qe + 4 + + q = qs + while 1: + x1 = x + y1 = y + if q != qe: + if cw: + x1, y1 = self.quadrant_start(q, i, j, rad) + else: + x1, y1 = self.quadrant_end(q, i, j, rad) + + if ((math.fabs(x1 - self.x) > 0.000001) or (math.fabs(y1 - self.y) > 0.000001)) and ((self.fmt.string(x1) != self.fmt.string(self.x)) or (self.fmt.string(y1) != self.fmt.string(self.y))): + self.arc(cw, x1, y1, z, i, j, k, r) + if q == qe: + break + if cw: + q = q - 1 + else: + q = q + 1 + + self.in_quadrant_splitting = False + return + + # if self.same_xyz(x, y, z): return + self.write_blocknum() + arc_g_code = '' + if cw: + arc_g_code = self.ARC_CW() + else: + arc_g_code = self.ARC_CCW() + if self.g0123_modal: + if self.prev_g0123 != arc_g_code: + self.write(arc_g_code) + self.prev_g0123 = arc_g_code + else: + self.write(arc_g_code) + self.write_preps() + if (x != None): + dx = x - self.x + if (self.absolute_flag): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + else: + self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) + if (y != None): + dy = y - self.y + if (self.absolute_flag): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + else: + self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) + if (z != None): + dz = z - self.z + if (self.absolute_flag): + self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) + else: + self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) + if (i != None): + if self.arc_centre_absolute == False: + i = i - self.x + s = self.fmt.string(i) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_X() + s) + if (j != None): + if self.arc_centre_absolute == False: + j = j - self.y + s = self.fmt.string(j) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_Y() + s) + if (k != None): + if self.arc_centre_absolute == False: + k = k - self.z + s = self.fmt.string(k) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_Z() + s) + if (r != None): + s = self.fmt.string(r) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.RADIUS() + s) # use horizontal feed rate - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - if (x != None): - self.x = x - if (y != None): - self.y = y - if (z != None): - self.z = z - - def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - self.arc(True, x, y, z, i, j, k, r) - - def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - self.arc(False, x, y, z, i, j, k, r) - - def dwell(self, t): - self.write_blocknum() - self.write_preps() - self.write(self.FORMAT_DWELL() % t) - self.write_misc() - self.write('\n') - - def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - def rapid_unhome(self): - pass - - def set_machine_coordinates(self): - self.write(self.SPACE() + self.MACHINE_COORDINATES()) - self.prev_g0123 = '' - - ############################################################################ - ## CRC - - def use_CRC(self): - return self.useCrc - - def CRC_nominal_path(self): - return self.useCrcCenterline - - def start_CRC(self, left = True, radius = 0.0): - # set up prep code, to be output on next line - if self.t == None: - raise "No tool specified for start_CRC()" - self.write_blocknum() - if left: - self.write(self.SPACE() + 'G41') - else: - self.write(self.SPACE() + 'G42') - self.write((self.SPACE() + 'D%i\n') % self.t) - - def end_CRC(self): - self.write_blocknum() - self.write(self.SPACE() + 'G40\n') - - ############################################################################ - ## Cycles - - def pattern(self): - pass - - def pocket(self): - pass - - def profile(self): - pass - - # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). - # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to - # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. - # Instead, this routine combines the Z value and the depth value to determine the bottom of - # the hole. - # - # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts - # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. - # - # The peck_depth value is the incremental depth (Q value) that tells the peck drilling - # cycle how deep to go on each peck until the full depth is achieved. - # - # NOTE: This routine forces the mode to absolute mode so that the values passed into - # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't - # revert it. I must set the mode so that I can be sure the values I'm passing in make - # sense to the end-machine. - # - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance = None): - if (standoff == None): - # This is a bad thing. All the drilling cycles need a retraction (and starting) height. - return - - if (z == None): - return # We need a Z value as well. This input parameter represents the top of the hole - - if self.drillExpanded: - # for machines which don't understand G81, G82 etc. - if peck_depth == None: - peck_depth = depth - current_z = z - self.rapid(x, y) - - first = True - - while True: - next_z = current_z - peck_depth - if next_z < z - depth: - next_z = z - depth - if next_z >= current_z: - break; - if first: - self.rapid(z = z + standoff) - else: - self.rapid(z = current_z) - self.feed(z = next_z) - self.rapid(z = z + standoff) - current_z = next_z - if dwell: - self.dwell(dwell) - first = False - - # we should pass clearance height into here, but my machine is on and I'm in a hurry... 22nd June 2011 danheeks - self.rapid(z = z + 5.0) - - return - - self.write_preps() - self.write_blocknum() - - if (peck_depth != 0): - # We're pecking. Let's find a tree. - if self.drill_modal: - if self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth) != self.prev_drill: - self.write(self.SPACE() + self.PECK_DRILL() + self.SPACE() + self.PECK_DEPTH(self.fmt, peck_depth)) - self.prev_drill = self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth) - else: - self.write(self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth)) - - else: - # We're either just drilling or drilling with dwell. - if (dwell == 0): - # We're just drilling. - if self.drill_modal: - if self.DRILL() != self.prev_drill: - self.write(self.SPACE() + self.DRILL()) - self.prev_drill = self.DRILL() - else: - self.write(self.SPACE() + self.DRILL()) - - else: - # We're drilling with dwell. - - if self.drill_modal: - if self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell) != self.prev_drill: - self.write(self.SPACE() + self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell)) - self.prev_drill = self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell) - else: - self.write(self.SPACE() + self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell)) - - - #self.write(self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell)) - - # Set the retraction point to the 'standoff' distance above the starting z height. - retract_height = z + standoff - if (x != None): - dx = x - self.x - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - self.x = x - - if (y != None): - dy = y - self.y - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - self.y = y - - dz = (z + standoff) - self.z # In the end, we will be standoff distance above the z value passed in. - - if self.drill_modal: - if z != self.prev_z: - self.write(self.SPACE() + self.Z() + (self.fmt.string(z - depth))) - self.prev_z=z - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(z - depth))) # This is the 'z' value for the bottom of the hole. - self.z = (z + standoff) # We want to remember where z is at the end (at the top of the hole) - - if self.drill_modal: - if self.prev_retract != self.RETRACT(self.fmt, retract_height) : - self.write(self.SPACE() + self.RETRACT(self.fmt, retract_height)) - self.prev_retract = self.RETRACT(self.fmt, retract_height) - else: - self.write(self.SPACE() + self.RETRACT(self.fmt, retract_height)) - - if (self.fhv) : - self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) - - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - - # G33.1 tapping with EMC for now - # unsynchronized (chuck) taps NIY (tap_mode = 1) - - def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): - # mystery parameters: - # zretract=None, dwell_bottom=None,pitch=None, stoppos=None, spin_in=None, spin_out=None): - # I dont see how to map these to EMC Gcode - - if (standoff == None): - # This is a bad thing. All the drilling cycles need a retraction (and starting) height. - return - if (z == None): - return # We need a Z value as well. This input parameter represents the top of the hole - if (pitch == None): - return # We need a pitch value. - if (direction == None): - return # We need a direction value. - - if (tap_mode != 0): - raise "only rigid tapping currently supported" - - self.write_preps() - self.write_blocknum() - self.write_spindle() - self.write('\n') - - # rapid to starting point; z first, then x,y iff given - - # Set the retraction point to the 'standoff' distance above the starting z height. - retract_height = z + standoff - - # unsure if this is needed: - if self.z != retract_height: - self.rapid(z = retract_height) - - # then continue to x,y if given - if (x != None) or (y != None): - self.write_blocknum() - self.write(self.RAPID() ) - - if (x != None): - self.write(self.X() + self.fmt.string(x + self.shift_x)) - self.x = x - - if (y != None): - self.write(self.Y() + self.fmt.string(y + self.shift_y)) - self.y = y - self.write('\n') - - self.write_blocknum() - self.write( self.TAP() ) - self.write( self.TAP_DEPTH(self.ffmt,pitch) + self.SPACE() ) - self.write(self.Z() + self.fmt.string(z - depth))# This is the 'z' value for the bottom of the tap. - self.write_misc() - self.write('\n') - - self.z = retract_height # this cycle returns to the start position, so remember that as z value - - def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): - pass - - def end_canned_cycle(self): - if self.drillExpanded: - return - self.write_blocknum() - self.write(self.SPACE() + self.END_CANNED_CYCLE() + '\n') - self.prev_drill = '' - self.prev_g0123 = '' - self.prev_z = '' - self.prev_f = '' - self.prev_retract = '' - ############################################################################ - ## Misc - - def comment(self, text): - self.write((self.COMMENT(text) + '\n')) - - def insert(self, text): - pass - - def block_delete(self, on=False): - pass - - def variable(self, id): - return (self.VARIABLE() % id) - - def variable_set(self, id, value): - self.write_blocknum() - self.write(self.SPACE() + (self.VARIABLE() % id) + self.SPACE() + (self.VARIABLE_SET() % value) + '\n') - - # This routine uses the G92 coordinate system offsets to establish a temporary coordinate - # system at the machine's current position. It can then use absolute coordinates relative - # to this position which makes coding easy. It then moves to the 'point along edge' which - # should be above the workpiece but still on one edge. It then backs off from the edge - # to the 'retracted point'. It then plunges down by the depth value specified. It then - # probes back towards the 'destination point'. The probed X,Y location are stored - # into the 'intersection variable' variables. Finally the machine moves back to the - # original location. This is important so that the results of multiple calls to this - # routine may be compared meaningfully. - def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None ): - self.write_blocknum() - self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) - - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write_blocknum() - self.write_feedrate() - self.write('\t(Set the feed rate for probing)\n') - - self.rapid(point_along_edge_x,point_along_edge_y) - self.rapid(retracted_point_x,retracted_point_y) - self.feed(z=depth) - - self.write_blocknum() - self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + ' Y ' + (self.fmt.string(destination_point_y)) ) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Back off the workpiece and re-probe more slowly') - self.write_blocknum() - self.write(self.SPACE() + ('#' + intersection_variable_x + '= [#5061 - [ 0.5 * ' + probe_offset_x_component + ']]\n')) - self.write_blocknum() - self.write(self.SPACE() + ('#' + intersection_variable_y + '= [#5062 - [ 0.5 * ' + probe_offset_y_component + ']]\n')) - self.write_blocknum(); - self.write(self.RAPID()) - self.write(self.SPACE() + ' X #' + intersection_variable_x + ' Y #' + intersection_variable_y + '\n') - - self.write_blocknum() - self.write(self.SPACE() + self.FEEDRATE() + self.ffmt.string(self.fh / 2.0) + '\n') - - self.write_blocknum() - self.write((self.SPACE() + self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + ' Y ' + (self.fmt.string(destination_point_y)) ) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Store the probed location somewhere we can get it again later') - self.write_blocknum() - self.write(('#' + intersection_variable_x + '=' + probe_offset_x_component + ' (Portion of probe radius that contributes to the X coordinate)\n')) - self.write_blocknum() - self.write(('#' + intersection_variable_x + '=[#' + intersection_variable_x + ' + #5061]\n')) - self.write_blocknum() - self.write(('#' + intersection_variable_y + '=' + probe_offset_y_component + ' (Portion of probe radius that contributes to the Y coordinate)\n')) - self.write_blocknum() - self.write(('#' + intersection_variable_y + '=[#' + intersection_variable_y + ' + #5062]\n')) - - self.comment('Now move back to the original location') - self.rapid(retracted_point_x,retracted_point_y) - self.rapid(z=0) - self.rapid(point_along_edge_x,point_along_edge_y) - self.rapid(x=0, y=0) - - self.write_blocknum() - self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ('\t(Restore the previous coordinate system)\n'))) - - def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): - self.write_blocknum() - self.write((self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write_blocknum() - self.write(self.FEEDRATE() + ' [' + self.ffmt.string(self.fh) + ' / 5.0 ]') - self.write('\t(Set the feed rate for probing)\n') - - if x != None and y != None: - self.write_blocknum(); - self.write(self.RAPID()) - self.write(' X ' + x + ' Y ' + y + '\n') - - self.write_blocknum() - self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + ' Z ' + (self.fmt.string(depth)) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Store the probed location somewhere we can get it again later') - self.write_blocknum() - self.write(('#' + intersection_variable_z + '= #5063\n')) - - self.comment('Now move back to the original location') - self.rapid(z=0) - self.rapid(x=0, y=0) - - self.write_blocknum() - self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ('\t(Restore the previous coordinate system)\n'))) - - - def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): - pass - - def open_log_file(self, xml_file_name=None ): - pass - - def log_coordinate(self, x=None, y=None, z=None): - pass - - def log_message(self, message=None): - pass - - def close_log_file(self): - pass - - # Rapid movement to the midpoint between the two points specified. - # NOTE: The points are specified either as strings representing numbers or as strings - # representing variable names. This allows the HeeksCNC module to determine which - # variable names are used in these various routines. - def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): - self.write_blocknum() - self.write(self.RAPID()) - if ((x1 != None) and (x2 != None)): - self.write((' X ' + '[[[' + x1 + ' - ' + x2 + '] / 2.0] + ' + x2 + ']')) - - if ((y1 != None) and (y2 != None)): - self.write((' Y ' + '[[[' + y1 + ' - ' + y2 + '] / 2.0] + ' + y2 + ']')) - - if ((z1 != None) and (z2 != None)): - self.write((' Z ' + '[[[' + z1 + ' - ' + z2 + '] / 2.0] + ' + z2 + ']')) - - self.write('\n') - - # Rapid movement to the intersection of two lines (in the XY plane only). This routine - # is based on information found in http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ - # written by Paul Bourke. The ua_numerator, ua_denominator, ua and ub parameters - # represent variable names (with the preceding '#' included in them) for use as temporary - # variables. They're specified here simply so that HeeksCNC can manage which variables - # are used in which GCode calculations. - # - # As per the notes on the web page, the ua_denominator and ub_denominator formulae are - # the same so we don't repeat this. If the two lines are coincident or parallel then - # no movement occurs. - # - # NOTE: The points are specified either as strings representing numbers or as strings - # representing variable names. This allows the HeeksCNC module to determine which - # variable names are used in these various routines. - def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): - self.comment('Find the intersection of the two lines made up by the four probed points') - self.write_blocknum(); - self.write(ua_numerator + '=[[[' + x4 + ' - ' + x3 + '] * [' + y1 + ' - ' + y3 + ']] - [[' + y4 + ' - ' + y3 + '] * [' + x1 + ' - ' + x3 + ']]]\n') - self.write_blocknum(); - self.write(ua_denominator + '=[[[' + y4 + ' - ' + y3 + '] * [' + x2 + ' - ' + x1 + ']] - [[' + x4 + ' - ' + x3 + '] * [' + y2 + ' - ' + y1 + ']]]\n') - self.write_blocknum(); - self.write(ub_numerator + '=[[[' + x2 + ' - ' + x1 + '] * [' + y1 + ' - ' + y3 + ']] - [[' + y2 + ' - ' + y1 + '] * [' + x1 + ' - ' + x3 + ']]]\n') - - self.comment('If they are not parallel') - self.write('O900 IF [' + ua_denominator + ' NE 0]\n') - self.comment('And if they are not coincident') - self.write('O901 IF [' + ua_numerator + ' NE 0 ]\n') - - self.write_blocknum(); - self.write(' ' + ua + '=[' + ua_numerator + ' / ' + ua_denominator + ']\n') - self.write_blocknum(); - self.write(' ' + ub + '=[' + ub_numerator + ' / ' + ua_denominator + ']\n') # NOTE: ub denominator is the same as ua denominator - self.write_blocknum(); - self.write(' ' + intersection_x + '=[' + x1 + ' + [[' + ua + ' * [' + x2 + ' - ' + x1 + ']]]]\n') - self.write_blocknum(); - self.write(' ' + intersection_y + '=[' + y1 + ' + [[' + ua + ' * [' + y2 + ' - ' + y1 + ']]]]\n') - self.write_blocknum(); - self.write(' ' + self.RAPID()) - self.write(' X ' + intersection_x + ' Y ' + intersection_y + '\n') - - self.write('O901 ENDIF\n') - self.write('O900 ENDIF\n') - - # We need to calculate the rotation angle based on the line formed by the - # x1,y1 and x2,y2 coordinate pair. With that angle, we need to move - # x_offset and y_offset distance from the current (0,0,0) position. - # - # The x1,y1,x2 and y2 parameters are all variable names that contain the actual - # values. - # The x_offset and y_offset are both numeric (floating point) values - def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): - self.comment('Rapid to rotated coordinate') - self.write_blocknum(); - self.write( '#1 = [atan[' + y2 + ' - ' + y1 + ']/[' + x2 +' - ' + x1 + ']] (nominal_angle)\n') - self.write_blocknum(); - self.write( '#2 = [atan[' + ref_y + ']/[' + ref_x + ']] (reference angle)\n') - self.write_blocknum(); - self.write( '#3 = [#1 - #2] (angle)\n' ) - self.write_blocknum(); - self.write( '#4 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + '] * COS[ #3 ]] - [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * SIN[ #3 ]]]\n' ) - self.write_blocknum(); - self.write( '#5 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + '] * SIN[ #3 ]] + [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * COS[ #3 ]]]\n' ) - - self.write_blocknum(); - self.write( '#6 = [[' + (self.fmt.string(x_final)) + ' * COS[ #3 ]] - [' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]]]\n' ) - self.write_blocknum(); - self.write( '#7 = [[' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]] + [' + (self.fmt.string(y_final)) + ' * COS[ #3 ]]]\n' ) - - self.write_blocknum(); - self.write( self.RAPID() + ' X [ #4 + #6 ] Y [ #5 + #7 ]\n' ) - - def BEST_POSSIBLE_SPEED(self, motion_blending_tolerance, naive_cam_tolerance): - statement = 'G64' - - if (motion_blending_tolerance > 0): - statement += ' P ' + str(motion_blending_tolerance) - - if (naive_cam_tolerance > 0): - statement += ' Q ' + str(naive_cam_tolerance) - - return(statement) - - def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance ): - self.write_blocknum() - if (mode == 0): - self.write( self.EXACT_PATH_MODE() + '\n' ) - if (mode == 1): - self.write( self.EXACT_STOP_MODE() + '\n' ) - if (mode == 2): - self.write( self.BEST_POSSIBLE_SPEED( motion_blending_tolerance, naive_cam_tolerance ) + '\n' ) + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + if (x != None): + self.x = x + if (y != None): + self.y = y + if (z != None): + self.z = z + + def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + self.arc(True, x, y, z, i, j, k, r) + + def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + self.arc(False, x, y, z, i, j, k, r) + + def dwell(self, t): + self.write_blocknum() + self.write_preps() + self.write(self.FORMAT_DWELL() % t) + self.write_misc() + self.write('\n') + + def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + def rapid_unhome(self): + pass + + def set_machine_coordinates(self): + self.write(self.SPACE() + self.MACHINE_COORDINATES()) + self.prev_g0123 = '' + + ############################################################################ + # CRC + + def use_CRC(self): + return self.useCrc + + def CRC_nominal_path(self): + return self.useCrcCenterline + + def start_CRC(self, left=True, radius=0.0): + # set up prep code, to be output on next line + if self.t == None: + raise "No tool specified for start_CRC()" + self.write_blocknum() + if left: + self.write(self.SPACE() + 'G41') + else: + self.write(self.SPACE() + 'G42') + self.write((self.SPACE() + 'D%i\n') % self.t) + + def end_CRC(self): + self.write_blocknum() + self.write(self.SPACE() + 'G40\n') + + ############################################################################ + # Cycles + + def pattern(self): + pass + + def pocket(self): + pass + + def profile(self): + pass + + # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). + # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to + # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. + # Instead, this routine combines the Z value and the depth value to determine the bottom of + # the hole. + # + # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts + # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. + # + # The peck_depth value is the incremental depth (Q value) that tells the peck drilling + # cycle how deep to go on each peck until the full depth is achieved. + # + # NOTE: This routine forces the mode to absolute mode so that the values passed into + # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't + # revert it. I must set the mode so that I can be sure the values I'm passing in make + # sense to the end-machine. + # + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + if (standoff == None): + # This is a bad thing. All the drilling cycles need a retraction (and starting) height. + return + + if (z == None): + return # We need a Z value as well. This input parameter represents the top of the hole + + if self.drillExpanded: + # for machines which don't understand G81, G82 etc. + if peck_depth == None: + peck_depth = depth + current_z = z + self.rapid(x, y) + + first = True + + while True: + next_z = current_z - peck_depth + if next_z < z - depth: + next_z = z - depth + if next_z >= current_z: + break + if first: + self.rapid(z=z + standoff) + else: + self.rapid(z=current_z) + self.feed(z=next_z) + self.rapid(z=z + standoff) + current_z = next_z + if dwell: + self.dwell(dwell) + first = False + + # we should pass clearance height into here, but my machine is on and I'm in a hurry... 22nd June 2011 danheeks + self.rapid(z=z + 5.0) + + return + + self.write_preps() + self.write_blocknum() + + if (peck_depth != 0): + # We're pecking. Let's find a tree. + if self.drill_modal: + if self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth) != self.prev_drill: + self.write(self.SPACE() + self.PECK_DRILL() + self.SPACE() + + self.PECK_DEPTH(self.fmt, peck_depth)) + self.prev_drill = self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth) + else: + self.write(self.PECK_DRILL() + self.PECK_DEPTH(self.fmt, peck_depth)) + + else: + # We're either just drilling or drilling with dwell. + if (dwell == 0): + # We're just drilling. + if self.drill_modal: + if self.DRILL() != self.prev_drill: + self.write(self.SPACE() + self.DRILL()) + self.prev_drill = self.DRILL() + else: + self.write(self.SPACE() + self.DRILL()) + + else: + # We're drilling with dwell. + + if self.drill_modal: + if self.DRILL_WITH_DWELL(self.FORMAT_DWELL(), dwell) != self.prev_drill: + self.write(self.SPACE() + self.DRILL_WITH_DWELL(self.FORMAT_DWELL(), dwell)) + self.prev_drill = self.DRILL_WITH_DWELL(self.FORMAT_DWELL(), dwell) + else: + self.write(self.SPACE() + self.DRILL_WITH_DWELL(self.FORMAT_DWELL(), dwell)) + + # self.write(self.DRILL_WITH_DWELL(self.FORMAT_DWELL(),dwell)) + + # Set the retraction point to the 'standoff' distance above the starting z height. + retract_height = z + standoff + if (x != None): + dx = x - self.x + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + self.x = x + + if (y != None): + dy = y - self.y + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + self.y = y + + # In the end, we will be standoff distance above the z value passed in. + dz = (z + standoff) - self.z + + if self.drill_modal: + if z != self.prev_z: + self.write(self.SPACE() + self.Z() + (self.fmt.string(z - depth))) + self.prev_z = z + else: + # This is the 'z' value for the bottom of the hole. + self.write(self.SPACE() + self.Z() + (self.fmt.string(z - depth))) + # We want to remember where z is at the end (at the top of the hole) + self.z = (z + standoff) + + if self.drill_modal: + if self.prev_retract != self.RETRACT(self.fmt, retract_height): + self.write(self.SPACE() + self.RETRACT(self.fmt, retract_height)) + self.prev_retract = self.RETRACT(self.fmt, retract_height) + else: + self.write(self.SPACE() + self.RETRACT(self.fmt, retract_height)) + + if (self.fhv): + self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) + + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + + # G33.1 tapping with EMC for now + # unsynchronized (chuck) taps NIY (tap_mode = 1) + + def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): + # mystery parameters: + # zretract=None, dwell_bottom=None,pitch=None, stoppos=None, spin_in=None, spin_out=None): + # I dont see how to map these to EMC Gcode + + if (standoff == None): + # This is a bad thing. All the drilling cycles need a retraction (and starting) height. + return + if (z == None): + return # We need a Z value as well. This input parameter represents the top of the hole + if (pitch == None): + return # We need a pitch value. + if (direction == None): + return # We need a direction value. + + if (tap_mode != 0): + raise "only rigid tapping currently supported" + + self.write_preps() + self.write_blocknum() + self.write_spindle() + self.write('\n') + + # rapid to starting point; z first, then x,y iff given + + # Set the retraction point to the 'standoff' distance above the starting z height. + retract_height = z + standoff + + # unsure if this is needed: + if self.z != retract_height: + self.rapid(z=retract_height) + + # then continue to x,y if given + if (x != None) or (y != None): + self.write_blocknum() + self.write(self.RAPID()) + + if (x != None): + self.write(self.X() + self.fmt.string(x + self.shift_x)) + self.x = x + + if (y != None): + self.write(self.Y() + self.fmt.string(y + self.shift_y)) + self.y = y + self.write('\n') + + self.write_blocknum() + self.write(self.TAP()) + self.write(self.TAP_DEPTH(self.ffmt, pitch) + self.SPACE()) + # This is the 'z' value for the bottom of the tap. + self.write(self.Z() + self.fmt.string(z - depth)) + self.write_misc() + self.write('\n') + + self.z = retract_height # this cycle returns to the start position, so remember that as z value + + def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): + pass + + def end_canned_cycle(self): + if self.drillExpanded: + return + self.write_blocknum() + self.write(self.SPACE() + self.END_CANNED_CYCLE() + '\n') + self.prev_drill = '' + self.prev_g0123 = '' + self.prev_z = '' + self.prev_f = '' + self.prev_retract = '' + ############################################################################ + # Misc + + def comment(self, text): + self.write((self.COMMENT(text) + '\n')) + + def insert(self, text): + pass + + def block_delete(self, on=False): + pass + + def variable(self, id): + return (self.VARIABLE() % id) + + def variable_set(self, id, value): + self.write_blocknum() + self.write(self.SPACE() + (self.VARIABLE() % id) + + self.SPACE() + (self.VARIABLE_SET() % value) + '\n') + + # This routine uses the G92 coordinate system offsets to establish a temporary coordinate + # system at the machine's current position. It can then use absolute coordinates relative + # to this position which makes coding easy. It then moves to the 'point along edge' which + # should be above the workpiece but still on one edge. It then backs off from the edge + # to the 'retracted point'. It then plunges down by the depth value specified. It then + # probes back towards the 'destination point'. The probed X,Y location are stored + # into the 'intersection variable' variables. Finally the machine moves back to the + # original location. This is important so that the results of multiple calls to this + # routine may be compared meaningfully. + def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None): + self.write_blocknum() + self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM() + + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) + + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write_blocknum() + self.write_feedrate() + self.write('\t(Set the feed rate for probing)\n') + + self.rapid(point_along_edge_x, point_along_edge_y) + self.rapid(retracted_point_x, retracted_point_y) + self.feed(z=depth) + + self.write_blocknum() + self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + + ' Y ' + (self.fmt.string(destination_point_y))) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Back off the workpiece and re-probe more slowly') + self.write_blocknum() + self.write(self.SPACE() + ('#' + intersection_variable_x + + '= [#5061 - [ 0.5 * ' + probe_offset_x_component + ']]\n')) + self.write_blocknum() + self.write(self.SPACE() + ('#' + intersection_variable_y + + '= [#5062 - [ 0.5 * ' + probe_offset_y_component + ']]\n')) + self.write_blocknum() + self.write(self.RAPID()) + self.write(self.SPACE() + ' X #' + intersection_variable_x + + ' Y #' + intersection_variable_y + '\n') + + self.write_blocknum() + self.write(self.SPACE() + self.FEEDRATE() + self.ffmt.string(self.fh / 2.0) + '\n') + + self.write_blocknum() + self.write((self.SPACE() + self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x) + ) + ' Y ' + (self.fmt.string(destination_point_y))) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Store the probed location somewhere we can get it again later') + self.write_blocknum() + self.write(('#' + intersection_variable_x + '=' + probe_offset_x_component + + ' (Portion of probe radius that contributes to the X coordinate)\n')) + self.write_blocknum() + self.write(('#' + intersection_variable_x + + '=[#' + intersection_variable_x + ' + #5061]\n')) + self.write_blocknum() + self.write(('#' + intersection_variable_y + '=' + probe_offset_y_component + + ' (Portion of probe radius that contributes to the Y coordinate)\n')) + self.write_blocknum() + self.write(('#' + intersection_variable_y + + '=[#' + intersection_variable_y + ' + #5062]\n')) + + self.comment('Now move back to the original location') + self.rapid(retracted_point_x, retracted_point_y) + self.rapid(z=0) + self.rapid(point_along_edge_x, point_along_edge_y) + self.rapid(x=0, y=0) + + self.write_blocknum() + self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + + ('\t(Restore the previous coordinate system)\n'))) + + def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): + self.write_blocknum() + self.write((self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + + ('\t(Temporarily make this the origin)\n'))) + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write_blocknum() + self.write(self.FEEDRATE() + ' [' + self.ffmt.string(self.fh) + ' / 5.0 ]') + self.write('\t(Set the feed rate for probing)\n') + + if x != None and y != None: + self.write_blocknum() + self.write(self.RAPID()) + self.write(' X ' + x + ' Y ' + y + '\n') + + self.write_blocknum() + self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + ' Z ' + + (self.fmt.string(depth)) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Store the probed location somewhere we can get it again later') + self.write_blocknum() + self.write(('#' + intersection_variable_z + '= #5063\n')) + + self.comment('Now move back to the original location') + self.rapid(z=0) + self.rapid(x=0, y=0) + + self.write_blocknum() + self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + + ('\t(Restore the previous coordinate system)\n'))) + + def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): + pass + + def open_log_file(self, xml_file_name=None): + pass + + def log_coordinate(self, x=None, y=None, z=None): + pass + + def log_message(self, message=None): + pass + + def close_log_file(self): + pass + + # Rapid movement to the midpoint between the two points specified. + # NOTE: The points are specified either as strings representing numbers or as strings + # representing variable names. This allows the HeeksCNC module to determine which + # variable names are used in these various routines. + def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): + self.write_blocknum() + self.write(self.RAPID()) + if ((x1 != None) and (x2 != None)): + self.write((' X ' + '[[[' + x1 + ' - ' + x2 + '] / 2.0] + ' + x2 + ']')) + + if ((y1 != None) and (y2 != None)): + self.write((' Y ' + '[[[' + y1 + ' - ' + y2 + '] / 2.0] + ' + y2 + ']')) + + if ((z1 != None) and (z2 != None)): + self.write((' Z ' + '[[[' + z1 + ' - ' + z2 + '] / 2.0] + ' + z2 + ']')) + + self.write('\n') + + # Rapid movement to the intersection of two lines (in the XY plane only). This routine + # is based on information found in http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ + # written by Paul Bourke. The ua_numerator, ua_denominator, ua and ub parameters + # represent variable names (with the preceding '#' included in them) for use as temporary + # variables. They're specified here simply so that HeeksCNC can manage which variables + # are used in which GCode calculations. + # + # As per the notes on the web page, the ua_denominator and ub_denominator formulae are + # the same so we don't repeat this. If the two lines are coincident or parallel then + # no movement occurs. + # + # NOTE: The points are specified either as strings representing numbers or as strings + # representing variable names. This allows the HeeksCNC module to determine which + # variable names are used in these various routines. + def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): + self.comment('Find the intersection of the two lines made up by the four probed points') + self.write_blocknum() + self.write(ua_numerator + '=[[[' + x4 + ' - ' + x3 + '] * [' + y1 + ' - ' + + y3 + ']] - [[' + y4 + ' - ' + y3 + '] * [' + x1 + ' - ' + x3 + ']]]\n') + self.write_blocknum() + self.write(ua_denominator + '=[[[' + y4 + ' - ' + y3 + '] * [' + x2 + ' - ' + + x1 + ']] - [[' + x4 + ' - ' + x3 + '] * [' + y2 + ' - ' + y1 + ']]]\n') + self.write_blocknum() + self.write(ub_numerator + '=[[[' + x2 + ' - ' + x1 + '] * [' + y1 + ' - ' + + y3 + ']] - [[' + y2 + ' - ' + y1 + '] * [' + x1 + ' - ' + x3 + ']]]\n') + + self.comment('If they are not parallel') + self.write('O900 IF [' + ua_denominator + ' NE 0]\n') + self.comment('And if they are not coincident') + self.write('O901 IF [' + ua_numerator + ' NE 0 ]\n') + + self.write_blocknum() + self.write(' ' + ua + '=[' + ua_numerator + ' / ' + ua_denominator + ']\n') + self.write_blocknum() + # NOTE: ub denominator is the same as ua denominator + self.write(' ' + ub + '=[' + ub_numerator + ' / ' + ua_denominator + ']\n') + self.write_blocknum() + self.write(' ' + intersection_x + '=[' + x1 + + ' + [[' + ua + ' * [' + x2 + ' - ' + x1 + ']]]]\n') + self.write_blocknum() + self.write(' ' + intersection_y + '=[' + y1 + + ' + [[' + ua + ' * [' + y2 + ' - ' + y1 + ']]]]\n') + self.write_blocknum() + self.write(' ' + self.RAPID()) + self.write(' X ' + intersection_x + ' Y ' + intersection_y + '\n') + + self.write('O901 ENDIF\n') + self.write('O900 ENDIF\n') + + # We need to calculate the rotation angle based on the line formed by the + # x1,y1 and x2,y2 coordinate pair. With that angle, we need to move + # x_offset and y_offset distance from the current (0,0,0) position. + # + # The x1,y1,x2 and y2 parameters are all variable names that contain the actual + # values. + # The x_offset and y_offset are both numeric (floating point) values + def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): + self.comment('Rapid to rotated coordinate') + self.write_blocknum() + self.write('#1 = [atan[' + y2 + ' - ' + y1 + ']/[' + + x2 + ' - ' + x1 + ']] (nominal_angle)\n') + self.write_blocknum() + self.write('#2 = [atan[' + ref_y + ']/[' + ref_x + ']] (reference angle)\n') + self.write_blocknum() + self.write('#3 = [#1 - #2] (angle)\n') + self.write_blocknum() + self.write('#4 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + + '] * COS[ #3 ]] - [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * SIN[ #3 ]]]\n') + self.write_blocknum() + self.write('#5 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + + '] * SIN[ #3 ]] + [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * COS[ #3 ]]]\n') + + self.write_blocknum() + self.write('#6 = [[' + (self.fmt.string(x_final)) + ' * COS[ #3 ]] - [' + + (self.fmt.string(y_final)) + ' * SIN[ #3 ]]]\n') + self.write_blocknum() + self.write('#7 = [[' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]] + [' + + (self.fmt.string(y_final)) + ' * COS[ #3 ]]]\n') + + self.write_blocknum() + self.write(self.RAPID() + ' X [ #4 + #6 ] Y [ #5 + #7 ]\n') + + def BEST_POSSIBLE_SPEED(self, motion_blending_tolerance, naive_cam_tolerance): + statement = 'G64' + + if (motion_blending_tolerance > 0): + statement += ' P ' + str(motion_blending_tolerance) + + if (naive_cam_tolerance > 0): + statement += ' Q ' + str(naive_cam_tolerance) + + return(statement) + + def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance): + self.write_blocknum() + if (mode == 0): + self.write(self.EXACT_PATH_MODE() + '\n') + if (mode == 1): + self.write(self.EXACT_STOP_MODE() + '\n') + if (mode == 2): + self.write(self.BEST_POSSIBLE_SPEED( + motion_blending_tolerance, naive_cam_tolerance) + '\n') ################################################################################ diff --git a/scripts/addons/cam/nc/heiden530.py b/scripts/addons/cam/nc/heiden530.py index d7473f871..1a5e4061b 100644 --- a/scripts/addons/cam/nc/heiden530.py +++ b/scripts/addons/cam/nc/heiden530.py @@ -13,6 +13,7 @@ from .format import Format from .format import * + class Creator(iso.Creator): def __init__(self): iso.Creator.__init__(self) @@ -34,15 +35,14 @@ def __init__(self): self.shift_y = 0.0 self.shift_z = 0.0 - ############################################################################ - ## Codes + # Codes def SPACE(self): return(' ') def NEW_LINE(self): return('\n') def BLOCK(self): return('%i') - def COMMENT(self,comment): return( (';%s' % comment ) ) + def COMMENT(self, comment): return((';%s' % comment)) def BEGIN_PGM(self): return('BEGIN PGM %i') def END_PGM(self): return('END PGM %i') @@ -65,7 +65,7 @@ def Y(self): return('Y') def Z(self): return('Z') ############################################################################ - ## Internals + # Internals def write_blocknum(self): self.write(self.BLOCK() % self.n) @@ -76,7 +76,7 @@ def write_spindle(self): self.write(self.NEW_LINE()) ############################################################################ - ## Programs + # Programs def program_begin(self, id, name=''): self.program_id = id @@ -99,7 +99,7 @@ def program_end(self): self.write(self.NEW_LINE()) ############################################################################ - ## Settings + # Settings def absolute(self): pass @@ -111,7 +111,7 @@ def set_plane(self, plane): pass ############################################################################ - ## Tools + # Tools def tool_change(self, id): self.t = id @@ -122,9 +122,9 @@ def tool_change(self, id): self.write(self.TOOL() % self.t) ############################################################################ - ## Moves + # Moves - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write_blocknum() @@ -135,14 +135,14 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) else: self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) self.x = x if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) else: self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) @@ -150,7 +150,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): self.y = y if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) else: self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) @@ -162,8 +162,8 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - (x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z) - if axis_count==0: return + (x, y, z, a, b, c, axis_count) = self.filter_xyz(x, y, z) + if axis_count == 0: return self.write_blocknum() @@ -201,7 +201,7 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write_misc() ############################################################################ - ## Misc + # Misc def comment(self, text): self.write((self.COMMENT(text) + '\n')) diff --git a/scripts/addons/cam/nc/heiden_read.py b/scripts/addons/cam/nc/heiden_read.py index bcd14760d..e2f81fe44 100644 --- a/scripts/addons/cam/nc/heiden_read.py +++ b/scripts/addons/cam/nc/heiden_read.py @@ -10,6 +10,8 @@ import math ################################################################################ + + class Parser(nc.Parser): def __init__(self, writer): @@ -97,7 +99,7 @@ def ParseWord(self, word): def Parsey(self, name): self.files_open(name) - for_full_machine_sim = True # to do, make derived class to do this + for_full_machine_sim = True # to do, make derived class to do this #for_full_machine_sim = False self.f = None @@ -135,16 +137,22 @@ def Parsey(self, name): self.ParseWord(word) if (self.move and not self.no_move): - if (self.arc==0): + if (self.arc == 0): self.add_line(self.x, self.y, self.z, self.a, self.b, self.rapid) else: self.add_arc(self.x, self.y, self.z, self.i, self.j, self.k, self.r, self.arc) - if self.x != None: self.oldx = self.x - if self.y != None: self.oldy = self.y - if self.z != None: self.oldz = self.z - if self.a != None: self.olda = self.a - if self.b != None: self.oldb = self.b - if self.c != None: self.oldc = self.c + if self.x != None: + self.oldx = self.x + if self.y != None: + self.oldy = self.y + if self.z != None: + self.oldz = self.z + if self.a != None: + self.olda = self.a + if self.b != None: + self.oldb = self.b + if self.c != None: + self.oldc = self.c elif (self.t): self.change_tool(self.t) diff --git a/scripts/addons/cam/nc/hm50.py b/scripts/addons/cam/nc/hm50.py index 2e0b065ba..2282edad1 100644 --- a/scripts/addons/cam/nc/hm50.py +++ b/scripts/addons/cam/nc/hm50.py @@ -1,19 +1,20 @@ from . import nc from . import emc2 + class Creator(emc2.Creator): - def __init__(self): - emc2.Creator.__init__(self) + def __init__(self): + emc2.Creator.__init__(self) - def program_begin(self, id, comment): - self.write( ('(' + comment + ')' + '\n') ) + def program_begin(self, id, comment): + self.write(('(' + comment + ')' + '\n')) - def tool_change(self, id): - self.write('G53 G00 Z30\n') - self.write((self.TOOL() % id) + '\n') - self.write('G01 Z100.000 F800.000\n') - self.write('M0\n') - self.write('G01 Z10.000 F300.000\n') + def tool_change(self, id): + self.write('G53 G00 Z30\n') + self.write((self.TOOL() % id) + '\n') + self.write('G01 Z100.000 F800.000\n') + self.write('M0\n') + self.write('G01 Z10.000 F300.000\n') nc.creator = Creator() diff --git a/scripts/addons/cam/nc/hm50_read.py b/scripts/addons/cam/nc/hm50_read.py index 25f8cf320..3df5337c7 100644 --- a/scripts/addons/cam/nc/hm50_read.py +++ b/scripts/addons/cam/nc/hm50_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/hpgl2d.py b/scripts/addons/cam/nc/hpgl2d.py index 7b6642c18..fa7b8a9db 100644 --- a/scripts/addons/cam/nc/hpgl2d.py +++ b/scripts/addons/cam/nc/hpgl2d.py @@ -7,18 +7,19 @@ from . import nc import math + class Creator(nc.Creator): def __init__(self): nc.Creator.__init__(self) self.x = int(0) - self.y = int(0) # these are in machine units, like 0.01mm or maybe 0.25mm - self.metric() # set self.units_to_mc_units + self.y = int(0) # these are in machine units, like 0.01mm or maybe 0.25mm + self.metric() # set self.units_to_mc_units def imperial(self): - self.units_to_mc_units = 2540 # multiplier from inches to machine units + self.units_to_mc_units = 2540 # multiplier from inches to machine units def metric(self): - self.units_to_mc_units = 100 # multiplier from mm to machine units + self.units_to_mc_units = 100 # multiplier from mm to machine units def program_begin(self, id, name=''): self.write('IN;\n') @@ -89,12 +90,15 @@ def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): start_angle = math.atan2(sdy, sdx) end_angle = math.atan2(edy, edx) if cw: - if start_angle < end_angle: start_angle += 2 * math.pi + if start_angle < end_angle: + start_angle += 2 * math.pi else: - if end_angle < start_angle: end_angle += 2 * math.pi + if end_angle < start_angle: + end_angle += 2 * math.pi a = math.fabs(end_angle - start_angle) - if cw: a = -a + if cw: + a = -a mcx, mcy = self.get_machine_x_y(cx, cy) @@ -106,4 +110,5 @@ def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.arc(False, x, y, z, i, j, k, r) + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/hpgl2d_read.py b/scripts/addons/cam/nc/hpgl2d_read.py index c1825bca2..4a49c5fd5 100644 --- a/scripts/addons/cam/nc/hpgl2d_read.py +++ b/scripts/addons/cam/nc/hpgl2d_read.py @@ -2,6 +2,7 @@ import sys import math + class Parser(num_reader.NumReader): def __init__(self, writer): @@ -21,12 +22,16 @@ def ParsePuOrPd(self, up): if len(x) > 0: y = self.get_number() if len(y) > 0: - if up: color = "rapid" - else: color = "feed" + if up: + color = "rapid" + else: + color = "feed" self.add_word(color) self.begin_path(color) - if up: z = self.up_z - else: z = self.down_z + if up: + z = self.up_z + else: + z = self.down_z if self.up != up: self.add_line(self.x * self.units_to_mm, self.y * self.units_to_mm, z) self.add_line(int(x) * self.units_to_mm, int(y) * self.units_to_mm, z) @@ -61,10 +66,13 @@ def ParseAA(self): ex = int(cx) + radius * math.cos(end_angle) ey = int(cy) + radius * math.sin(end_angle) - if int(a) > 0: d = 1 - else: d = -1 + if int(a) > 0: + d = 1 + else: + d = -1 - self.add_arc(ex * self.units_to_mm, ey * self.units_to_mm, i = int(-sdx) * self.units_to_mm, j = int(-sdy) * self.units_to_mm, d = d) + self.add_arc(ex * self.units_to_mm, ey * self.units_to_mm, i=int(-sdx) + * self.units_to_mm, j=int(-sdy) * self.units_to_mm, d=d) self.end_path() self.up = False self.x = int(ex) @@ -76,15 +84,14 @@ def ParseFromFirstLetter(self, c): if self.line_index < self.line_length: c1 = self.line[self.line_index] self.parse_word += c1 - if c1 == 'U': # PU + if c1 == 'U': # PU self.ParsePuOrPd(True) - elif c1 == 'D': # PD + elif c1 == 'D': # PD self.ParsePuOrPd(False) elif c == 'A': self.line_index = self.line_index + 1 if self.line_index < self.line_length: c1 = self.line[self.line_index] self.parse_word += c1 - if c1 == 'A': # AA, arc absolute + if c1 == 'A': # AA, arc absolute self.ParseAA() - diff --git a/scripts/addons/cam/nc/hpgl2dv.py b/scripts/addons/cam/nc/hpgl2dv.py index 717d9f970..1f1be7942 100644 --- a/scripts/addons/cam/nc/hpgl2dv.py +++ b/scripts/addons/cam/nc/hpgl2dv.py @@ -9,14 +9,16 @@ from . import nc from . import hpgl2d + class Creator(hpgl2d.Creator): def __init__(self): hpgl2d.Creator.__init__(self) def imperial(self): - self.units_to_mc_units = 101.6 # multiplier from inches to machine units + self.units_to_mc_units = 101.6 # multiplier from inches to machine units def metric(self): - self.units_to_mc_units = 4 # multiplier from mm to machine units + self.units_to_mc_units = 4 # multiplier from mm to machine units + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/hpgl2dv_read.py b/scripts/addons/cam/nc/hpgl2dv_read.py index def61e0ff..a4b8d13b3 100644 --- a/scripts/addons/cam/nc/hpgl2dv_read.py +++ b/scripts/addons/cam/nc/hpgl2dv_read.py @@ -3,6 +3,7 @@ # same as hpgl2d, but with 0.25mm units, instead of 0.01mm + class Parser(hpgl.Parser): def __init__(self, writer): hpgl.ParserHgpl2d.__init__(self, writer) diff --git a/scripts/addons/cam/nc/hpgl3d.py b/scripts/addons/cam/nc/hpgl3d.py index 42948bc72..8f8f8564f 100644 --- a/scripts/addons/cam/nc/hpgl3d.py +++ b/scripts/addons/cam/nc/hpgl3d.py @@ -8,11 +8,12 @@ from . import hpgl2d import math + class Creator(hpgl2d.Creator): def __init__(self): hpgl2d.Creator.__init__(self) self.z = int(0) - self.metric() # set self.units_to_mc_units + self.metric() # set self.units_to_mc_units self.doing_rapid = True def program_begin(self, id, name=''): @@ -42,7 +43,8 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): # for now, do all rapid moves at V50 ( 50 mm/s ) mx, my, mz = self.get_machine_xyz(x, y, z) if mx != self.x or my != self.y or mz != self.z: - if self.doing_rapid == False: self.write('V50.0;') + if self.doing_rapid == False: + self.write('V50.0;') self.write(('Z%i' % mx) + (',%i' % my) + (',%i;\n' % mz)) self.x = mx self.y = my @@ -54,11 +56,13 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): # for now, do all feed moves at V10 ( 10 mm/s ) mx, my, mz = self.get_machine_xyz(x, y, z) if mx != self.x or my != self.y or mz != self.z: - if self.doing_rapid == True: self.write('V10.0;') + if self.doing_rapid == True: + self.write('V10.0;') self.write(('Z%i' % mx) + (',%i' % my) + (',%i;\n' % mz)) self.x = mx self.y = my self.z = mz self.doing_rapid = False + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/hpgl3d_read.py b/scripts/addons/cam/nc/hpgl3d_read.py index 646fb883b..732689462 100644 --- a/scripts/addons/cam/nc/hpgl3d_read.py +++ b/scripts/addons/cam/nc/hpgl3d_read.py @@ -2,6 +2,7 @@ import sys import math + class Parser(num_reader.NumReader): def __init__(self, writer): @@ -27,11 +28,14 @@ def ParseZ(self): if len(y) > 0: z = self.get_number() if len(z) > 0: - if self.f > 40: color = "rapid" - else: color = "feed" + if self.f > 40: + color = "rapid" + else: + color = "feed" self.add_word(color) self.begin_path(color) - self.add_line(int(x) * self.units_to_mm, int(y) * self.units_to_mm, int(z) * self.units_to_mm) + self.add_line(int(x) * self.units_to_mm, int(y) * + self.units_to_mm, int(z) * self.units_to_mm) self.end_path() self.x = int(x) self.y = int(y) @@ -42,4 +46,3 @@ def ParseFromFirstLetter(self, c): self.ParseZ() elif c == 'V': self.ParseV() - diff --git a/scripts/addons/cam/nc/hxml_writer.py b/scripts/addons/cam/nc/hxml_writer.py index 455f82c21..39ad2fb97 100644 --- a/scripts/addons/cam/nc/hxml_writer.py +++ b/scripts/addons/cam/nc/hxml_writer.py @@ -1,5 +1,6 @@ import tempfile + class HxmlWriter: def __init__(self): self.file_out = open(tempfile.gettempdir()+'/backplot.xml', 'w') @@ -26,25 +27,32 @@ def add_text(self, s, col, cdata): s.replace('"', '"') s.replace('<', '<') s.replace('>', '>') - if (cdata) : (cd1, cd2) = ('') - else : (cd1, cd2) = ('', '') - if (col != None) : self.file_out.write('\t\t'+cd1+s+cd2+'\n') - else : self.file_out.write('\t\t'+cd1+s+cd2+'\n') + if (cdata): + (cd1, cd2) = ('') + else: + (cd1, cd2) = ('', '') + if (col != None): + self.file_out.write('\t\t'+cd1+s+cd2+'\n') + else: + self.file_out.write('\t\t'+cd1+s+cd2+'\n') def set_mode(self, units): self.file_out.write('\t\t\n') def metric(self): - self.set_mode(units = 1.0) + self.set_mode(units=1.0) def imperial(self): - self.set_mode(units = 25.4) + self.set_mode(units=25.4) def begin_path(self, col): - if (col != None) : self.file_out.write('\t\t\n') - else : self.file_out.write('\t\t\n') + if (col != None): + self.file_out.write('\t\t\n') + else: + self.file_out.write('\t\t\n') def end_path(self): self.file_out.write('\t\t\n') @@ -71,7 +79,7 @@ def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): def tool_change(self, id): self.file_out.write('\t\t\n') @@ -81,36 +89,50 @@ def spindle(self, s, clockwise): def feedrate(self, f): pass - def add_line(self, x, y, z, a = None, b = None, c = None): + def add_line(self, x, y, z, a=None, b=None, c=None): self.file_out.write('\t\t\t\n') - if x != None: self.oldx = x - if y != None: self.oldy = y - if z != None: self.oldz = z - - def add_arc(self, x, y, z, i, j, k, r = None, d = None): + if x != None: + self.oldx = x + if y != None: + self.oldy = y + if z != None: + self.oldz = z + + def add_arc(self, x, y, z, i, j, k, r=None, d=None): self.file_out.write('\t\t\t\n') - if x != None: self.oldx = x - if y != None: self.oldy = y - if z != None: self.oldz = z + if x != None: + self.oldx = x + if y != None: + self.oldy = y + if z != None: + self.oldz = z diff --git a/scripts/addons/cam/nc/iso.py b/scripts/addons/cam/nc/iso.py index 32701e1f0..5b115b93f 100644 --- a/scripts/addons/cam/nc/iso.py +++ b/scripts/addons/cam/nc/iso.py @@ -11,1328 +11,1404 @@ from .format import * ################################################################################ + + class Creator(nc.Creator): - def __init__(self): - nc.Creator.__init__(self) - - # internal variables - self.a = 0 - self.b = 0 - self.c = 0 - self.f = Address('F', fmt = Format(number_of_decimal_places = 2)) - self.fh = None - self.fv = None - self.fhv = False - self.g_plane = Address('G', fmt = Format(number_of_decimal_places = 0)) - self.g_list = [] - self.i = 0 - self.j = 0 - self.k = 0 - self.m = [] - self.r = 0 - self.s = AddressPlusMinus('S', fmt = Format(number_of_decimal_places = 2), modal = False) - self.t = None - self.x = None - self.y = None - self.z = None - self.g0123_modal = False - self.drill_modal = False - self.prev_f = '' - self.prev_g0123 = '' - self.prev_drill = '' - self.prev_retract = '' - self.prev_z = '' - self.useCrc = False - self.useCrcCenterline = False - self.gCRC = '' - self.fmt = Format() - self.absolute_flag = True - self.ffmt = Format(number_of_decimal_places = 2) - self.sfmt = Format(number_of_decimal_places = 1) - self.in_quadrant_splitting = False - self.in_canned_cycle = False - self.first_drill_pos = True - self.shift_x = 0.0 - self.shift_y = 0.0 - self.shift_z = 0.0 - self.start_of_line = False - self.internal_coolant_on = None - self.g98_not_g99 = None # True for G98 ouput, False for G99 output - self.current_fixture = None - self.fixture_wanted = '54' - self.move_done_since_tool_change = False - self.tool_defn_params = {} - self.program_id = None - self.current_sub_id = None - self.subroutine_files = [] - self.program_name = None - self.temp_file_to_append_on_close = None - self.fixture_order = ['54', '55', '56', '57', '58', '59'] - for i in range(1, 50): - self.fixture_order.append('54.' + str(i)) - self.output_disabled = False - self.z_for_g43 = None - - # optional settings - self.arc_centre_absolute = False - self.arc_centre_positive = False - self.drillExpanded = False - self.dwell_allowed_in_G83 = False - self.can_do_helical_arcs = True - self.z_for_g53 = None # set this to a value to output G53 Zvalue in tool change and at program end - self.output_h_and_d_at_tool_change = False - self.output_block_numbers = True - self.start_block_number = 10 - self.block_number_increment = 10 - self.block_number_restart_after = None - self.output_tool_definitions = True - self.output_tool_change = True - self.output_g43_on_tool_change_line = False - self.output_internal_coolant_commands = False - self.output_g98_and_g99 = True - self.output_g43_z_before_drilling_if_g98 = False - self.output_cutviewer_comments = False - self.output_fixtures = False - self.use_this_program_id = None - self.subroutines_in_own_files = False - self.pattern_done_with_subroutine = False - self.output_comment_before_tool_change = True - self.output_arcs_as_lines = False - self.m_codes_on_their_own_line = False - - ############################################################################ - ## Codes - - def SPACE_STR(self): return '' - def SPACE(self): - if self.start_of_line == True: - self.start_of_line = False - return '' - else: - return self.SPACE_STR() - - def FORMAT_FEEDRATE(self): return('%.2f') - def FORMAT_ANG(self): return('%.1f') - def FORMAT_TIME(self): return self.fmt - - def BLOCK(self): return('N%i') - def COMMENT(self,comment): return( ('(%s)' % comment ) ) - def VARIABLE(self): return( '#%i') - def VARIABLE_SET(self): return( '=%.3f') - - def PROGRAM(self): return( 'O%i') - def PROGRAM_END(self): return( 'M02') - - def SUBPROG_CALL(self): return( 'M98' + self.SPACE() + 'P%i') - def SUBPROG_END(self): return( 'M99') - - def STOP_OPTIONAL(self): return('M01') - def STOP(self): return('M00') - - def IMPERIAL(self): return('G20') - def METRIC(self): return('G21') - def ABSOLUTE(self): return('G90') - def INCREMENTAL(self): return('G91') - def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92') - def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1') - def POLAR_ON(self): return('G16') - def POLAR_OFF(self): return('G15') - def PLANE_XY(self): return('17') - def PLANE_XZ(self): return('18') - def PLANE_YZ(self): return('19') - - def TOOL(self): return('T%i' + self.SPACE() + 'M06') - def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1') - - def WORKPLANE(self): return('G%i') - def WORKPLANE_BASE(self): return(53) - - def SPINDLE_CW(self): return('M03') - def SPINDLE_CCW(self): return('M04') - def COOLANT_OFF(self): return('M09') - def COOLANT_MIST(self): return('M07') - def COOLANT_FLOOD(self): return('M08') - def GEAR_OFF(self): return('?') - def GEAR(self): return('M%i') - def GEAR_BASE(self): return(37) - - def RAPID(self): return('G00') - def FEED(self): return('G01') - def ARC_CW(self): return('G02') - def ARC_CCW(self): return('G03') - def DWELL(self, dwell): return('G04' + self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell))) - def DRILL(self): return('G81') - def DRILL_WITH_DWELL(self, dwell): return('G82' + self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell))) - def PECK_DRILL(self): return('G83') - def PECK_DEPTH(self, depth): return('Q' + (self.fmt.string(depth))) - def RETRACT(self, height): return('R' + (self.fmt.string(height))) - def END_CANNED_CYCLE(self): return('G80') - def TAP(self): return('G84') - def TAP_DEPTH(self, depth): return('K' + (self.fmt.string(depth))) - def INTERNAL_COOLANT_ON(self): return('M18') - def INTERNAL_COOLANT_OFF(self): return('M9') - - def X(self): return('X') - def Y(self): return('Y') - def Z(self): return('Z') - def A(self): return('A') - def B(self): return('B') - def C(self): return('C') - def CENTRE_X(self): return('I') - def CENTRE_Y(self): return('J') - def CENTRE_Z(self): return('K') - def RADIUS(self): return('R') - def TIME(self): return('P') - - def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2') - def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3') - def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4') - def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5') - - def MACHINE_COORDINATES(self): return('G53') - - def EXACT_PATH_MODE(self): return('G61') - def EXACT_STOP_MODE(self): return('G61.1') - - def RETRACT_TO_CLEARANCE(self): return('G98') - def RETRACT_TO_STANDOFF(self): return('G99') - - ############################################################################ - ## Internals - def write(self, s): - if self.output_disabled == False: - nc.Creator.write(self, s) - if '\n' in s: - self.start_of_line = s[-1] == '\n' - - def write_feedrate(self): - self.f.write(self) - - def write_preps(self): - i = 0 - if self.g_plane.str: - i += 1 - self.g_plane.write(self) - for g in self.g_list: - if i > 0: - self.write('\n' if self.m_codes_on_their_own_line else self.SPACE()) - self.write(g) - i += 1 - self.g_list = [] - - def write_misc(self): - if (len(self.m)): - self.write('\n' if self.m_codes_on_their_own_line else self.SPACE()) - self.write(self.m.pop()) - - def write_spindle(self): - if self.s.str: - self.write('\n' if self.m_codes_on_their_own_line else '') - self.s.write(self) - - def output_fixture(self): - if self.current_fixture != self.fixture_wanted: - self.current_fixture = self.fixture_wanted - self.g_list.append('G' + str(self.current_fixture)) - - def increment_fixture(self): - for i in range(0, len(self.fixture_order) - 1): - if self.fixture_order[i] == self.fixture_wanted: - self.fixture_wanted = self.fixture_order[i+1] - return - raise 'too many fixtures wanted!' - - def get_fixture(self): - return self.fixture_wanted - - def set_fixture(self, fixture): - self.fixture_wanted = fixture - - ############################################################################ - ## Programs - - def program_begin(self, id, name=''): - if self.use_this_program_id: - id = self.use_this_program_id - if self.PROGRAM() != None: - self.writem([(self.PROGRAM() % id), self.SPACE(), (self.COMMENT(name))]) - self.write('\n') - self.program_id = id - self.program_name = name - - def add_stock(self, type_name, params): - if self.output_cutviewer_comments: - self.write("(STOCK/" + type_name) - for param in params: - self.write(",") - self.write(str(param)) - self.write(")\n") - - def program_stop(self, optional=False): - if (optional) : - self.write(self.SPACE() + self.STOP_OPTIONAL() + '\n') - self.prev_g0123 = '' - else : - self.write(self.STOP() + '\n') - self.prev_g0123 = '' - - def number_file(self, filename): - import tempfile - temp_filename = tempfile.gettempdir()+'/renumbering.txt' - - # make a copy of file - f_in = open(filename, 'r') - f_out = open(temp_filename, 'w') - while (True): - line = f_in.readline() - if (len(line) == 0) : break - f_out.write(line) - f_in.close() - f_out.close() - - # read copy - f_in = open(temp_filename, 'r') - f_out = open(filename, 'w') - n = self.start_block_number - while (True): - line = f_in.readline() - - if (len(line) == 0) : break - f_out.write(self.BLOCK() % n + self.SPACE() + line) - n += self.block_number_increment - if self.block_number_restart_after != None: - if n >= self.block_number_restart_after: - n = self.start_block_number - f_in.close() - f_out.close() - - def program_end(self): - if self.z_for_g53 != None: - self.write(self.SPACE() + self.MACHINE_COORDINATES() + self.SPACE() + 'Z' + self.fmt.string(self.z_for_g53) + '\n') - self.write(self.SPACE() + self.PROGRAM_END() + '\n') - - if self.temp_file_to_append_on_close != None: - f_in = open(self.temp_file_to_append_on_close, 'r') - while (True): - line = f_in.readline() - if (len(line) == 0) : break - self.write(line) - f_in.close() - - self.file_close() - - if self.output_block_numbers: - # number every line of the file afterwards - self.number_file(self.filename) - - for f in self.subroutine_files: - self.number_file(f) - - def flush_nc(self): - if len(self.g_list) == 0 and len(self.m) == 0: return - self.write_preps() - self.write_misc() - self.write('\n') - - ############################################################################ - ## Subprograms - - def make_subroutine_name(self, id): - s = self.filename - for i in reversed(range(0, len(s))): - if s[i] == '.': - return s[0:i] + 'sub' + str(id) + s[i:] - - # '.' not found - return s + 'sub' + str(id) - - def sub_begin(self, id, name=None): - if id == None: - if self.current_sub_id == None: - self.current_sub_id = self.program_id - self.current_sub_id += 1 - id = self.current_sub_id - - if name == None: - name = self.program_name + ' subroutine ' + str(id) - - self.save_file = self.file - if self.subroutines_in_own_files: - new_name = self.make_subroutine_name(id) - self.file = open(new_name, 'w') - self.subroutine_files.append(new_name) - else: - ## use temporary file - import tempfile - temp_filename = tempfile.gettempdir()+'/subroutines.txt' - if self.temp_file_to_append_on_close == None: - self.temp_file_to_append_on_close = temp_filename - self.file = open(temp_filename, 'w') - else: - self.file = open(temp_filename, 'a') - - if self.PROGRAM() != None: - self.write((self.PROGRAM() % id) + self.SPACE() + (self.COMMENT(name))) - self.write('\n') - - def sub_call(self, id): - if id == None: - id = self.current_sub_id - self.write(self.SPACE() + (self.SUBPROG_CALL() % id) + '\n') - - def sub_end(self): - self.write(self.SPACE() + self.SUBPROG_END() + '\n') - - self.file.close() - self.file = self.save_file - - def disable_output(self): - self.output_disabled = True - - def enable_output(self): - self.output_disabled = False - - ############################################################################ - ## Settings - - def imperial(self): - self.g_list.append(self.IMPERIAL()) - self.fmt.number_of_decimal_places = 4 - - def metric(self): - self.g_list.append(self.METRIC()) - self.fmt.number_of_decimal_places = 3 - - def absolute(self): - self.g_list.append(self.ABSOLUTE()) - self.absolute_flag = True - - def incremental(self): - self.g_list.append(self.INCREMENTAL()) - self.absolute_flag = False - - def polar(self, on=True): - if (on) : self.g_list.append(self.POLAR_ON()) - else : self.g_list.append(self.POLAR_OFF()) - - def set_plane(self, plane): - if (plane == 0) : self.g_plane.set(self.PLANE_XY()) - elif (plane == 1) : self.g_plane.set(self.PLANE_XZ()) - elif (plane == 2) : self.g_plane.set(self.PLANE_YZ()) - - def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): - self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM())) - if (x != None): self.write( self.SPACE() + 'X ' + (self.fmt.string(x + self.shift_x)) ) - if (y != None): self.write( self.SPACE() + 'Y ' + (self.fmt.string(y + self.shift_y)) ) - if (z != None): self.write( self.SPACE() + 'Z ' + (self.fmt.string(z + self.shift_z)) ) - if (a != None): self.write( self.SPACE() + 'A ' + (self.fmt.string(a)) ) - if (b != None): self.write( self.SPACE() + 'B ' + (self.fmt.string(b)) ) - if (c != None): self.write( self.SPACE() + 'C ' + (self.fmt.string(c)) ) - self.write('\n') - - def remove_temporary_origin(self): - self.write(self.SPACE() + (self.REMOVE_TEMPORARY_COORDINATE_SYSTEM())) - self.write('\n') - ############################################################################ - ## new graphics origin- make a new coordinate system and snap it onto the geometry - ## the toolpath generated should be translated - def translate(self,x=None, y=None, z=None): - self.shift_x = -x - self.shift_y = -y - self.shift_z = -z - - ############################################################################ - ## Tools - - def tool_change(self, id): - if self.output_comment_before_tool_change: - if id in self.tool_defn_params: - self.comment('tool change to ' + self.tool_defn_params[id]['name']); - - if self.output_cutviewer_comments: - import cutviewer - if id in self.tool_defn_params: - cutviewer.tool_defn(self, id, self.tool_defn_params[id]) - if (self.t != None) and (self.z_for_g53 != None): - self.write('G53 Z' + str(self.z_for_g53) + '\n') - self.write(self.SPACE() + (self.TOOL() % id)) - if self.output_g43_on_tool_change_line == True: - self.write(self.SPACE() + 'G43') - self.write('\n') - if self.output_h_and_d_at_tool_change == True: - if self.output_g43_on_tool_change_line == False: - self.write(self.SPACE() + 'G43') - self.write(self.SPACE() + 'D' + str(id) + self.SPACE() + 'H' + str(id) + '\n') - self.t = id - self.move_done_since_tool_change = False - - def tool_defn(self, id, name='',params=None): - self.tool_defn_params[id] = params - if self.output_tool_definitions: - self.write(self.SPACE() + self.TOOL_DEFINITION()) - self.write(self.SPACE() + ('P%i' % id) + ' ') - - if (params['diameter'] != None): - self.write(self.SPACE() + ('R%.3f' % (float(params['diameter'])/2))) - - if (params['cutting edge height'] != None): - self.write(self.SPACE() + 'Z%.3f' % float(params['cutting edge height'])) - - self.write('\n') - - def offset_radius(self, id, radius=None): - pass - - def offset_length(self, id, length=None): - pass - - def current_tool(self): - return self.t - - - ############################################################################ - ## Datums - - def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 - # These are selected by values from 1 to 9 inclusive. - def workplane(self, id): - if ((id >= 1) and (id <= 6)): - self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) - if ((id >= 7) and (id <= 9)): - self.g_list.append(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) - - - ############################################################################ - ## Rates + Modes - - def feedrate(self, f): - self.f.set(f) - self.fhv = False - - def feedrate_hv(self, fh, fv): - self.fh = fh - self.fv = fv - self.fhv = True - - def calc_feedrate_hv(self, h, v): - if math.fabs(v) > math.fabs(h * 2): - # some horizontal, so it should be fine to use the horizontal feed rate - self.f.set(self.fv) - else: - # not much, if any horizontal component, so use the vertical feed rate - self.f.set(self.fh) - - def spindle(self, s, clockwise): - if clockwise == True: - self.s.set(s, self.SPINDLE_CW(), self.SPINDLE_CCW()) - else: - self.s.set(s, self.SPINDLE_CCW(), self.SPINDLE_CW()) - - def coolant(self, mode=0): - if (mode <= 0) : self.m.append(self.COOLANT_OFF()) - elif (mode == 1) : self.m.append(self.COOLANT_MIST()) - elif (mode == 2) : self.m.append(self.COOLANT_FLOOD()) - - def gearrange(self, gear=0): - if (gear <= 0) : self.m.append(self.GEAR_OFF()) - elif (gear <= 4) : self.m.append(self.GEAR() % (gear + GEAR_BASE())) - - ############################################################################ - ## Moves - - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ): - (x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z, a, b, c) - if axis_count==0: return - self.on_move() - - if self.g0123_modal: - if self.prev_g0123 != self.RAPID(): - self.write(self.SPACE() + self.RAPID()) - self.prev_g0123 = self.RAPID() - else: - self.write(self.SPACE() + self.RAPID()) - self.write_preps() - if (x != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - else: - dx = x - self.x - self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) - self.x = x - if (y != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - else: - dy = y - self.y - self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) - - self.y = y - if (z != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) - else: - dz = z - self.z - self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) - - self.z = z - - if (a != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.A() + (self.fmt.string(a))) - else: - da = a - self.a - self.write(self.SPACE() + self.A() + (self.fmt.string(da))) - self.a = a - - if (b != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.B() + (self.fmt.string(b))) - else: - db = b - self.b - self.write(self.SPACE() + self.B() + (self.fmt.string(db))) - self.b = b - - if (c != None): - if (self.absolute_flag ): - self.write(self.SPACE() + self.C() + (self.fmt.string(c))) - else: - dc = c - self.c - self.write(self.SPACE() + self.C() + (self.fmt.string(dc))) - self.c = c - self.write_spindle() - self.write_misc() - self.write('\n') - - def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - (x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z, a, b, c) - if axis_count==0: return - self.on_move() - if self.g0123_modal: - if self.prev_g0123 != self.FEED(): - self.writem([self.SPACE() , self.FEED()]) - self.prev_g0123 = self.FEED() - else: - self.write(self.SPACE() + self.FEED()) - self.write_preps() - dx = dy = dz = 0 - if (x != None): - dx = x - self.x - if (self.absolute_flag ): - self.writem([self.SPACE() , self.X() , (self.fmt.string(x + self.shift_x))]) - else: - self.writem([self.SPACE() , self.X() , (self.fmt.string(dx))]) - self.x = x - if (y != None): - dy = y - self.y - if (self.absolute_flag ): - self.writem([self.SPACE() , self.Y() , (self.fmt.string(y + self.shift_y))]) - else: - self.writem([self.SPACE() , self.Y() , (self.fmt.string(dy))]) - - self.y = y - if (z != None): - dz = z - self.z - if (self.absolute_flag ): - self.writem([self.SPACE() , self.Z() , (self.fmt.string(z + self.shift_z))]) - else: - self.writem([self.SPACE() , self.Z() , (self.fmt.string(dz))]) - - self.z = z - - if (a != None): - da = a - self.a - if (self.absolute_flag ): - self.writem([self.SPACE() , self.A() , (self.fmt.string(a))]) - else: - self.writem([self.SPACE() , self.A() , (self.fmt.string(da))]) - self.a = a - - if (b != None): - db = b - self.b - if (self.absolute_flag ): - self.writem([self.SPACE() , self.B() , (self.fmt.string(b))]) - else: - self.writem([self.SPACE() , self.B() , (self.fmt.string(db))]) - self.b = b - - if (c != None): - dc = c - self.c - if (self.absolute_flag ): - self.writem([self.SPACE() , self.C() , (self.fmt.string(c))]) - else: - self.writem([self.SPACE() , self.C() , (self.fmt.string(dc))]) - self.c = c - - if (self.fhv) : self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - - def filter_xyz(self, x=None, y=None, z=None, a=None, b=None, c=None): - """ Check if x,y,z,a,b,c are the same and set them to None if they are - return value = (x,y,z,a,b,c,count) where count is the number of - axis moves left. - """ - rv = [x,y,z,a,b,c,0] - comparisons = ((x,self.shift_x,self.x),(y,self.shift_y,self.y),(z,self.shift_z,self.z), - (a,0,self.a),(b,0,self.b),(c,0,self.c)) - - for i,(new_val,shift,current_val) in enumerate(comparisons): - if new_val is not None: - if self.fmt.string(new_val+shift) == self.fmt.string(current_val): - rv[i]=None - else: - rv[6]+=1 - return rv - - - def get_quadrant(self, dx, dy): - if dx < 0: - if dy < 0: - return 2 - else: - return 1 - - else: - if dy < 0: - return 3 - else: - return 0 - - def quadrant_start(self, q, i, j, rad): - while q > 3: q = q - 4 - if q == 0: - return i + rad, j - if q == 1: - return i, j + rad - if q == 2: - return i - rad, j - return i, j - rad - - def quadrant_end(self, q, i, j, rad): - return self.quadrant_start(q + 1, i, j, rad) - - def get_arc_angle(self, sdx, sdy, edx, edy, cw): - angle_s = math.atan2(sdy, sdx); - angle_e = math.atan2(edy, edx); - if cw: - if angle_s < angle_e: angle_s = angle_s + 2 * math.pi - else: - if angle_e < angle_s: angle_e = angle_e + 2 * math.pi - return angle_e - angle_s - - def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - (x,y,z,_,_,_,axis_count) = self.filter_xyz(x,y,z) - if axis_count==0: return - - if self.output_arcs_as_lines or (self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z))): - # split the helical arc into little line feed moves - - if x == None: x = self.x - if y == None: y = self.y - sdx = self.x - i - sdy = self.y - j - edx = x - i - edy = y - j - radius = math.sqrt(sdx*sdx + sdy*sdy) - arc_angle = self.get_arc_angle(sdx, sdy, edx, edy, cw) - angle_start = math.atan2(sdy, sdx); - tolerance = 0.02 - angle_step = 2.0 * math.atan( math.sqrt ( tolerance /(radius - tolerance) )) - segments = int(math.fabs(arc_angle / angle_step) + 1) - angle_step = arc_angle / segments - angle = angle_start - if z != None: - z_step = float(z - self.z)/segments - next_z = self.z - - for p in range(0, segments): - angle = angle + angle_step - next_x = i + radius * math.cos(angle) - next_y = j + radius * math.sin(angle) - if z == None: - next_z = None - else: - next_z = next_z + z_step - self.feed(next_x, next_y, next_z) - return - - if self.arc_centre_positive == True and self.in_quadrant_splitting == False: - # split in to quadrant arcs - self.in_quadrant_splitting = True - - if x == None: x = self.x - if y == None: y = self.y - sdx = self.x - i - sdy = self.y - j - edx = x - i - edy = y - j - - qs = self.get_quadrant(sdx, sdy) - qe = self.get_quadrant(edx, edy) - - if qs == qe: - arc_angle = math.fabs(self.get_arc_angle(sdx, sdy, edx, edy, cw)) - # arc_angle will be either less than pi/2 or greater than 3pi/2 - if arc_angle > 3.14: - if cw: - qs = qs + 4 - else: - qe = qe + 4 - - if qs == qe: - self.arc(cw, x, y, z, i, j, k, r) - else: - rad = math.sqrt(sdx * sdx + sdy * sdy) - if cw: - if qs < qe: qs = qs + 4 - else: - if qe < qs: qe = qe + 4 - - q = qs - while 1: - x1 = x - y1 = y - if q != qe: - if cw: - x1, y1 = self.quadrant_start(q, i, j, rad) - else: - x1, y1 = self.quadrant_end(q, i, j, rad) - - if (self.fmt.string(x1) != self.fmt.string(self.x)) or (self.fmt.string(y1) != self.fmt.string(self.y)): - if (math.fabs(x1 - self.x) > 0.01) or (math.fabs(y1 - self.y) > 0.01): - self.arc(cw, x1, y1, z, i, j, k, r) - else: - self.feed(x1, y1, z) - if q == qe: - break - if cw: - q = q - 1 - else: - q = q + 1 - - self.in_quadrant_splitting = False - return - - self.on_move() - arc_g_code = '' - if cw: arc_g_code = self.ARC_CW() - else: arc_g_code = self.ARC_CCW() - if self.g0123_modal: - if self.prev_g0123 != arc_g_code: - self.write(self.SPACE() + arc_g_code) - self.prev_g0123 = arc_g_code - else: - self.write(self.SPACE() + arc_g_code) - self.write_preps() - if (x != None): - dx = x - self.x - if (self.absolute_flag ): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - else: - self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) - if (y != None): - dy = y - self.y - if (self.absolute_flag ): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - else: - self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) - if (z != None): - dz = z - self.z - if (self.absolute_flag ): - self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) - if (i != None): - if self.arc_centre_absolute == False: - i = i - self.x - s = self.fmt.string(i) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_X() + s) - if (j != None): - if self.arc_centre_absolute == False: - j = j - self.y - s = self.fmt.string(j) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_Y() + s) - if (k != None): - if self.arc_centre_absolute == False: - k = k - self.z - s = self.fmt.string(k) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.CENTRE_Z() + s) - if (r != None): - s = self.fmt.string(r) - if self.arc_centre_positive == True: - if s[0] == '-': - s = s[1:] - self.write(self.SPACE() + self.RADIUS() + s) + def __init__(self): + nc.Creator.__init__(self) + + # internal variables + self.a = 0 + self.b = 0 + self.c = 0 + self.f = Address('F', fmt=Format(number_of_decimal_places=2)) + self.fh = None + self.fv = None + self.fhv = False + self.g_plane = Address('G', fmt=Format(number_of_decimal_places=0)) + self.g_list = [] + self.i = 0 + self.j = 0 + self.k = 0 + self.m = [] + self.r = 0 + self.s = AddressPlusMinus('S', fmt=Format(number_of_decimal_places=2), modal=False) + self.t = None + self.x = None + self.y = None + self.z = None + self.g0123_modal = False + self.drill_modal = False + self.prev_f = '' + self.prev_g0123 = '' + self.prev_drill = '' + self.prev_retract = '' + self.prev_z = '' + self.useCrc = False + self.useCrcCenterline = False + self.gCRC = '' + self.fmt = Format() + self.absolute_flag = True + self.ffmt = Format(number_of_decimal_places=2) + self.sfmt = Format(number_of_decimal_places=1) + self.in_quadrant_splitting = False + self.in_canned_cycle = False + self.first_drill_pos = True + self.shift_x = 0.0 + self.shift_y = 0.0 + self.shift_z = 0.0 + self.start_of_line = False + self.internal_coolant_on = None + self.g98_not_g99 = None # True for G98 ouput, False for G99 output + self.current_fixture = None + self.fixture_wanted = '54' + self.move_done_since_tool_change = False + self.tool_defn_params = {} + self.program_id = None + self.current_sub_id = None + self.subroutine_files = [] + self.program_name = None + self.temp_file_to_append_on_close = None + self.fixture_order = ['54', '55', '56', '57', '58', '59'] + for i in range(1, 50): + self.fixture_order.append('54.' + str(i)) + self.output_disabled = False + self.z_for_g43 = None + + # optional settings + self.arc_centre_absolute = False + self.arc_centre_positive = False + self.drillExpanded = False + self.dwell_allowed_in_G83 = False + self.can_do_helical_arcs = True + self.z_for_g53 = None # set this to a value to output G53 Zvalue in tool change and at program end + self.output_h_and_d_at_tool_change = False + self.output_block_numbers = True + self.start_block_number = 10 + self.block_number_increment = 10 + self.block_number_restart_after = None + self.output_tool_definitions = True + self.output_tool_change = True + self.output_g43_on_tool_change_line = False + self.output_internal_coolant_commands = False + self.output_g98_and_g99 = True + self.output_g43_z_before_drilling_if_g98 = False + self.output_cutviewer_comments = False + self.output_fixtures = False + self.use_this_program_id = None + self.subroutines_in_own_files = False + self.pattern_done_with_subroutine = False + self.output_comment_before_tool_change = True + self.output_arcs_as_lines = False + self.m_codes_on_their_own_line = False + + ############################################################################ + # Codes + + def SPACE_STR(self): return '' + + def SPACE(self): + if self.start_of_line == True: + self.start_of_line = False + return '' + else: + return self.SPACE_STR() + + def FORMAT_FEEDRATE(self): return('%.2f') + def FORMAT_ANG(self): return('%.1f') + def FORMAT_TIME(self): return self.fmt + + def BLOCK(self): return('N%i') + def COMMENT(self, comment): return(('(%s)' % comment)) + def VARIABLE(self): return('#%i') + def VARIABLE_SET(self): return('=%.3f') + + def PROGRAM(self): return('O%i') + def PROGRAM_END(self): return('M02') + + def SUBPROG_CALL(self): return('M98' + self.SPACE() + 'P%i') + def SUBPROG_END(self): return('M99') + + def STOP_OPTIONAL(self): return('M01') + def STOP(self): return('M00') + + def IMPERIAL(self): return('G20') + def METRIC(self): return('G21') + def ABSOLUTE(self): return('G90') + def INCREMENTAL(self): return('G91') + def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92') + def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1') + def POLAR_ON(self): return('G16') + def POLAR_OFF(self): return('G15') + def PLANE_XY(self): return('17') + def PLANE_XZ(self): return('18') + def PLANE_YZ(self): return('19') + + def TOOL(self): return('T%i' + self.SPACE() + 'M06') + def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1') + + def WORKPLANE(self): return('G%i') + def WORKPLANE_BASE(self): return(53) + + def SPINDLE_CW(self): return('M03') + def SPINDLE_CCW(self): return('M04') + def COOLANT_OFF(self): return('M09') + def COOLANT_MIST(self): return('M07') + def COOLANT_FLOOD(self): return('M08') + def GEAR_OFF(self): return('?') + def GEAR(self): return('M%i') + def GEAR_BASE(self): return(37) + + def RAPID(self): return('G00') + def FEED(self): return('G01') + def ARC_CW(self): return('G02') + def ARC_CCW(self): return('G03') + def DWELL(self, dwell): return('G04' + self.SPACE() + + self.TIME() + (self.FORMAT_TIME().string(dwell))) + + def DRILL(self): return('G81') + def DRILL_WITH_DWELL(self, dwell): return('G82' + self.SPACE() + + self.TIME() + (self.FORMAT_TIME().string(dwell))) + + def PECK_DRILL(self): return('G83') + def PECK_DEPTH(self, depth): return('Q' + (self.fmt.string(depth))) + def RETRACT(self, height): return('R' + (self.fmt.string(height))) + def END_CANNED_CYCLE(self): return('G80') + def TAP(self): return('G84') + def TAP_DEPTH(self, depth): return('K' + (self.fmt.string(depth))) + def INTERNAL_COOLANT_ON(self): return('M18') + def INTERNAL_COOLANT_OFF(self): return('M9') + + def X(self): return('X') + def Y(self): return('Y') + def Z(self): return('Z') + def A(self): return('A') + def B(self): return('B') + def C(self): return('C') + def CENTRE_X(self): return('I') + def CENTRE_Y(self): return('J') + def CENTRE_Z(self): return('K') + def RADIUS(self): return('R') + def TIME(self): return('P') + + def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2') + def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3') + def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4') + def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5') + + def MACHINE_COORDINATES(self): return('G53') + + def EXACT_PATH_MODE(self): return('G61') + def EXACT_STOP_MODE(self): return('G61.1') + + def RETRACT_TO_CLEARANCE(self): return('G98') + def RETRACT_TO_STANDOFF(self): return('G99') + + ############################################################################ + # Internals + def write(self, s): + if self.output_disabled == False: + nc.Creator.write(self, s) + if '\n' in s: + self.start_of_line = s[-1] == '\n' + + def write_feedrate(self): + self.f.write(self) + + def write_preps(self): + i = 0 + if self.g_plane.str: + i += 1 + self.g_plane.write(self) + for g in self.g_list: + if i > 0: + self.write('\n' if self.m_codes_on_their_own_line else self.SPACE()) + self.write(g) + i += 1 + self.g_list = [] + + def write_misc(self): + if (len(self.m)): + self.write('\n' if self.m_codes_on_their_own_line else self.SPACE()) + self.write(self.m.pop()) + + def write_spindle(self): + if self.s.str: + self.write('\n' if self.m_codes_on_their_own_line else '') + self.s.write(self) + + def output_fixture(self): + if self.current_fixture != self.fixture_wanted: + self.current_fixture = self.fixture_wanted + self.g_list.append('G' + str(self.current_fixture)) + + def increment_fixture(self): + for i in range(0, len(self.fixture_order) - 1): + if self.fixture_order[i] == self.fixture_wanted: + self.fixture_wanted = self.fixture_order[i+1] + return + raise 'too many fixtures wanted!' + + def get_fixture(self): + return self.fixture_wanted + + def set_fixture(self, fixture): + self.fixture_wanted = fixture + + ############################################################################ + # Programs + + def program_begin(self, id, name=''): + if self.use_this_program_id: + id = self.use_this_program_id + if self.PROGRAM() != None: + self.writem([(self.PROGRAM() % id), self.SPACE(), (self.COMMENT(name))]) + self.write('\n') + self.program_id = id + self.program_name = name + + def add_stock(self, type_name, params): + if self.output_cutviewer_comments: + self.write("(STOCK/" + type_name) + for param in params: + self.write(",") + self.write(str(param)) + self.write(")\n") + + def program_stop(self, optional=False): + if (optional): + self.write(self.SPACE() + self.STOP_OPTIONAL() + '\n') + self.prev_g0123 = '' + else: + self.write(self.STOP() + '\n') + self.prev_g0123 = '' + + def number_file(self, filename): + import tempfile + temp_filename = tempfile.gettempdir()+'/renumbering.txt' + + # make a copy of file + f_in = open(filename, 'r') + f_out = open(temp_filename, 'w') + while (True): + line = f_in.readline() + if (len(line) == 0): + break + f_out.write(line) + f_in.close() + f_out.close() + + # read copy + f_in = open(temp_filename, 'r') + f_out = open(filename, 'w') + n = self.start_block_number + while (True): + line = f_in.readline() + + if (len(line) == 0): + break + f_out.write(self.BLOCK() % n + self.SPACE() + line) + n += self.block_number_increment + if self.block_number_restart_after != None: + if n >= self.block_number_restart_after: + n = self.start_block_number + f_in.close() + f_out.close() + + def program_end(self): + if self.z_for_g53 != None: + self.write(self.SPACE() + self.MACHINE_COORDINATES() + + self.SPACE() + 'Z' + self.fmt.string(self.z_for_g53) + '\n') + self.write(self.SPACE() + self.PROGRAM_END() + '\n') + + if self.temp_file_to_append_on_close != None: + f_in = open(self.temp_file_to_append_on_close, 'r') + while (True): + line = f_in.readline() + if (len(line) == 0): + break + self.write(line) + f_in.close() + + self.file_close() + + if self.output_block_numbers: + # number every line of the file afterwards + self.number_file(self.filename) + + for f in self.subroutine_files: + self.number_file(f) + + def flush_nc(self): + if len(self.g_list) == 0 and len(self.m) == 0: + return + self.write_preps() + self.write_misc() + self.write('\n') + + ############################################################################ + # Subprograms + + def make_subroutine_name(self, id): + s = self.filename + for i in reversed(range(0, len(s))): + if s[i] == '.': + return s[0:i] + 'sub' + str(id) + s[i:] + + # '.' not found + return s + 'sub' + str(id) + + def sub_begin(self, id, name=None): + if id == None: + if self.current_sub_id == None: + self.current_sub_id = self.program_id + self.current_sub_id += 1 + id = self.current_sub_id + + if name == None: + name = self.program_name + ' subroutine ' + str(id) + + self.save_file = self.file + if self.subroutines_in_own_files: + new_name = self.make_subroutine_name(id) + self.file = open(new_name, 'w') + self.subroutine_files.append(new_name) + else: + # use temporary file + import tempfile + temp_filename = tempfile.gettempdir()+'/subroutines.txt' + if self.temp_file_to_append_on_close == None: + self.temp_file_to_append_on_close = temp_filename + self.file = open(temp_filename, 'w') + else: + self.file = open(temp_filename, 'a') + + if self.PROGRAM() != None: + self.write((self.PROGRAM() % id) + self.SPACE() + (self.COMMENT(name))) + self.write('\n') + + def sub_call(self, id): + if id == None: + id = self.current_sub_id + self.write(self.SPACE() + (self.SUBPROG_CALL() % id) + '\n') + + def sub_end(self): + self.write(self.SPACE() + self.SUBPROG_END() + '\n') + + self.file.close() + self.file = self.save_file + + def disable_output(self): + self.output_disabled = True + + def enable_output(self): + self.output_disabled = False + + ############################################################################ + # Settings + + def imperial(self): + self.g_list.append(self.IMPERIAL()) + self.fmt.number_of_decimal_places = 4 + + def metric(self): + self.g_list.append(self.METRIC()) + self.fmt.number_of_decimal_places = 3 + + def absolute(self): + self.g_list.append(self.ABSOLUTE()) + self.absolute_flag = True + + def incremental(self): + self.g_list.append(self.INCREMENTAL()) + self.absolute_flag = False + + def polar(self, on=True): + if (on): + self.g_list.append(self.POLAR_ON()) + else: + self.g_list.append(self.POLAR_OFF()) + + def set_plane(self, plane): + if (plane == 0): + self.g_plane.set(self.PLANE_XY()) + elif (plane == 1): + self.g_plane.set(self.PLANE_XZ()) + elif (plane == 2): + self.g_plane.set(self.PLANE_YZ()) + + def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): + self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM())) + if (x != None): + self.write(self.SPACE() + 'X ' + (self.fmt.string(x + self.shift_x))) + if (y != None): + self.write(self.SPACE() + 'Y ' + (self.fmt.string(y + self.shift_y))) + if (z != None): + self.write(self.SPACE() + 'Z ' + (self.fmt.string(z + self.shift_z))) + if (a != None): + self.write(self.SPACE() + 'A ' + (self.fmt.string(a))) + if (b != None): + self.write(self.SPACE() + 'B ' + (self.fmt.string(b))) + if (c != None): + self.write(self.SPACE() + 'C ' + (self.fmt.string(c))) + self.write('\n') + + def remove_temporary_origin(self): + self.write(self.SPACE() + (self.REMOVE_TEMPORARY_COORDINATE_SYSTEM())) + self.write('\n') + ############################################################################ + # new graphics origin- make a new coordinate system and snap it onto the geometry + # the toolpath generated should be translated + + def translate(self, x=None, y=None, z=None): + self.shift_x = -x + self.shift_y = -y + self.shift_z = -z + + ############################################################################ + # Tools + + def tool_change(self, id): + if self.output_comment_before_tool_change: + if id in self.tool_defn_params: + self.comment('tool change to ' + self.tool_defn_params[id]['name']) + + if self.output_cutviewer_comments: + import cutviewer + if id in self.tool_defn_params: + cutviewer.tool_defn(self, id, self.tool_defn_params[id]) + if (self.t != None) and (self.z_for_g53 != None): + self.write('G53 Z' + str(self.z_for_g53) + '\n') + self.write(self.SPACE() + (self.TOOL() % id)) + if self.output_g43_on_tool_change_line == True: + self.write(self.SPACE() + 'G43') + self.write('\n') + if self.output_h_and_d_at_tool_change == True: + if self.output_g43_on_tool_change_line == False: + self.write(self.SPACE() + 'G43') + self.write(self.SPACE() + 'D' + str(id) + self.SPACE() + 'H' + str(id) + '\n') + self.t = id + self.move_done_since_tool_change = False + + def tool_defn(self, id, name='', params=None): + self.tool_defn_params[id] = params + if self.output_tool_definitions: + self.write(self.SPACE() + self.TOOL_DEFINITION()) + self.write(self.SPACE() + ('P%i' % id) + ' ') + + if (params['diameter'] != None): + self.write(self.SPACE() + ('R%.3f' % (float(params['diameter'])/2))) + + if (params['cutting edge height'] != None): + self.write(self.SPACE() + 'Z%.3f' % float(params['cutting edge height'])) + + self.write('\n') + + def offset_radius(self, id, radius=None): + pass + + def offset_length(self, id, length=None): + pass + + def current_tool(self): + return self.t + + ############################################################################ + # Datums + + def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 + # These are selected by values from 1 to 9 inclusive. + def workplane(self, id): + if ((id >= 1) and (id <= 6)): + self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + if ((id >= 7) and (id <= 9)): + self.g_list.append( + ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6)))) + + ############################################################################ + ## Rates + Modes + + def feedrate(self, f): + self.f.set(f) + self.fhv = False + + def feedrate_hv(self, fh, fv): + self.fh = fh + self.fv = fv + self.fhv = True + + def calc_feedrate_hv(self, h, v): + if math.fabs(v) > math.fabs(h * 2): + # some horizontal, so it should be fine to use the horizontal feed rate + self.f.set(self.fv) + else: + # not much, if any horizontal component, so use the vertical feed rate + self.f.set(self.fh) + + def spindle(self, s, clockwise): + if clockwise == True: + self.s.set(s, self.SPINDLE_CW(), self.SPINDLE_CCW()) + else: + self.s.set(s, self.SPINDLE_CCW(), self.SPINDLE_CW()) + + def coolant(self, mode=0): + if (mode <= 0): + self.m.append(self.COOLANT_OFF()) + elif (mode == 1): + self.m.append(self.COOLANT_MIST()) + elif (mode == 2): + self.m.append(self.COOLANT_FLOOD()) + + def gearrange(self, gear=0): + if (gear <= 0): + self.m.append(self.GEAR_OFF()) + elif (gear <= 4): + self.m.append(self.GEAR() % (gear + GEAR_BASE())) + + ############################################################################ + # Moves + + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): + (x, y, z, a, b, c, axis_count) = self.filter_xyz(x, y, z, a, b, c) + if axis_count == 0: + return + self.on_move() + + if self.g0123_modal: + if self.prev_g0123 != self.RAPID(): + self.write(self.SPACE() + self.RAPID()) + self.prev_g0123 = self.RAPID() + else: + self.write(self.SPACE() + self.RAPID()) + self.write_preps() + if (x != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + else: + dx = x - self.x + self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) + self.x = x + if (y != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + else: + dy = y - self.y + self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) + + self.y = y + if (z != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) + else: + dz = z - self.z + self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) + + self.z = z + + if (a != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.A() + (self.fmt.string(a))) + else: + da = a - self.a + self.write(self.SPACE() + self.A() + (self.fmt.string(da))) + self.a = a + + if (b != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.B() + (self.fmt.string(b))) + else: + db = b - self.b + self.write(self.SPACE() + self.B() + (self.fmt.string(db))) + self.b = b + + if (c != None): + if (self.absolute_flag): + self.write(self.SPACE() + self.C() + (self.fmt.string(c))) + else: + dc = c - self.c + self.write(self.SPACE() + self.C() + (self.fmt.string(dc))) + self.c = c + self.write_spindle() + self.write_misc() + self.write('\n') + + def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): + (x, y, z, a, b, c, axis_count) = self.filter_xyz(x, y, z, a, b, c) + if axis_count == 0: + return + self.on_move() + if self.g0123_modal: + if self.prev_g0123 != self.FEED(): + self.writem([self.SPACE(), self.FEED()]) + self.prev_g0123 = self.FEED() + else: + self.write(self.SPACE() + self.FEED()) + self.write_preps() + dx = dy = dz = 0 + if (x != None): + dx = x - self.x + if (self.absolute_flag): + self.writem([self.SPACE(), self.X(), (self.fmt.string(x + self.shift_x))]) + else: + self.writem([self.SPACE(), self.X(), (self.fmt.string(dx))]) + self.x = x + if (y != None): + dy = y - self.y + if (self.absolute_flag): + self.writem([self.SPACE(), self.Y(), (self.fmt.string(y + self.shift_y))]) + else: + self.writem([self.SPACE(), self.Y(), (self.fmt.string(dy))]) + + self.y = y + if (z != None): + dz = z - self.z + if (self.absolute_flag): + self.writem([self.SPACE(), self.Z(), (self.fmt.string(z + self.shift_z))]) + else: + self.writem([self.SPACE(), self.Z(), (self.fmt.string(dz))]) + + self.z = z + + if (a != None): + da = a - self.a + if (self.absolute_flag): + self.writem([self.SPACE(), self.A(), (self.fmt.string(a))]) + else: + self.writem([self.SPACE(), self.A(), (self.fmt.string(da))]) + self.a = a + + if (b != None): + db = b - self.b + if (self.absolute_flag): + self.writem([self.SPACE(), self.B(), (self.fmt.string(b))]) + else: + self.writem([self.SPACE(), self.B(), (self.fmt.string(db))]) + self.b = b + + if (c != None): + dc = c - self.c + if (self.absolute_flag): + self.writem([self.SPACE(), self.C(), (self.fmt.string(c))]) + else: + self.writem([self.SPACE(), self.C(), (self.fmt.string(dc))]) + self.c = c + + if (self.fhv): + self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + + def filter_xyz(self, x=None, y=None, z=None, a=None, b=None, c=None): + """ Check if x,y,z,a,b,c are the same and set them to None if they are + return value = (x,y,z,a,b,c,count) where count is the number of + axis moves left. + """ + rv = [x, y, z, a, b, c, 0] + comparisons = ((x, self.shift_x, self.x), (y, self.shift_y, self.y), (z, self.shift_z, self.z), + (a, 0, self.a), (b, 0, self.b), (c, 0, self.c)) + + for i, (new_val, shift, current_val) in enumerate(comparisons): + if new_val is not None: + if self.fmt.string(new_val+shift) == self.fmt.string(current_val): + rv[i] = None + else: + rv[6] += 1 + return rv + + def get_quadrant(self, dx, dy): + if dx < 0: + if dy < 0: + return 2 + else: + return 1 + + else: + if dy < 0: + return 3 + else: + return 0 + + def quadrant_start(self, q, i, j, rad): + while q > 3: + q = q - 4 + if q == 0: + return i + rad, j + if q == 1: + return i, j + rad + if q == 2: + return i - rad, j + return i, j - rad + + def quadrant_end(self, q, i, j, rad): + return self.quadrant_start(q + 1, i, j, rad) + + def get_arc_angle(self, sdx, sdy, edx, edy, cw): + angle_s = math.atan2(sdy, sdx) + angle_e = math.atan2(edy, edx) + if cw: + if angle_s < angle_e: + angle_s = angle_s + 2 * math.pi + else: + if angle_e < angle_s: + angle_e = angle_e + 2 * math.pi + return angle_e - angle_s + + def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + (x, y, z, _, _, _, axis_count) = self.filter_xyz(x, y, z) + if axis_count == 0: + return + + if self.output_arcs_as_lines or (self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z))): + # split the helical arc into little line feed moves + + if x == None: + x = self.x + if y == None: + y = self.y + sdx = self.x - i + sdy = self.y - j + edx = x - i + edy = y - j + radius = math.sqrt(sdx*sdx + sdy*sdy) + arc_angle = self.get_arc_angle(sdx, sdy, edx, edy, cw) + angle_start = math.atan2(sdy, sdx) + tolerance = 0.02 + angle_step = 2.0 * math.atan(math.sqrt(tolerance / (radius - tolerance))) + segments = int(math.fabs(arc_angle / angle_step) + 1) + angle_step = arc_angle / segments + angle = angle_start + if z != None: + z_step = float(z - self.z)/segments + next_z = self.z + + for p in range(0, segments): + angle = angle + angle_step + next_x = i + radius * math.cos(angle) + next_y = j + radius * math.sin(angle) + if z == None: + next_z = None + else: + next_z = next_z + z_step + self.feed(next_x, next_y, next_z) + return + + if self.arc_centre_positive == True and self.in_quadrant_splitting == False: + # split in to quadrant arcs + self.in_quadrant_splitting = True + + if x == None: + x = self.x + if y == None: + y = self.y + sdx = self.x - i + sdy = self.y - j + edx = x - i + edy = y - j + + qs = self.get_quadrant(sdx, sdy) + qe = self.get_quadrant(edx, edy) + + if qs == qe: + arc_angle = math.fabs(self.get_arc_angle(sdx, sdy, edx, edy, cw)) + # arc_angle will be either less than pi/2 or greater than 3pi/2 + if arc_angle > 3.14: + if cw: + qs = qs + 4 + else: + qe = qe + 4 + + if qs == qe: + self.arc(cw, x, y, z, i, j, k, r) + else: + rad = math.sqrt(sdx * sdx + sdy * sdy) + if cw: + if qs < qe: + qs = qs + 4 + else: + if qe < qs: + qe = qe + 4 + + q = qs + while 1: + x1 = x + y1 = y + if q != qe: + if cw: + x1, y1 = self.quadrant_start(q, i, j, rad) + else: + x1, y1 = self.quadrant_end(q, i, j, rad) + + if (self.fmt.string(x1) != self.fmt.string(self.x)) or (self.fmt.string(y1) != self.fmt.string(self.y)): + if (math.fabs(x1 - self.x) > 0.01) or (math.fabs(y1 - self.y) > 0.01): + self.arc(cw, x1, y1, z, i, j, k, r) + else: + self.feed(x1, y1, z) + if q == qe: + break + if cw: + q = q - 1 + else: + q = q + 1 + + self.in_quadrant_splitting = False + return + + self.on_move() + arc_g_code = '' + if cw: + arc_g_code = self.ARC_CW() + else: + arc_g_code = self.ARC_CCW() + if self.g0123_modal: + if self.prev_g0123 != arc_g_code: + self.write(self.SPACE() + arc_g_code) + self.prev_g0123 = arc_g_code + else: + self.write(self.SPACE() + arc_g_code) + self.write_preps() + if (x != None): + dx = x - self.x + if (self.absolute_flag): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + else: + self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) + if (y != None): + dy = y - self.y + if (self.absolute_flag): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + else: + self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) + if (z != None): + dz = z - self.z + if (self.absolute_flag): + self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) + else: + self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) + if (i != None): + if self.arc_centre_absolute == False: + i = i - self.x + s = self.fmt.string(i) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_X() + s) + if (j != None): + if self.arc_centre_absolute == False: + j = j - self.y + s = self.fmt.string(j) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_Y() + s) + if (k != None): + if self.arc_centre_absolute == False: + k = k - self.z + s = self.fmt.string(k) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.CENTRE_Z() + s) + if (r != None): + s = self.fmt.string(r) + if self.arc_centre_positive == True: + if s[0] == '-': + s = s[1:] + self.write(self.SPACE() + self.RADIUS() + s) # use horizontal feed rate - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - if (x != None): - self.x = x - if (y != None): - self.y = y - if (z != None): - self.z = z - - def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - self.arc(True, x, y, z, i, j, k, r) - - def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - self.arc(False, x, y, z, i, j, k, r) - - def dwell(self, t): - self.write_preps() - self.write(self.SPACE() + self.DWELL(t)) - self.write_misc() - self.write('\n') - - def on_move(self): - if self.output_fixtures: - self.output_fixture() - self.move_done_since_tool_change = True - - def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): - pass - - def rapid_unhome(self): - pass - - def set_machine_coordinates(self): - self.write(self.SPACE() + self.MACHINE_COORDINATES()) - self.prev_g0123 = '' - - ############################################################################ - ## CRC - - def use_CRC(self): - return self.useCrc - - def CRC_nominal_path(self): - return self.useCrcCenterline - - def start_CRC(self, left = True, radius = 0.0): - # set up prep code, to be output on next line - if self.t == None: - raise "No tool specified for start_CRC()" - if left: - self.write(self.SPACE() + 'G41') - else: - self.write(self.SPACE() + 'G42') - self.write((self.SPACE() + 'D%i\n') % self.t) - - def end_CRC(self): - self.write(self.SPACE() + 'G40\n') - - ############################################################################ - ## Cycles - - def pattern(self): - pass - - def pattern_uses_subroutine(self): - return self.pattern_done_with_subroutine - - def pocket(self): - pass - - def profile(self): - pass - - def write_internal_coolant_commands(self, internal_coolant_on): - if (internal_coolant_on != None) and (self.output_internal_coolant_commands == True): - if internal_coolant_on == True: - if self.internal_coolant_on != True: - self.write(self.SPACE()) - self.write(self.INTERNAL_COOLANT_ON() + '\n') - self.internal_coolant_on = True - else: - if self.internal_coolant_on != False: - self.write(self.SPACE()) - self.write(self.INTERNAL_COOLANT_OFF() + '\n') - self.internal_coolant_on = False - - # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). - # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to - # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. - # Instead, this routine combines the Z value and the depth value to determine the bottom of - # the hole. - # - # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts - # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. - # - # The peck_depth value is the incremental depth (Q value) that tells the peck drilling - # cycle how deep to go on each peck until the full depth is achieved. - # - # NOTE: This routine forces the mode to absolute mode so that the values passed into - # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't - # revert it. I must set the mode so that I can be sure the values I'm passing in make - # sense to the end-machine. - # - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): - if (depthparams.clearance_height == None): - self.first_drill_pos = False - return - - self.write_internal_coolant_commands(internal_coolant_on) - - drillExpanded = self.drillExpanded - if (depthparams.step_down != 0) and (dwell != 0): - # pecking and dwell together - if self.dwell_allowed_in_G83 != True: - drillExpanded = True - - if drillExpanded: - # for machines which don't understand G81, G82 etc. - peck_depth = depthparams.step_down - if peck_depth == None: - peck_depth = depthparams.final_depth - current_z = depthparams.start_depth - self.rapid(x, y) - - first = True - last_cut = False - - while True: - next_z = current_z - peck_depth - if next_z < (depthparams.final_depth + 0.001): - next_z = depthparams.final_depth - last_cut = True - if next_z >= current_z: - break; - if first: - self.rapid(z = depthparams.start_depth + depthparams.rapid_safety_space) - else: - self.rapid(z = current_z) - self.feed(z = next_z) - if dwell != 0 and last_cut: - self.dwell(dwell) - if last_cut:self.rapid(z = depthparams.clearance_height) - else: - if rapid_to_clearance: - self.rapid(z = depthparams.clearance_height) - else: - self.rapid(z = depthparams.start_depth + depthparams.rapid_safety_space) - current_z = next_z - first = False - - self.first_drill_pos = False - return - - if self.output_g98_and_g99 == True: - if rapid_to_clearance == True: - if self.output_g43_z_before_drilling_if_g98: - if self.fmt.string(depthparams.clearance_height) != self.z_for_g43: - self.z_for_g43 = self.fmt.string(depthparams.clearance_height) - self.write(self.SPACE() + 'G43' + self.SPACE() + 'Z' + self.z_for_g43 + '\n') - - if self.first_drill_pos ==True and rapid_to_clearance == True: - self.rapid(x, y) - self.rapid(z = depthparams.clearance_height) - - self.in_canned_cycle = True - self.write_preps() - - if (depthparams.step_down != 0): - # G83 peck drilling - if self.drill_modal: - if self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) != self.prev_drill: - self.write(self.SPACE() + self.PECK_DRILL() + self.SPACE() + self.PECK_DEPTH(depthparams.step_down)) - self.prev_drill = self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) - else: - self.write(self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down)) - - if (self.dwell != 0) and self.dwell_allowed_in_G83: - self.write(self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell))) - - else: - # We're either just drilling or drilling with dwell. - if (dwell == 0): - # We're just drilling. - if self.drill_modal: - if self.DRILL() != self.prev_drill: - self.write(self.SPACE() + self.DRILL()) - self.prev_drill = self.DRILL() - else: - self.write(self.SPACE() + self.DRILL()) - - else: - # We're drilling with dwell. - - if self.drill_modal: - if self.DRILL_WITH_DWELL(dwell) != self.prev_drill: - self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) - self.prev_drill = self.DRILL_WITH_DWELL(dwell) - else: - self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) - - if self.output_g98_and_g99 == True: - if rapid_to_clearance == True: - if self.g98_not_g99 != True: - self.write(self.SPACE() + self.RETRACT_TO_CLEARANCE()) - self.g98_not_g99 = True - else: - if self.g98_not_g99 != False: - self.write(self.SPACE() + self.RETRACT_TO_STANDOFF()) - self.g98_not_g99 = False - - # Set the retraction point to the 'standoff' distance above the starting z height. - retract_height = depthparams.start_depth + depthparams.rapid_safety_space - if (x != None): - self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) - self.x = x - - if (y != None): - self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) - self.y = y - - if self.drill_modal: - if depthparams.start_depth != self.prev_z: - self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) - self.prev_z=depthparams.start_depth - else: - self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) # This is the 'z' value for the bottom of the hole. - self.z = (depthparams.start_depth + depthparams.rapid_safety_space) # We want to remember where z is at the end (at the top of the hole) - - if self.drill_modal: - if self.prev_retract != self.RETRACT(retract_height) : - self.write(self.SPACE() + self.RETRACT(retract_height)) - self.prev_retract = self.RETRACT(retract_height) - else: - self.write(self.SPACE() + self.RETRACT(retract_height)) - - if (self.fv) : - self.f.set(self.fv) - - self.write_feedrate() - self.write_spindle() - self.write_misc() - self.write('\n') - self.first_drill_pos = False - - def end_canned_cycle(self): - if self.in_canned_cycle == False: - return - self.write(self.SPACE() + self.END_CANNED_CYCLE() + '\n') - self.write_internal_coolant_commands(0) - self.prev_drill = '' - self.prev_g0123 = '' - self.prev_z = '' - self.prev_f = '' - self.prev_retract = '' - self.in_canned_cycle = False - self.first_drill_pos = True - - ############################################################################ - ## Misc - - def comment(self, text): - self.write((self.COMMENT(text) + '\n')) - - def insert(self, text): - pass - - def block_delete(self, on=False): - pass - - def variable(self, id): - return (self.VARIABLE() % id) - - def variable_set(self, id, value): - self.write(self.SPACE() + (self.VARIABLE() % id) + self.SPACE() + (self.VARIABLE_SET() % value) + '\n') - - # This routine uses the G92 coordinate system offsets to establish a temporary coordinate - # system at the machine's current position. It can then use absolute coordinates relative - # to this position which makes coding easy. It then moves to the 'point along edge' which - # should be above the workpiece but still on one edge. It then backs off from the edge - # to the 'retracted point'. It then plunges down by the depth value specified. It then - # probes back towards the 'destination point'. The probed X,Y location are stored - # into the 'intersection variable' variables. Finally the machine moves back to the - # original location. This is important so that the results of multiple calls to this - # routine may be compared meaningfully. - def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None ): - self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) - - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write_feedrate() - self.write('\t(Set the feed rate for probing)\n') - - self.rapid(point_along_edge_x,point_along_edge_y) - self.rapid(retracted_point_x,retracted_point_y) - self.feed(z=depth) - - self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + ' Y ' + (self.fmt.string(destination_point_y)) ) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Back off the workpiece and re-probe more slowly') - self.write(self.SPACE() + ('#' + intersection_variable_x + '= [#5061 - [ 0.5 * ' + probe_offset_x_component + ']]\n')) - self.write(self.SPACE() + ('#' + intersection_variable_y + '= [#5062 - [ 0.5 * ' + probe_offset_y_component + ']]\n')) - self.write(self.RAPID()) - self.write(self.SPACE() + ' X #' + intersection_variable_x + ' Y #' + intersection_variable_y + '\n') - - self.write(self.SPACE() + self.FEEDRATE() + self.ffmt.string(self.fh / 2.0) + '\n') - - self.write((self.SPACE() + self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + ' Y ' + (self.fmt.string(destination_point_y)) ) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Store the probed location somewhere we can get it again later') - self.write(('#' + intersection_variable_x + '=' + probe_offset_x_component + ' (Portion of probe radius that contributes to the X coordinate)\n')) - self.write(('#' + intersection_variable_x + '=[#' + intersection_variable_x + ' + #5061]\n')) - self.write(('#' + intersection_variable_y + '=' + probe_offset_y_component + ' (Portion of probe radius that contributes to the Y coordinate)\n')) - self.write(('#' + intersection_variable_y + '=[#' + intersection_variable_y + ' + #5062]\n')) - - self.comment('Now move back to the original location') - self.rapid(retracted_point_x,retracted_point_y) - self.rapid(z=0) - self.rapid(point_along_edge_x,point_along_edge_y) - self.rapid(x=0, y=0) - - self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ('\t(Restore the previous coordinate system)\n'))) - - def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): - self.write((self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) - if (self.fhv) : self.calc_feedrate_hv(1, 0) - self.write(self.FEEDRATE() + ' [' + self.ffmt.string(self.fh) + ' / 5.0 ]') - self.write('\t(Set the feed rate for probing)\n') - - if x != None and y != None: - self.write(self.RAPID()) - self.write(' X ' + x + ' Y ' + y + '\n') - - self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + ' Z ' + (self.fmt.string(depth)) + ('\t(Probe towards our destination point)\n'))) - - self.comment('Store the probed location somewhere we can get it again later') - self.write(('#' + intersection_variable_z + '= #5063\n')) - - self.comment('Now move back to the original location') - self.rapid(z=0) - self.rapid(x=0, y=0) - - self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ('\t(Restore the previous coordinate system)\n'))) - - - def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): - pass - - def open_log_file(self, xml_file_name=None ): - pass - - def log_coordinate(self, x=None, y=None, z=None): - pass - - def log_message(self, message=None): - pass - - def close_log_file(self): - pass - - # Rapid movement to the midpoint between the two points specified. - # NOTE: The points are specified either as strings representing numbers or as strings - # representing variable names. This allows the HeeksCNC module to determine which - # variable names are used in these various routines. - def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): - self.write(self.RAPID()) - if ((x1 != None) and (x2 != None)): - self.write((' X ' + '[[[' + x1 + ' - ' + x2 + '] / 2.0] + ' + x2 + ']')) - - if ((y1 != None) and (y2 != None)): - self.write((' Y ' + '[[[' + y1 + ' - ' + y2 + '] / 2.0] + ' + y2 + ']')) - - if ((z1 != None) and (z2 != None)): - self.write((' Z ' + '[[[' + z1 + ' - ' + z2 + '] / 2.0] + ' + z2 + ']')) - - self.write('\n') - - # Rapid movement to the intersection of two lines (in the XY plane only). This routine - # is based on information found in http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ - # written by Paul Bourke. The ua_numerator, ua_denominator, ua and ub parameters - # represent variable names (with the preceding '#' included in them) for use as temporary - # variables. They're specified here simply so that HeeksCNC can manage which variables - # are used in which GCode calculations. - # - # As per the notes on the web page, the ua_denominator and ub_denominator formulae are - # the same so we don't repeat this. If the two lines are coincident or parallel then - # no movement occurs. - # - # NOTE: The points are specified either as strings representing numbers or as strings - # representing variable names. This allows the HeeksCNC module to determine which - # variable names are used in these various routines. - def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): - self.comment('Find the intersection of the two lines made up by the four probed points') - self.write(ua_numerator + '=[[[' + x4 + ' - ' + x3 + '] * [' + y1 + ' - ' + y3 + ']] - [[' + y4 + ' - ' + y3 + '] * [' + x1 + ' - ' + x3 + ']]]\n') - self.write(ua_denominator + '=[[[' + y4 + ' - ' + y3 + '] * [' + x2 + ' - ' + x1 + ']] - [[' + x4 + ' - ' + x3 + '] * [' + y2 + ' - ' + y1 + ']]]\n') - self.write(ub_numerator + '=[[[' + x2 + ' - ' + x1 + '] * [' + y1 + ' - ' + y3 + ']] - [[' + y2 + ' - ' + y1 + '] * [' + x1 + ' - ' + x3 + ']]]\n') - - self.comment('If they are not parallel') - self.write('O900 IF [' + ua_denominator + ' NE 0]\n') - self.comment('And if they are not coincident') - self.write('O901 IF [' + ua_numerator + ' NE 0 ]\n') - - self.write(' ' + ua + '=[' + ua_numerator + ' / ' + ua_denominator + ']\n') - self.write(' ' + ub + '=[' + ub_numerator + ' / ' + ua_denominator + ']\n') # NOTE: ub denominator is the same as ua denominator - self.write(' ' + intersection_x + '=[' + x1 + ' + [[' + ua + ' * [' + x2 + ' - ' + x1 + ']]]]\n') - self.write(' ' + intersection_y + '=[' + y1 + ' + [[' + ua + ' * [' + y2 + ' - ' + y1 + ']]]]\n') - self.write(' ' + self.RAPID()) - self.write(' X ' + intersection_x + ' Y ' + intersection_y + '\n') - - self.write('O901 ENDIF\n') - self.write('O900 ENDIF\n') - - # We need to calculate the rotation angle based on the line formed by the - # x1,y1 and x2,y2 coordinate pair. With that angle, we need to move - # x_offset and y_offset distance from the current (0,0,0) position. - # - # The x1,y1,x2 and y2 parameters are all variable names that contain the actual - # values. - # The x_offset and y_offset are both numeric (floating point) values - def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): - self.comment('Rapid to rotated coordinate') - self.write( '#1 = [atan[' + y2 + ' - ' + y1 + ']/[' + x2 +' - ' + x1 + ']] (nominal_angle)\n') - self.write( '#2 = [atan[' + ref_y + ']/[' + ref_x + ']] (reference angle)\n') - self.write( '#3 = [#1 - #2] (angle)\n' ) - self.write( '#4 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + '] * COS[ #3 ]] - [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * SIN[ #3 ]]]\n' ) - self.write( '#5 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + '] * SIN[ #3 ]] + [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * COS[ #3 ]]]\n' ) - - self.write( '#6 = [[' + (self.fmt.string(x_final)) + ' * COS[ #3 ]] - [' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]]]\n' ) - self.write( '#7 = [[' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]] + [' + (self.fmt.string(y_final)) + ' * COS[ #3 ]]]\n' ) - - self.write( self.RAPID() + ' X [ #4 + #6 ] Y [ #5 + #7 ]\n' ) - - def BEST_POSSIBLE_SPEED(self, motion_blending_tolerance, naive_cam_tolerance): - statement = 'G64' - - if (motion_blending_tolerance > 0): - statement += ' P ' + str(motion_blending_tolerance) - - if (naive_cam_tolerance > 0): - statement += ' Q ' + str(naive_cam_tolerance) - - return(statement) - - def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance ): - if (mode == 0): - self.write( self.EXACT_PATH_MODE() + '\n' ) - if (mode == 1): - self.write( self.EXACT_STOP_MODE() + '\n' ) - if (mode == 2): - self.write( self.BEST_POSSIBLE_SPEED( motion_blending_tolerance, naive_cam_tolerance ) + '\n' ) + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + if (x != None): + self.x = x + if (y != None): + self.y = y + if (z != None): + self.z = z + + def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + self.arc(True, x, y, z, i, j, k, r) + + def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + self.arc(False, x, y, z, i, j, k, r) + + def dwell(self, t): + self.write_preps() + self.write(self.SPACE() + self.DWELL(t)) + self.write_misc() + self.write('\n') + + def on_move(self): + if self.output_fixtures: + self.output_fixture() + self.move_done_since_tool_change = True + + def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): + pass + + def rapid_unhome(self): + pass + + def set_machine_coordinates(self): + self.write(self.SPACE() + self.MACHINE_COORDINATES()) + self.prev_g0123 = '' + + ############################################################################ + # CRC + + def use_CRC(self): + return self.useCrc + + def CRC_nominal_path(self): + return self.useCrcCenterline + + def start_CRC(self, left=True, radius=0.0): + # set up prep code, to be output on next line + if self.t == None: + raise "No tool specified for start_CRC()" + if left: + self.write(self.SPACE() + 'G41') + else: + self.write(self.SPACE() + 'G42') + self.write((self.SPACE() + 'D%i\n') % self.t) + + def end_CRC(self): + self.write(self.SPACE() + 'G40\n') + + ############################################################################ + # Cycles + + def pattern(self): + pass + + def pattern_uses_subroutine(self): + return self.pattern_done_with_subroutine + + def pocket(self): + pass + + def profile(self): + pass + + def write_internal_coolant_commands(self, internal_coolant_on): + if (internal_coolant_on != None) and (self.output_internal_coolant_commands == True): + if internal_coolant_on == True: + if self.internal_coolant_on != True: + self.write(self.SPACE()) + self.write(self.INTERNAL_COOLANT_ON() + '\n') + self.internal_coolant_on = True + else: + if self.internal_coolant_on != False: + self.write(self.SPACE()) + self.write(self.INTERNAL_COOLANT_OFF() + '\n') + self.internal_coolant_on = False + + # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). + # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to + # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. + # Instead, this routine combines the Z value and the depth value to determine the bottom of + # the hole. + # + # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts + # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. + # + # The peck_depth value is the incremental depth (Q value) that tells the peck drilling + # cycle how deep to go on each peck until the full depth is achieved. + # + # NOTE: This routine forces the mode to absolute mode so that the values passed into + # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't + # revert it. I must set the mode so that I can be sure the values I'm passing in make + # sense to the end-machine. + # + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + if (depthparams.clearance_height == None): + self.first_drill_pos = False + return + + self.write_internal_coolant_commands(internal_coolant_on) + + drillExpanded = self.drillExpanded + if (depthparams.step_down != 0) and (dwell != 0): + # pecking and dwell together + if self.dwell_allowed_in_G83 != True: + drillExpanded = True + + if drillExpanded: + # for machines which don't understand G81, G82 etc. + peck_depth = depthparams.step_down + if peck_depth == None: + peck_depth = depthparams.final_depth + current_z = depthparams.start_depth + self.rapid(x, y) + + first = True + last_cut = False + + while True: + next_z = current_z - peck_depth + if next_z < (depthparams.final_depth + 0.001): + next_z = depthparams.final_depth + last_cut = True + if next_z >= current_z: + break + if first: + self.rapid(z=depthparams.start_depth + depthparams.rapid_safety_space) + else: + self.rapid(z=current_z) + self.feed(z=next_z) + if dwell != 0 and last_cut: + self.dwell(dwell) + if last_cut: + self.rapid(z=depthparams.clearance_height) + else: + if rapid_to_clearance: + self.rapid(z=depthparams.clearance_height) + else: + self.rapid(z=depthparams.start_depth + depthparams.rapid_safety_space) + current_z = next_z + first = False + + self.first_drill_pos = False + return + + if self.output_g98_and_g99 == True: + if rapid_to_clearance == True: + if self.output_g43_z_before_drilling_if_g98: + if self.fmt.string(depthparams.clearance_height) != self.z_for_g43: + self.z_for_g43 = self.fmt.string(depthparams.clearance_height) + self.write(self.SPACE() + 'G43' + self.SPACE() + + 'Z' + self.z_for_g43 + '\n') + + if self.first_drill_pos == True and rapid_to_clearance == True: + self.rapid(x, y) + self.rapid(z=depthparams.clearance_height) + + self.in_canned_cycle = True + self.write_preps() + + if (depthparams.step_down != 0): + # G83 peck drilling + if self.drill_modal: + if self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) != self.prev_drill: + self.write(self.SPACE() + self.PECK_DRILL() + self.SPACE() + + self.PECK_DEPTH(depthparams.step_down)) + self.prev_drill = self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) + else: + self.write(self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down)) + + if (self.dwell != 0) and self.dwell_allowed_in_G83: + self.write(self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell))) + + else: + # We're either just drilling or drilling with dwell. + if (dwell == 0): + # We're just drilling. + if self.drill_modal: + if self.DRILL() != self.prev_drill: + self.write(self.SPACE() + self.DRILL()) + self.prev_drill = self.DRILL() + else: + self.write(self.SPACE() + self.DRILL()) + + else: + # We're drilling with dwell. + + if self.drill_modal: + if self.DRILL_WITH_DWELL(dwell) != self.prev_drill: + self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) + self.prev_drill = self.DRILL_WITH_DWELL(dwell) + else: + self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) + + if self.output_g98_and_g99 == True: + if rapid_to_clearance == True: + if self.g98_not_g99 != True: + self.write(self.SPACE() + self.RETRACT_TO_CLEARANCE()) + self.g98_not_g99 = True + else: + if self.g98_not_g99 != False: + self.write(self.SPACE() + self.RETRACT_TO_STANDOFF()) + self.g98_not_g99 = False + + # Set the retraction point to the 'standoff' distance above the starting z height. + retract_height = depthparams.start_depth + depthparams.rapid_safety_space + if (x != None): + self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) + self.x = x + + if (y != None): + self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) + self.y = y + + if self.drill_modal: + if depthparams.start_depth != self.prev_z: + self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) + self.prev_z = depthparams.start_depth + else: + # This is the 'z' value for the bottom of the hole. + self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) + # We want to remember where z is at the end (at the top of the hole) + self.z = (depthparams.start_depth + depthparams.rapid_safety_space) + + if self.drill_modal: + if self.prev_retract != self.RETRACT(retract_height): + self.write(self.SPACE() + self.RETRACT(retract_height)) + self.prev_retract = self.RETRACT(retract_height) + else: + self.write(self.SPACE() + self.RETRACT(retract_height)) + + if (self.fv): + self.f.set(self.fv) + + self.write_feedrate() + self.write_spindle() + self.write_misc() + self.write('\n') + self.first_drill_pos = False + + def end_canned_cycle(self): + if self.in_canned_cycle == False: + return + self.write(self.SPACE() + self.END_CANNED_CYCLE() + '\n') + self.write_internal_coolant_commands(0) + self.prev_drill = '' + self.prev_g0123 = '' + self.prev_z = '' + self.prev_f = '' + self.prev_retract = '' + self.in_canned_cycle = False + self.first_drill_pos = True + + ############################################################################ + # Misc + + def comment(self, text): + self.write((self.COMMENT(text) + '\n')) + + def insert(self, text): + pass + + def block_delete(self, on=False): + pass + + def variable(self, id): + return (self.VARIABLE() % id) + + def variable_set(self, id, value): + self.write(self.SPACE() + (self.VARIABLE() % id) + + self.SPACE() + (self.VARIABLE_SET() % value) + '\n') + + # This routine uses the G92 coordinate system offsets to establish a temporary coordinate + # system at the machine's current position. It can then use absolute coordinates relative + # to this position which makes coding easy. It then moves to the 'point along edge' which + # should be above the workpiece but still on one edge. It then backs off from the edge + # to the 'retracted point'. It then plunges down by the depth value specified. It then + # probes back towards the 'destination point'. The probed X,Y location are stored + # into the 'intersection variable' variables. Finally the machine moves back to the + # original location. This is important so that the results of multiple calls to this + # routine may be compared meaningfully. + def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None): + self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM() + + (' X 0 Y 0 Z 0') + ('\t(Temporarily make this the origin)\n'))) + + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write_feedrate() + self.write('\t(Set the feed rate for probing)\n') + + self.rapid(point_along_edge_x, point_along_edge_y) + self.rapid(retracted_point_x, retracted_point_y) + self.feed(z=depth) + + self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x)) + + ' Y ' + (self.fmt.string(destination_point_y))) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Back off the workpiece and re-probe more slowly') + self.write(self.SPACE() + ('#' + intersection_variable_x + + '= [#5061 - [ 0.5 * ' + probe_offset_x_component + ']]\n')) + self.write(self.SPACE() + ('#' + intersection_variable_y + + '= [#5062 - [ 0.5 * ' + probe_offset_y_component + ']]\n')) + self.write(self.RAPID()) + self.write(self.SPACE() + ' X #' + intersection_variable_x + + ' Y #' + intersection_variable_y + '\n') + + self.write(self.SPACE() + self.FEEDRATE() + self.ffmt.string(self.fh / 2.0) + '\n') + + self.write((self.SPACE() + self.PROBE_TOWARDS_WITH_SIGNAL() + (' X ' + (self.fmt.string(destination_point_x) + ) + ' Y ' + (self.fmt.string(destination_point_y))) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Store the probed location somewhere we can get it again later') + self.write(('#' + intersection_variable_x + '=' + probe_offset_x_component + + ' (Portion of probe radius that contributes to the X coordinate)\n')) + self.write(('#' + intersection_variable_x + + '=[#' + intersection_variable_x + ' + #5061]\n')) + self.write(('#' + intersection_variable_y + '=' + probe_offset_y_component + + ' (Portion of probe radius that contributes to the Y coordinate)\n')) + self.write(('#' + intersection_variable_y + + '=[#' + intersection_variable_y + ' + #5062]\n')) + + self.comment('Now move back to the original location') + self.rapid(retracted_point_x, retracted_point_y) + self.rapid(z=0) + self.rapid(point_along_edge_x, point_along_edge_y) + self.rapid(x=0, y=0) + + self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + + ('\t(Restore the previous coordinate system)\n'))) + + def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): + self.write((self.SET_TEMPORARY_COORDINATE_SYSTEM() + (' X 0 Y 0 Z 0') + + ('\t(Temporarily make this the origin)\n'))) + if (self.fhv): + self.calc_feedrate_hv(1, 0) + self.write(self.FEEDRATE() + ' [' + self.ffmt.string(self.fh) + ' / 5.0 ]') + self.write('\t(Set the feed rate for probing)\n') + + if x != None and y != None: + self.write(self.RAPID()) + self.write(' X ' + x + ' Y ' + y + '\n') + + self.write((self.PROBE_TOWARDS_WITH_SIGNAL() + ' Z ' + + (self.fmt.string(depth)) + ('\t(Probe towards our destination point)\n'))) + + self.comment('Store the probed location somewhere we can get it again later') + self.write(('#' + intersection_variable_z + '= #5063\n')) + + self.comment('Now move back to the original location') + self.rapid(z=0) + self.rapid(x=0, y=0) + + self.write((self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + + ('\t(Restore the previous coordinate system)\n'))) + + def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): + pass + + def open_log_file(self, xml_file_name=None): + pass + + def log_coordinate(self, x=None, y=None, z=None): + pass + + def log_message(self, message=None): + pass + + def close_log_file(self): + pass + + # Rapid movement to the midpoint between the two points specified. + # NOTE: The points are specified either as strings representing numbers or as strings + # representing variable names. This allows the HeeksCNC module to determine which + # variable names are used in these various routines. + def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): + self.write(self.RAPID()) + if ((x1 != None) and (x2 != None)): + self.write((' X ' + '[[[' + x1 + ' - ' + x2 + '] / 2.0] + ' + x2 + ']')) + + if ((y1 != None) and (y2 != None)): + self.write((' Y ' + '[[[' + y1 + ' - ' + y2 + '] / 2.0] + ' + y2 + ']')) + + if ((z1 != None) and (z2 != None)): + self.write((' Z ' + '[[[' + z1 + ' - ' + z2 + '] / 2.0] + ' + z2 + ']')) + + self.write('\n') + + # Rapid movement to the intersection of two lines (in the XY plane only). This routine + # is based on information found in http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ + # written by Paul Bourke. The ua_numerator, ua_denominator, ua and ub parameters + # represent variable names (with the preceding '#' included in them) for use as temporary + # variables. They're specified here simply so that HeeksCNC can manage which variables + # are used in which GCode calculations. + # + # As per the notes on the web page, the ua_denominator and ub_denominator formulae are + # the same so we don't repeat this. If the two lines are coincident or parallel then + # no movement occurs. + # + # NOTE: The points are specified either as strings representing numbers or as strings + # representing variable names. This allows the HeeksCNC module to determine which + # variable names are used in these various routines. + def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): + self.comment('Find the intersection of the two lines made up by the four probed points') + self.write(ua_numerator + '=[[[' + x4 + ' - ' + x3 + '] * [' + y1 + ' - ' + + y3 + ']] - [[' + y4 + ' - ' + y3 + '] * [' + x1 + ' - ' + x3 + ']]]\n') + self.write(ua_denominator + '=[[[' + y4 + ' - ' + y3 + '] * [' + x2 + ' - ' + + x1 + ']] - [[' + x4 + ' - ' + x3 + '] * [' + y2 + ' - ' + y1 + ']]]\n') + self.write(ub_numerator + '=[[[' + x2 + ' - ' + x1 + '] * [' + y1 + ' - ' + + y3 + ']] - [[' + y2 + ' - ' + y1 + '] * [' + x1 + ' - ' + x3 + ']]]\n') + + self.comment('If they are not parallel') + self.write('O900 IF [' + ua_denominator + ' NE 0]\n') + self.comment('And if they are not coincident') + self.write('O901 IF [' + ua_numerator + ' NE 0 ]\n') + + self.write(' ' + ua + '=[' + ua_numerator + ' / ' + ua_denominator + ']\n') + # NOTE: ub denominator is the same as ua denominator + self.write(' ' + ub + '=[' + ub_numerator + ' / ' + ua_denominator + ']\n') + self.write(' ' + intersection_x + + '=[' + x1 + ' + [[' + ua + ' * [' + x2 + ' - ' + x1 + ']]]]\n') + self.write(' ' + intersection_y + + '=[' + y1 + ' + [[' + ua + ' * [' + y2 + ' - ' + y1 + ']]]]\n') + self.write(' ' + self.RAPID()) + self.write(' X ' + intersection_x + ' Y ' + intersection_y + '\n') + + self.write('O901 ENDIF\n') + self.write('O900 ENDIF\n') + + # We need to calculate the rotation angle based on the line formed by the + # x1,y1 and x2,y2 coordinate pair. With that angle, we need to move + # x_offset and y_offset distance from the current (0,0,0) position. + # + # The x1,y1,x2 and y2 parameters are all variable names that contain the actual + # values. + # The x_offset and y_offset are both numeric (floating point) values + def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): + self.comment('Rapid to rotated coordinate') + self.write('#1 = [atan[' + y2 + ' - ' + y1 + ']/[' + + x2 + ' - ' + x1 + ']] (nominal_angle)\n') + self.write('#2 = [atan[' + ref_y + ']/[' + ref_x + ']] (reference angle)\n') + self.write('#3 = [#1 - #2] (angle)\n') + self.write('#4 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + + '] * COS[ #3 ]] - [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * SIN[ #3 ]]]\n') + self.write('#5 = [[[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(x_current)) + + '] * SIN[ #3 ]] + [[' + (self.fmt.string(0)) + ' - ' + (self.fmt.string(y_current)) + '] * COS[ #3 ]]]\n') + + self.write('#6 = [[' + (self.fmt.string(x_final)) + ' * COS[ #3 ]] - [' + + (self.fmt.string(y_final)) + ' * SIN[ #3 ]]]\n') + self.write('#7 = [[' + (self.fmt.string(y_final)) + ' * SIN[ #3 ]] + [' + + (self.fmt.string(y_final)) + ' * COS[ #3 ]]]\n') + + self.write(self.RAPID() + ' X [ #4 + #6 ] Y [ #5 + #7 ]\n') + + def BEST_POSSIBLE_SPEED(self, motion_blending_tolerance, naive_cam_tolerance): + statement = 'G64' + + if (motion_blending_tolerance > 0): + statement += ' P ' + str(motion_blending_tolerance) + + if (naive_cam_tolerance > 0): + statement += ' Q ' + str(naive_cam_tolerance) + + return(statement) + + def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance): + if (mode == 0): + self.write(self.EXACT_PATH_MODE() + '\n') + if (mode == 1): + self.write(self.EXACT_STOP_MODE() + '\n') + if (mode == 2): + self.write(self.BEST_POSSIBLE_SPEED( + motion_blending_tolerance, naive_cam_tolerance) + '\n') ################################################################################ diff --git a/scripts/addons/cam/nc/iso_codes.py b/scripts/addons/cam/nc/iso_codes.py index 3c17f37a1..1cba92e10 100644 --- a/scripts/addons/cam/nc/iso_codes.py +++ b/scripts/addons/cam/nc/iso_codes.py @@ -1,4 +1,5 @@ class Codes(): - pass + pass + codes = Codes() diff --git a/scripts/addons/cam/nc/iso_crc.py b/scripts/addons/cam/nc/iso_crc.py index 312b00e11..61144d3ec 100644 --- a/scripts/addons/cam/nc/iso_crc.py +++ b/scripts/addons/cam/nc/iso_crc.py @@ -10,6 +10,8 @@ import math ################################################################################ + + class Creator(iso.Creator): def __init__(self): @@ -18,4 +20,5 @@ def __init__(self): ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/iso_crc_read.py b/scripts/addons/cam/nc/iso_crc_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/iso_crc_read.py +++ b/scripts/addons/cam/nc/iso_crc_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/iso_modal.py b/scripts/addons/cam/nc/iso_modal.py index 98508791f..ffdde3a97 100644 --- a/scripts/addons/cam/nc/iso_modal.py +++ b/scripts/addons/cam/nc/iso_modal.py @@ -10,6 +10,8 @@ import math ################################################################################ + + class Creator(iso.Creator): def __init__(self): @@ -19,4 +21,5 @@ def __init__(self): self.drill_modal = True ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/iso_modal_read.py b/scripts/addons/cam/nc/iso_modal_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/iso_modal_read.py +++ b/scripts/addons/cam/nc/iso_modal_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/iso_read.py b/scripts/addons/cam/nc/iso_read.py index a923bcd88..9e729a657 100644 --- a/scripts/addons/cam/nc/iso_read.py +++ b/scripts/addons/cam/nc/iso_read.py @@ -10,20 +10,23 @@ import sys ################################################################################ + + class Parser(nc.Parser): def __init__(self, writer): nc.Parser.__init__(self, writer) - self.pattern_main = re.compile('([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') + self.pattern_main = re.compile( + '([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') self.arc_centre_absolute = False self.arc_centre_positive = False self.oldx = None self.oldy = None self.oldz = None - #if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point - # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal + # if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point + # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal # add your character here > [(!;] for comments char # then look for the 'comment' function towards the end of the file and add another elif @@ -86,7 +89,7 @@ def ParseWord(self, word): self.path_col = "feed" self.col = "feed" elif (word == 'G82' or word == 'g82'): - self.drill = True; + self.drill = True self.no_move = True self.path_col = "feed" self.col = "feed" @@ -99,7 +102,8 @@ def ParseWord(self, word): self.absolute() elif (word == 'G91' or word == 'g91'): self.incremental() - elif (word[0] == 'G') : col = "prep" + elif (word[0] == 'G'): + col = "prep" elif (word[0] == 'I' or word[0] == 'i'): self.col = "axis" self.i = eval(word[1:]) @@ -112,19 +116,22 @@ def ParseWord(self, word): self.col = "axis" self.k = eval(word[1:]) self.move = True - elif (word[0] == 'M') : self.col = "misc" - elif (word[0] == 'N') : self.col = "blocknum" - elif (word[0] == 'O') : self.col = "program" + elif (word[0] == 'M'): + self.col = "misc" + elif (word[0] == 'N'): + self.col = "blocknum" + elif (word[0] == 'O'): + self.col = "program" elif (word[0] == 'P' or word[0] == 'p'): - if (self.no_move != True): - self.col = "axis" - self.p = eval(word[1:]) - self.move = True + if (self.no_move != True): + self.col = "axis" + self.p = eval(word[1:]) + self.move = True elif (word[0] == 'Q' or word[0] == 'q'): - if (self.no_move != True): - self.col = "axis" - self.q = eval(word[1:]) - self.move = True + if (self.no_move != True): + self.col = "axis" + self.q = eval(word[1:]) + self.move = True elif (word[0] == 'R' or word[0] == 'r'): self.col = "axis" self.r = eval(word[1:]) @@ -132,9 +139,9 @@ def ParseWord(self, word): elif (word[0] == 'S' or word[0] == 's'): self.col = "axis" self.writer.spindle(word[1:], (float(word[1:]) >= 0.0)) - elif (word[0] == 'T') : + elif (word[0] == 'T'): self.col = "tool" - self.writer.tool_change( eval(word[1:]) ) + self.writer.tool_change(eval(word[1:])) elif (word[0] == 'X' or word[0] == 'x'): self.col = "axis" self.x = eval(word[1:]) @@ -147,9 +154,15 @@ def ParseWord(self, word): self.col = "axis" self.z = eval(word[1:]) self.move = True - elif (word[0] == '(') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == '!') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == ';') : (self.col, self.cdata) = ("comment", True) - elif (word[0] == '#') : self.col = "variable" - elif (word[0] == ':') : self.col = "blocknum" - elif (ord(word[0]) <= 32) : self.cdata = True + elif (word[0] == '('): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == '!'): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == ';'): + (self.col, self.cdata) = ("comment", True) + elif (word[0] == '#'): + self.col = "variable" + elif (word[0] == ':'): + self.col = "blocknum" + elif (ord(word[0]) <= 32): + self.cdata = True diff --git a/scripts/addons/cam/nc/lathe1.py b/scripts/addons/cam/nc/lathe1.py index adec62097..2d3b0e2c8 100644 --- a/scripts/addons/cam/nc/lathe1.py +++ b/scripts/addons/cam/nc/lathe1.py @@ -10,6 +10,8 @@ import math ################################################################################ + + class CreatorIso(nc.Creator): def __init__(self): @@ -48,7 +50,7 @@ def __init__(self): self.absolute_flag = True self.ffmt = iso.codes.FORMAT_FEEDRATE() ############################################################################ - ## Internals + # Internals def write_feedrate(self): if self.f_modal: @@ -64,39 +66,40 @@ def write_preps(self): self.g = '' def write_misc(self): - if (len(self.m)) : self.write(self.m.pop()) + if (len(self.m)): + self.write(self.m.pop()) def write_spindle(self): self.write(self.s) self.s = '' ############################################################################ - ## Programs + # Programs def program_begin(self, id, name=''): self.write((iso.codes.PROGRAM() % id) + iso.codes.SPACE() + (iso.codes.COMMENT(name))) self.write('\n') def program_stop(self, optional=False): - if (optional) : + if (optional): self.write(iso.codes.STOP_OPTIONAL() + '\n') self.prev_g0123 = '' - else : + else: self.write(iso.codes.STOP() + '\n') self.prev_g0123 = '' - def program_end(self): self.write(iso.codes.PROGRAM_END() + '\n') def flush_nc(self): - if len(self.g) == 0 and len(self.m) == 0: return + if len(self.g) == 0 and len(self.m) == 0: + return self.write_preps() self.write_misc() self.write('\n') ############################################################################ - ## Subprograms + # Subprograms def sub_begin(self, id, name=''): self.write((iso.codes.PROGRAM() % id) + iso.codes.SPACE() + (iso.codes.COMMENT(name))) @@ -109,7 +112,7 @@ def sub_end(self): self.write(iso.codes.SUBPROG_END() + '\n') ############################################################################ - ## Settings + # Settings def imperial(self): self.g += iso.codes.IMPERIAL() @@ -128,22 +131,33 @@ def incremental(self): self.absolute_flag = False def polar(self, on=True): - if (on) : self.g += iso.codes.POLAR_ON() - else : self.g += iso.codes.POLAR_OFF() + if (on): + self.g += iso.codes.POLAR_ON() + else: + self.g += iso.codes.POLAR_OFF() def set_plane(self, plane): - if (plane == 0) : self.g += iso.codes.PLANE_XY() - elif (plane == 1) : self.g += iso.codes.PLANE_XZ() - elif (plane == 2) : self.g += iso.codes.PLANE_YZ() + if (plane == 0): + self.g += iso.codes.PLANE_XY() + elif (plane == 1): + self.g += iso.codes.PLANE_XZ() + elif (plane == 2): + self.g += iso.codes.PLANE_YZ() def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write((iso.codes.SET_TEMPORARY_COORDINATE_SYSTEM())) - if (x != None): self.write( iso.codes.SPACE() + 'X ' + (self.fmt % x) ) - if (y != None): self.write( iso.codes.SPACE() + 'Y ' + (self.fmt % y) ) - if (z != None): self.write( iso.codes.SPACE() + 'Z ' + (self.fmt % z) ) - if (a != None): self.write( iso.codes.SPACE() + 'A ' + (self.fmt % a) ) - if (b != None): self.write( iso.codes.SPACE() + 'B ' + (self.fmt % b) ) - if (c != None): self.write( iso.codes.SPACE() + 'C ' + (self.fmt % c) ) + if (x != None): + self.write(iso.codes.SPACE() + 'X ' + (self.fmt % x)) + if (y != None): + self.write(iso.codes.SPACE() + 'Y ' + (self.fmt % y)) + if (z != None): + self.write(iso.codes.SPACE() + 'Z ' + (self.fmt % z)) + if (a != None): + self.write(iso.codes.SPACE() + 'A ' + (self.fmt % a)) + if (b != None): + self.write(iso.codes.SPACE() + 'B ' + (self.fmt % b)) + if (c != None): + self.write(iso.codes.SPACE() + 'C ' + (self.fmt % c)) self.write('\n') def remove_temporary_origin(self): @@ -151,7 +165,7 @@ def remove_temporary_origin(self): self.write('\n') ############################################################################ - ## Tools + # Tools def tool_change(self, id): self.write((iso.codes.TOOL() % id) + '\n') @@ -167,7 +181,7 @@ def offset_length(self, id, length=None): pass ############################################################################ - ## Datums + # Datums def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): pass @@ -181,8 +195,8 @@ def workplane(self, id): if ((id >= 1) and (id <= 6)): self.g += iso.codes.WORKPLANE() % (id + iso.codes.WORKPLANE_BASE()) if ((id >= 7) and (id <= 9)): - self.g += ((iso.codes.WORKPLANE() % (6 + iso.codes.WORKPLANE_BASE())) + ('.%i' % (id - 6))) - + self.g += ((iso.codes.WORKPLANE() % + (6 + iso.codes.WORKPLANE_BASE())) + ('.%i' % (id - 6))) ############################################################################ ## Rates + Modes @@ -205,7 +219,8 @@ def calc_feedrate_hv(self, h, v): self.f = iso.codes.FEEDRATE() + (self.ffmt % self.fh) def spindle(self, s, clockwise): - if s < 0: clockwise = not clockwise + if s < 0: + clockwise = not clockwise s = abs(s) self.s = iso.codes.SPINDLE(iso.codes.FORMAT_ANG(), s) if clockwise: @@ -214,16 +229,21 @@ def spindle(self, s, clockwise): self.s = self.s + iso.codes.SPINDLE_CCW() def coolant(self, mode=0): - if (mode <= 0) : self.m.append(iso.codes.COOLANT_OFF()) - elif (mode == 1) : self.m.append(iso.codes.COOLANT_MIST()) - elif (mode == 2) : self.m.append(iso.codes.COOLANT_FLOOD()) + if (mode <= 0): + self.m.append(iso.codes.COOLANT_OFF()) + elif (mode == 1): + self.m.append(iso.codes.COOLANT_MIST()) + elif (mode == 2): + self.m.append(iso.codes.COOLANT_FLOOD()) def gearrange(self, gear=0): - if (gear <= 0) : self.m.append(iso.codes.GEAR_OFF()) - elif (gear <= 4) : self.m.append(iso.codes.GEAR() % (gear + GEAR_BASE())) + if (gear <= 0): + self.m.append(iso.codes.GEAR_OFF()) + elif (gear <= 4): + self.m.append(iso.codes.GEAR() % (gear + GEAR_BASE())) ############################################################################ - ## Moves + # Moves def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write_blocknum() @@ -237,7 +257,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.X() + (self.fmt % (y*2))) else: self.write(iso.codes.X() + (self.fmt % (dy*2))) @@ -246,16 +266,15 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.Z() + (self.fmt % x)) else: self.write(iso.codes.Z() + (self.fmt % dx)) self.x = x - if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): pass #self.write(iso.codes.Z() + (self.fmt % z)) else: @@ -266,7 +285,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (a != None): da = a - self.a - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.A() + (self.fmt % a)) else: self.write(iso.codes.A() + (self.fmt % da)) @@ -274,7 +293,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (b != None): db = b - self.b - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.B() + (self.fmt % b)) else: self.write(iso.codes.B() + (self.fmt % db)) @@ -282,7 +301,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (c != None): dc = c - self.c - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.C() + (self.fmt % c)) else: self.write(iso.codes.C() + (self.fmt % dc)) @@ -292,7 +311,8 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write('\n') def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - if self.same_xyz(x, y, z): return + if self.same_xyz(x, y, z): + return self.write_blocknum() if self.g0123_modal: if self.prev_g0123 != iso.codes.FEED(): @@ -305,7 +325,7 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.X() + (self.fmt % (y*2))) else: self.write(iso.codes.X() + (self.fmt % (dy*2))) @@ -314,7 +334,7 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.Z() + (self.fmt % x)) else: self.write(iso.codes.Z() + (self.fmt % dx)) @@ -322,7 +342,7 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): pass #self.write(iso.codes.Z() + (self.fmt % z)) else: @@ -330,7 +350,8 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): #self.write(iso.codes.Z() + (self.fmt % dz)) self.z = z - if (self.fhv) : self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) + if (self.fhv): + self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) self.write_feedrate() self.write_spindle() self.write_misc() @@ -350,11 +371,14 @@ def same_xyz(self, x=None, y=None, z=None): return True def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - if self.same_xyz(x, y, z): return + if self.same_xyz(x, y, z): + return self.write_blocknum() arc_g_code = '' - if cw: arc_g_code = iso.codes.ARC_CW() - else: arc_g_code = iso.codes.ARC_CCW() + if cw: + arc_g_code = iso.codes.ARC_CW() + else: + arc_g_code = iso.codes.ARC_CCW() if self.g0123_modal: if self.prev_g0123 != arc_g_code: self.write(arc_g_code) @@ -365,23 +389,23 @@ def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): # make X take y values and multiply by 2 for diameter values for lathe if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.X() + (self.fmt % (y*2))) else: self.write(iso.codes.X() + (self.fmt % (dy*2))) self.y = y -#make Z take x values for lathe +# make Z take x values for lathe if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(iso.codes.Z() + (self.fmt % x)) else: self.write(iso.codes.Z() + (self.fmt % dx)) self.x = x if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): pass #self.write(iso.codes.X() + (self.fmt % z)) else: @@ -389,12 +413,17 @@ def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): #self.write(iso.codes.X() + (self.fmt % dz)) self.z = z - if (j != None) : self.write(iso.codes.CENTRE_X() + (self.fmt % j)) #change the order - if (i != None) : self.write(iso.codes.CENTRE_Z() + (self.fmt % i)) #and reversed i and j - if (k != None) :pass # self.write(iso.codes.CENTRE_Z() + (self.fmt % k)) - if (r != None) : self.write(iso.codes.RADIUS() + (self.fmt % r)) + if (j != None): + self.write(iso.codes.CENTRE_X() + (self.fmt % j)) # change the order + if (i != None): + self.write(iso.codes.CENTRE_Z() + (self.fmt % i)) # and reversed i and j + if (k != None): + pass # self.write(iso.codes.CENTRE_Z() + (self.fmt % k)) + if (r != None): + self.write(iso.codes.RADIUS() + (self.fmt % r)) # use horizontal feed rate - if (self.fhv) : self.calc_feedrate_hv(1, 0) + if (self.fhv): + self.calc_feedrate_hv(1, 0) self.write_feedrate() self.write_spindle() self.write_misc() @@ -424,12 +453,12 @@ def set_machine_coordinates(self): self.prev_g0123 = '' ############################################################################ - ## CRC + # CRC def use_CRC(self): return self.useCrc - def start_CRC(self, left = True, radius = 0.0): + def start_CRC(self, left=True, radius=0.0): # set up prep code, to be output on next line if self.t == None: raise "No tool specified for start_CRC()" @@ -443,12 +472,11 @@ def end_CRC(self): self.write('\n') ############################################################################ - ## Cycles + # Cycles def pattern(self): pass - def profile(self): pass @@ -461,7 +489,7 @@ def end_canned_cycle(self): self.prev_f = '' self.prev_retract = '' ############################################################################ - ## Misc + # Misc def comment(self, text): self.write((iso.codes.COMMENT(text) + '\n')) @@ -481,4 +509,5 @@ def variable_set(self, id, value): ################################################################################ + nc.creator = CreatorIso() diff --git a/scripts/addons/cam/nc/lathe1_read.py b/scripts/addons/cam/nc/lathe1_read.py index 6d7892f77..8297825b0 100644 --- a/scripts/addons/cam/nc/lathe1_read.py +++ b/scripts/addons/cam/nc/lathe1_read.py @@ -10,31 +10,34 @@ import sys ################################################################################ + + class Parser(nc.Parser): def __init__(self, writer): nc.Parser.__init__(self, writer) - self.pattern_main = re.compile('([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') + self.pattern_main = re.compile( + '([(!;].*|\s+|[a-zA-Z0-9_:](?:[+-])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+-])?\d*(?:\.\d*)?)') - #if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point - # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal + # if ( or ! or ; at least one space or a letter followed by some character or not followed by a +/- followed by decimal, with a possible decimal point + # followed by a possible deimcal, or a letter followed by # with a decimal . deimcal # add your character here > [(!;] for comments char # then look for the 'comment' function towards the end of the file and add another elif def Parse(self, name, oname=None): - self.files_open(name,oname) + self.files_open(name, oname) - #self.begin_ncblock() - #self.begin_path(None) - #self.add_line(z=500) - #self.end_path() - #self.end_ncblock() + # self.begin_ncblock() + # self.begin_path(None) + # self.add_line(z=500) + # self.end_path() + # self.end_ncblock() path_col = None f = None arc = 0 - uw = 0 + uw = 0 while (self.readline()): a = None @@ -113,7 +116,7 @@ def Parse(self, name, oname=None): path_col = "feed" col = "feed" elif (word == 'G82' or word == 'g82'): - drill = True; + drill = True no_move = True path_col = "feed" col = "feed" @@ -126,7 +129,8 @@ def Parse(self, name, oname=None): self.absolute() elif (word == 'G91' or word == 'g91'): self.incremental() - elif (word[0] == 'G') : col = "prep" + elif (word[0] == 'G'): + col = "prep" elif (word[0] == 'I' or word[0] == 'i'): col = "axis" i = eval(word[1:]) @@ -139,9 +143,12 @@ def Parse(self, name, oname=None): col = "axis" k = eval(word[1:]) move = True - elif (word[0] == 'M') : col = "misc" - elif (word[0] == 'N') : col = "blocknum" - elif (word[0] == 'O') : col = "program" + elif (word[0] == 'M'): + col = "misc" + elif (word[0] == 'N'): + col = "blocknum" + elif (word[0] == 'O'): + col = "program" elif (word[0] == 'P' or word[0] == 'p'): col = "axis" p = eval(word[1:]) @@ -158,9 +165,9 @@ def Parse(self, name, oname=None): col = "axis" s = eval(word[1:]) move = True - elif (word[0] == 'T') : + elif (word[0] == 'T'): col = "tool" - self.set_tool( eval(word[1:]) ) + self.set_tool(eval(word[1:])) elif (word[0] == 'X' or word[0] == 'x'): col = "axis" x = eval(word[1:]) @@ -173,24 +180,30 @@ def Parse(self, name, oname=None): col = "axis" u = eval(word[1:]) move = True - uw=1 + uw = 1 elif (word[0] == 'W' or word[0] == 'w'): col = "axis" w = eval(word[1:]) move = True - uw=1 + uw = 1 elif (word[0] == 'Z' or word[0] == 'z'): col = "axis" z = eval(word[1:]) move = True - elif (word[0] == '(') : (col, cdata) = ("comment", True) - elif (word[0] == '!') : (col, cdata) = ("comment", True) - elif (word[0] == ';') : (col, cdata) = ("comment", True) - elif (word[0] == '#') : col = "variable" - elif (word[0] == ':') : col = "blocknum" - elif (ord(word[0]) <= 32) : cdata = True + elif (word[0] == '('): + (col, cdata) = ("comment", True) + elif (word[0] == '!'): + (col, cdata) = ("comment", True) + elif (word[0] == ';'): + (col, cdata) = ("comment", True) + elif (word[0] == '#'): + col = "variable" + elif (word[0] == ':'): + col = "blocknum" + elif (ord(word[0]) <= 32): + cdata = True self.add_text(word, col, cdata) if (drill): @@ -208,17 +221,18 @@ def Parse(self, name, oname=None): else: if (move and not no_move): self.begin_path(path_col) - if (arc==-1): + if (arc == -1): self.add_arc(x, y, z, i, j, k, r, arc) - elif (arc==1): - #self.add_arc(x, y, z, i, j, k, -r, arc) #if you want to use arcs with R values uncomment the first part of this line and comment the next one + elif (arc == 1): + # self.add_arc(x, y, z, i, j, k, -r, arc) #if you want to use arcs with R values uncomment the first part of this line and comment the next one self.add_arc(x, y, z, i, j, k, r, arc) - #else : self.add_line(x, y, z, a, b, c) - elif(uw==1): - self.add_lathe_increment_line(u,w) + # else : self.add_line(x, y, z, a, b, c) + elif(uw == 1): + self.add_lathe_increment_line(u, w) - else : self.add_line(x, y, z, a, b, c) - self.end_path() + else: + self.add_line(x, y, z, a, b, c) + self.end_path() self.end_ncblock() diff --git a/scripts/addons/cam/nc/lynx_otter_o.py b/scripts/addons/cam/nc/lynx_otter_o.py index bf4301f02..4680ef18c 100644 --- a/scripts/addons/cam/nc/lynx_otter_o.py +++ b/scripts/addons/cam/nc/lynx_otter_o.py @@ -3,49 +3,50 @@ class Creator(iso.Creator): - def __init__(self): - iso.Creator.__init__(self) + def __init__(self): + iso.Creator.__init__(self) - def SPACE_STR(self): return(' ') + def SPACE_STR(self): return(' ') - def COMMENT(self, comment): return('') + def COMMENT(self, comment): return('') - def PROGRAM(self): return(None) + def PROGRAM(self): return(None) - def FORMAT_DWELL(self): return( self.SPACE() + self.DWELL() + ' X%f') - def SPINDLE_OFF(self): return('M05\n') - #optimize - def RAPID(self): return('G00') - def FEED(self): return('G01') + def FORMAT_DWELL(self): return(self.SPACE() + self.DWELL() + ' X%f') + def SPINDLE_OFF(self): return('M05\n') + # optimize + def RAPID(self): return('G00') + def FEED(self): return('G01') - # def IMPERIAL(self): return('G20\n') - # def METRIC(self): return('G21\n') - # def ABSOLUTE(self): return('G90\n') - # def INCREMENTAL(self): return('G91\n') - # def PLANE_XY(self): return('17\n') - # def PLANE_XZ(self): return('18\n') - # def PLANE_YZ(self): return('19\n') + # def IMPERIAL(self): return('G20\n') + # def METRIC(self): return('G21\n') + # def ABSOLUTE(self): return('G90\n') + # def INCREMENTAL(self): return('G91\n') + # def PLANE_XY(self): return('17\n') + # def PLANE_XZ(self): return('18\n') + # def PLANE_YZ(self): return('19\n') - def dwell(self, t): - pass - """ + def dwell(self, t): + pass + """ self.write_blocknum() self.write_preps() self.write(self.FORMAT_DWELL() % t) self.write_misc() self.write('\n') """ - def tool_change(self, id): - pass - # self.write_blocknum() - # self.write(self.SPACE() + (self.TOOL() % id) + '\n') - # self.write_blocknum() - # self.write(self.SPACE() + self.s.str) - # self.write('\n') - # self.flush_nc() - # self.t = id - - def PROGRAM_END(self): return( self.SPACE() + self.SPINDLE_OFF() + self.SPACE() + 'M30') + + def tool_change(self, id): + pass + # self.write_blocknum() + # self.write(self.SPACE() + (self.TOOL() % id) + '\n') + # self.write_blocknum() + # self.write(self.SPACE() + self.s.str) + # self.write('\n') + # self.flush_nc() + # self.t = id + + def PROGRAM_END(self): return(self.SPACE() + self.SPINDLE_OFF() + self.SPACE() + 'M30') nc.creator = Creator() diff --git a/scripts/addons/cam/nc/mach3.py b/scripts/addons/cam/nc/mach3.py index 25d608586..f2fc8f3b7 100644 --- a/scripts/addons/cam/nc/mach3.py +++ b/scripts/addons/cam/nc/mach3.py @@ -1,19 +1,21 @@ from . import nc from . import iso + class Creator(iso.Creator): - def __init__(self): - iso.Creator.__init__(self) + def __init__(self): + iso.Creator.__init__(self) + + def SPACE_STR(self): return(' ') - def SPACE_STR(self): return(' ') + def program_begin(self, id, comment): + self.write(('(' + 'GCode created using the HeeksCNC Mach3 post processor' + ')' + '\n')) + self.write(('(' + comment + ')' + '\n')) - def program_begin(self, id, comment): - self.write( ('(' + 'GCode created using the HeeksCNC Mach3 post processor' + ')' + '\n') ) - self.write( ('(' + comment + ')' + '\n') ) + def tool_change(self, id): + self.write('G43H%i' % id + '\n') + self.write((self.TOOL() % id) + '\n') + self.t = id - def tool_change(self, id): - self.write('G43H%i'% id +'\n') - self.write((self.TOOL() % id) + '\n') - self.t = id nc.creator = Creator() diff --git a/scripts/addons/cam/nc/mach3_read.py b/scripts/addons/cam/nc/mach3_read.py index 3a830598d..00575a3c3 100644 --- a/scripts/addons/cam/nc/mach3_read.py +++ b/scripts/addons/cam/nc/mach3_read.py @@ -2,6 +2,8 @@ from . import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/makerbotHBP.py b/scripts/addons/cam/nc/makerbotHBP.py index 0a5ad904a..49db7118d 100644 --- a/scripts/addons/cam/nc/makerbotHBP.py +++ b/scripts/addons/cam/nc/makerbotHBP.py @@ -8,6 +8,8 @@ now = datetime.datetime.now() ################################################################################ + + class CreatorMakerbotHBP(iso_modal.CreatorIsoModal): def __init__(self): iso_modal.CreatorIsoModal.__init__(self) @@ -19,33 +21,35 @@ def __init__(self): ################################################################################ # program begin and end + def program_begin(self, id, name=''): self.write((maker.codes.COMMENT(now))) - self.write((maker.codes.EXTRUDER_TEMP('220')) + (maker.codes.COMMENT('Extruder Temp')) ) - self.write((maker.codes.BUILD_BED_TEMP('110'))+ (maker.codes.COMMENT('Build Bed Temp')) ) - self.write((maker.codes.FAN_OFF()) + (maker.codes.COMMENT('Fan Off')) ) - self.write((maker.codes.METRIC()) + (maker.codes.COMMENT('Metric units')) ) - self.write((maker.codes.ABSOLUTE()) + (maker.codes.COMMENT('Absolute units')) ) + self.write((maker.codes.EXTRUDER_TEMP('220')) + (maker.codes.COMMENT('Extruder Temp'))) + self.write((maker.codes.BUILD_BED_TEMP('110')) + (maker.codes.COMMENT('Build Bed Temp'))) + self.write((maker.codes.FAN_OFF()) + (maker.codes.COMMENT('Fan Off'))) + self.write((maker.codes.METRIC()) + (maker.codes.COMMENT('Metric units'))) + self.write((maker.codes.ABSOLUTE()) + (maker.codes.COMMENT('Absolute units'))) self.write('G92 X0 Y0 Z0 (You are now at 0,0,0)\n') self.write('G0 Z15 (Move up for warmup)\n') - self.write((maker.codes.EXTRUDER_SPEED_PWM('255')) + (maker.codes.COMMENT('Extruder Speed')) ) + self.write((maker.codes.EXTRUDER_SPEED_PWM('255')) + + (maker.codes.COMMENT('Extruder Speed'))) self.write('M6 T0 (Wait for tool to heat up)\n') self.write('G04 P5000 (Wait 5 seconds)\n') - self.write((maker.codes.EXTRUDER_ON_FWD()) + (maker.codes.COMMENT('Extruder On')) ) + self.write((maker.codes.EXTRUDER_ON_FWD()) + (maker.codes.COMMENT('Extruder On'))) self.write('G04 P5000 (Wait 5 seconds)\n') - self.write((maker.codes.EXTRUDER_OFF()) + (maker.codes.COMMENT('Extruder Off')) ) + self.write((maker.codes.EXTRUDER_OFF()) + (maker.codes.COMMENT('Extruder Off'))) self.write('M01 (The heated build platform is heating up. Wait until after the lights have turned off for the first time, clear the test extrusion, and click yes.)\n') self.write('G0 Z0 (Go back to zero.)\n') def program_end(self): self.write((maker.codes.COMMENT('End of the file. Begin cool-down'))) - self.write((maker.codes.EXTRUDER_TEMP('0')) + (maker.codes.COMMENT('Extruder Temp')) ) - self.write((maker.codes.BUILD_BED_TEMP('0')) + (maker.codes.COMMENT('Build Bed Temp')) ) - self.write((maker.codes.FAN_ON()) + (maker.codes.COMMENT('Fan On')) ) + self.write((maker.codes.EXTRUDER_TEMP('0')) + (maker.codes.COMMENT('Extruder Temp'))) + self.write((maker.codes.BUILD_BED_TEMP('0')) + (maker.codes.COMMENT('Build Bed Temp'))) + self.write((maker.codes.FAN_ON()) + (maker.codes.COMMENT('Fan On'))) self.write('G92 Z0 (zero our z axis - hack b/c skeinforge mangles gcodes in end.txt)\n') self.write('G1 Z10 (go up 10 b/c it was zeroed earlier.)\n') self.write('G1 X0 Y0 Z10 (go to 0,0,z)\n') - self.write((maker.codes.STEPPERS_OFF()) + (maker.codes.COMMENT('Steppers Off')) ) + self.write((maker.codes.STEPPERS_OFF()) + (maker.codes.COMMENT('Steppers Off'))) def program_stop(self): self.write((maker.codes.EXTRUDER_TEMP('0'))) @@ -58,7 +62,7 @@ def write_blocknum(self): pass def set_plane(self, plane): - pass + pass def workplane(self, id): pass @@ -69,32 +73,32 @@ def spindle(self, s, clockwise): # Extruder Control def extruder_on(self): - self.write((maker.codes.EXTRUDER_ON()) + ('\n')) + self.write((maker.codes.EXTRUDER_ON()) + ('\n')) def extruder_off(self): - self.write((maker.codes.EXTRUDER_OFF()) + ('\n')) + self.write((maker.codes.EXTRUDER_OFF()) + ('\n')) def set_extruder_flowrate(self, flowrate): - self.write((maker.codes.EXTRUDER_SPEED_PWM(flowrate)) + ('\n')) + self.write((maker.codes.EXTRUDER_SPEED_PWM(flowrate)) + ('\n')) def extruder_temp(self, temp): - self.write((maker.codes.EXTRUDER_TEMP(temp)) + ('\n')) + self.write((maker.codes.EXTRUDER_TEMP(temp)) + ('\n')) ################################################################################ # Build Environment Control def build_bed_temp(self, temp): - self.write((maker.codes.BUILD_BED_TEMP(temp)) + ('\n')) + self.write((maker.codes.BUILD_BED_TEMP(temp)) + ('\n')) def chamber_temp(self, temp): - self.write((maker.codes.CHAMBER_TEMP(temp)) + ('\n')) + self.write((maker.codes.CHAMBER_TEMP(temp)) + ('\n')) ################################################################################ # Fan Control def fan_on(self): - self.write((maker.codes.FAN_ON()) + ('\n')) + self.write((maker.codes.FAN_ON()) + ('\n')) def fan_off(self): - self.write((maker.codes.FAN_OFF()) + ('\n')) + self.write((maker.codes.FAN_OFF()) + ('\n')) ################################################################################ # Custom routines @@ -111,15 +115,15 @@ def insert(self, text): ################################################################################ # tool info def tool_change(self, id): - pass + pass # self.write_blocknum() # self.write((maker.codes.TOOL() % id) + '\n') # self.t = id def tool_defn(self, id, name='', params=None): - pass + pass ############################################################################ -## Moves +# Moves def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write_blocknum() @@ -132,14 +136,14 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write_preps() if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.X() + (self.fmt % x)) else: self.write(maker.codes.X() + (self.fmt % dx)) self.x = x if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Y() + (self.fmt % y)) else: self.write(maker.codes.Y() + (self.fmt % dy)) @@ -147,7 +151,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.y = y if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Z() + (self.fmt % z)) else: self.write(maker.codes.Z() + (self.fmt % dz)) @@ -156,7 +160,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (a != None): da = a - self.a - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.A() + (self.fmt % a)) else: self.write(maker.codes.A() + (self.fmt % da)) @@ -164,7 +168,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (b != None): db = b - self.b - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.B() + (self.fmt % b)) else: self.write(maker.codes.B() + (self.fmt % db)) @@ -172,7 +176,7 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): if (c != None): dc = c - self.c - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.C() + (self.fmt % c)) else: self.write(maker.codes.C() + (self.fmt % dc)) @@ -182,7 +186,8 @@ def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write('\n') def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - if self.same_xyz(x, y, z): return + if self.same_xyz(x, y, z): + return self.write_blocknum() if self.g0123_modal: if self.prev_g0123 != maker.codes.FEED(): @@ -194,14 +199,14 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): dx = dy = dz = 0 if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.X() + (self.fmt % x)) else: self.write(maker.codes.X() + (self.fmt % dx)) self.x = x if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Y() + (self.fmt % y)) else: self.write(maker.codes.Y() + (self.fmt % dy)) @@ -209,13 +214,14 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): self.y = y if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Z() + (self.fmt % z)) else: self.write(maker.codes.Z() + (self.fmt % dz)) self.z = z - if (self.fhv) : self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) + if (self.fhv): + self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) self.write_feedrate() self.write_spindle() self.write_misc() @@ -235,11 +241,14 @@ def same_xyz(self, x=None, y=None, z=None): return True def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - if self.same_xyz(x, y, z): return + if self.same_xyz(x, y, z): + return self.write_blocknum() arc_g_code = '' - if cw: arc_g_code = maker.codes.ARC_CW() - else: arc_g_code = maker.codes.ARC_CCW() + if cw: + arc_g_code = maker.codes.ARC_CW() + else: + arc_g_code = maker.codes.ARC_CCW() if self.g0123_modal: if self.prev_g0123 != arc_g_code: self.write(arc_g_code) @@ -249,31 +258,36 @@ def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.write_preps() if (x != None): dx = x - self.x - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.X() + (self.fmt % x)) else: self.write(maker.codes.X() + (self.fmt % dx)) self.x = x if (y != None): dy = y - self.y - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Y() + (self.fmt % y)) else: self.write(maker.codes.Y() + (self.fmt % dy)) self.y = y if (z != None): dz = z - self.z - if (self.absolute_flag ): + if (self.absolute_flag): self.write(maker.codes.Z() + (self.fmt % z)) else: self.write(maker.codes.Z() + (self.fmt % dz)) self.z = z - if (i != None) : self.write(maker.codes.CENTRE_X() + (self.fmt % i)) - if (j != None) : self.write(maker.codes.CENTRE_Y() + (self.fmt % j)) - if (k != None) : self.write(maker.codes.CENTRE_Z() + (self.fmt % k)) - if (r != None) : self.write(maker.codes.RADIUS() + (self.fmt % r)) + if (i != None): + self.write(maker.codes.CENTRE_X() + (self.fmt % i)) + if (j != None): + self.write(maker.codes.CENTRE_Y() + (self.fmt % j)) + if (k != None): + self.write(maker.codes.CENTRE_Z() + (self.fmt % k)) + if (r != None): + self.write(maker.codes.RADIUS() + (self.fmt % r)) # use horizontal feed rate - if (self.fhv) : self.calc_feedrate_hv(1, 0) + if (self.fhv): + self.calc_feedrate_hv(1, 0) self.write_feedrate() self.write_spindle() self.write_misc() @@ -302,4 +316,5 @@ def set_machine_coordinates(self): self.write(maker.codes.MACHINE_COORDINATES()) self.prev_g0123 = '' + nc.creator = CreatorMakerbotHBP() diff --git a/scripts/addons/cam/nc/makerbotHBP_read.py b/scripts/addons/cam/nc/makerbotHBP_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/makerbotHBP_read.py +++ b/scripts/addons/cam/nc/makerbotHBP_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/makerbot_codes.py b/scripts/addons/cam/nc/makerbot_codes.py index 14b8b3366..814f1a6f3 100644 --- a/scripts/addons/cam/nc/makerbot_codes.py +++ b/scripts/addons/cam/nc/makerbot_codes.py @@ -8,121 +8,122 @@ # Many of these codes have nothing to do with reprap/additive machining but are left here in anticipation of future hybrid machines. class Codes(): - def SPACE(self): return(' ') - def FORMAT_FEEDRATE(self): return('%.2f') - def FORMAT_IN(self): return('%.5f') - def FORMAT_MM(self): return('%.3f') - def FORMAT_ANG(self): return('%.1f') - def FORMAT_TIME(self): return('%.2f') - def FORMAT_DWELL(self): return('P%f') - - def BLOCK(self): return('N%i' + self.SPACE()) - def COMMENT(self,comment): return( (' (%s)\n' % comment ) ) - def VARIABLE(self): return( '#%i') - def VARIABLE_SET(self): return( '=%.3f') - - def PROGRAM(self): return( 'O%i') - def PROGRAM_END(self): return( 'M02') - - def SUBPROG_CALL(self): return( 'M98' + self.SPACE() + 'P%i') - def SUBPROG_END(self): return( 'M99') - - def STOP_OPTIONAL(self): return('M01') - def STOP(self): return('M00') - - def IMPERIAL(self): return(self.SPACE() + 'G20') - def METRIC(self): return(self.SPACE() + 'G21' + self.SPACE()) - def ABSOLUTE(self): return(self.SPACE() + 'G90' + self.SPACE()) - def INCREMENTAL(self): return(self.SPACE() + 'G91') - def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92' + self.SPACE()) - def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1' + self.SPACE()) - def POLAR_ON(self): return(self.SPACE() + 'G16') - def POLAR_OFF(self): return(self.SPACE() + 'G15') - def PLANE_XY(self): return(self.SPACE() + 'G17') - def PLANE_XZ(self): return(self.SPACE() + 'G18') - def PLANE_YZ(self): return(self.SPACE() + 'G19') - - def TOOL(self): return(self.SPACE() +'T%i') - def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1' + self.SPACE()) - - def WORKPLANE(self): return('G%i') - def WORKPLANE_BASE(self): return(53) - - def FEEDRATE(self): return((self.SPACE() + ' F')) - def SPINDLE(self, format, speed): return(self.SPACE() + 'S' + (format % speed)) - def SPINDLE_CW(self): return(self.SPACE() + 'M03') - def SPINDLE_CCW(self): return(self.SPACE() + 'M04') - def COOLANT_OFF(self): return(self.SPACE() + 'M09') - def COOLANT_MIST(self): return(self.SPACE() + 'M07') - def COOLANT_FLOOD(self): return(self.SPACE() + 'M08') - def GEAR_OFF(self): return(self.SPACE() + '?') - def GEAR(self): return('M%i') - def GEAR_BASE(self): return(37) - - def RAPID(self): return('G0') - def FEED(self): return('G1') - def ARC_CW(self): return('G2') - def ARC_CCW(self): return('G3') - def DWELL(self): return('G04') - def DRILL(self): return(self.SPACE() + 'G81') - def DRILL_WITH_DWELL(self, format, dwell): return(self.SPACE() + 'G82' + (format % dwell)) - def PECK_DRILL(self): return(self.SPACE() + 'G83') - def PECK_DEPTH(self, format, depth): return(self.SPACE() + 'Q' + (format % depth)) - def RETRACT(self, format, height): return(self.SPACE() + 'R' + (format % height)) - def END_CANNED_CYCLE(self): return(self.SPACE() + 'G80') - - def X(self): return(self.SPACE() + 'X') - def Y(self): return(self.SPACE() + 'Y') - def Z(self): return(self.SPACE() + 'Z') - def A(self): return(self.SPACE() + 'A') - def B(self): return(self.SPACE() + 'B') - def C(self): return(self.SPACE() + 'C') - def CENTRE_X(self): return(self.SPACE() + 'I') - def CENTRE_Y(self): return(self.SPACE() + 'J') - def CENTRE_Z(self): return(self.SPACE() + 'K') - def RADIUS(self): return(self.SPACE() + 'R') - def TIME(self): return(self.SPACE() + 'P') - - def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2' + self.SPACE()) - def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3' + self.SPACE()) - def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4' + self.SPACE()) - def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5' + self.SPACE()) - - def MACHINE_COORDINATES(self): return('G53' + self.SPACE()) - - def EXTRUDER_ON (self): return('M101') #deprecated - def EXTRUDER_OFF (self): return('M103') - def EXTRUDER_TEMP (self, degree_celsius): return('M104 S' + '%s' % degree_celsius) - def EXTRUDER_TEMP_WAIT (self, degree_celsius): return('M109 S' + '%s' % degree_celsius) - def READ_EXTRUDER_TEMP (self): return('M105') - def EXTRUDER_SPEED_PWM (self, speed_in_PWM): return('M108 S' + '%s' % speed_in_PWM) #deprecated - def EXTRUDER_SPEED_RPM (self, speed_in_RPM): return('M108 P' + '%s' % speed_in_RPM) #deprecated - - def STEPPERS_OFF(self): return(self.SPACE() + 'M118') - - def ALL_WAIT (self): return(self.SPACE() + 'M116') # Wait for all temperature and slow-changing variables to reach set values - - def FAN_ON (self): return(self.SPACE() + 'M106') - def FAN_OFF (self): return(self.SPACE() + 'M107') - - def VALVE_OPEN (self, delay): return(self.SPACE() + ('M126 P' + '%' % delay) ) - def VALVE_CLOSE (self, delay): return(self.SPACE() + ('M127 P' + '%' % delay) ) - - def BUILD_BED_TEMP (self, degree_celsius): return('M140 S' + '%s' % degree_celsius) - def BED_HOLDING_PRESSURE (self, pressure): return('M142 S' + '%s' % pressure) - - def CHAMBER_TEMP (self, degree_celsius): return('M141 S' + '%s' % degree_celsius) - -#The following codes are listed on the reprap wiki page at http://reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes but require more study. + def SPACE(self): return(' ') + def FORMAT_FEEDRATE(self): return('%.2f') + def FORMAT_IN(self): return('%.5f') + def FORMAT_MM(self): return('%.3f') + def FORMAT_ANG(self): return('%.1f') + def FORMAT_TIME(self): return('%.2f') + def FORMAT_DWELL(self): return('P%f') + + def BLOCK(self): return('N%i' + self.SPACE()) + def COMMENT(self, comment): return((' (%s)\n' % comment)) + def VARIABLE(self): return('#%i') + def VARIABLE_SET(self): return('=%.3f') + + def PROGRAM(self): return('O%i') + def PROGRAM_END(self): return('M02') + + def SUBPROG_CALL(self): return('M98' + self.SPACE() + 'P%i') + def SUBPROG_END(self): return('M99') + + def STOP_OPTIONAL(self): return('M01') + def STOP(self): return('M00') + + def IMPERIAL(self): return(self.SPACE() + 'G20') + def METRIC(self): return(self.SPACE() + 'G21' + self.SPACE()) + def ABSOLUTE(self): return(self.SPACE() + 'G90' + self.SPACE()) + def INCREMENTAL(self): return(self.SPACE() + 'G91') + def SET_TEMPORARY_COORDINATE_SYSTEM(self): return('G92' + self.SPACE()) + def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return('G92.1' + self.SPACE()) + def POLAR_ON(self): return(self.SPACE() + 'G16') + def POLAR_OFF(self): return(self.SPACE() + 'G15') + def PLANE_XY(self): return(self.SPACE() + 'G17') + def PLANE_XZ(self): return(self.SPACE() + 'G18') + def PLANE_YZ(self): return(self.SPACE() + 'G19') + + def TOOL(self): return(self.SPACE() + 'T%i') + def TOOL_DEFINITION(self): return('G10' + self.SPACE() + 'L1' + self.SPACE()) + + def WORKPLANE(self): return('G%i') + def WORKPLANE_BASE(self): return(53) + + def FEEDRATE(self): return((self.SPACE() + ' F')) + def SPINDLE(self, format, speed): return(self.SPACE() + 'S' + (format % speed)) + def SPINDLE_CW(self): return(self.SPACE() + 'M03') + def SPINDLE_CCW(self): return(self.SPACE() + 'M04') + def COOLANT_OFF(self): return(self.SPACE() + 'M09') + def COOLANT_MIST(self): return(self.SPACE() + 'M07') + def COOLANT_FLOOD(self): return(self.SPACE() + 'M08') + def GEAR_OFF(self): return(self.SPACE() + '?') + def GEAR(self): return('M%i') + def GEAR_BASE(self): return(37) + + def RAPID(self): return('G0') + def FEED(self): return('G1') + def ARC_CW(self): return('G2') + def ARC_CCW(self): return('G3') + def DWELL(self): return('G04') + def DRILL(self): return(self.SPACE() + 'G81') + def DRILL_WITH_DWELL(self, format, dwell): return(self.SPACE() + 'G82' + (format % dwell)) + def PECK_DRILL(self): return(self.SPACE() + 'G83') + def PECK_DEPTH(self, format, depth): return(self.SPACE() + 'Q' + (format % depth)) + def RETRACT(self, format, height): return(self.SPACE() + 'R' + (format % height)) + def END_CANNED_CYCLE(self): return(self.SPACE() + 'G80') + + def X(self): return(self.SPACE() + 'X') + def Y(self): return(self.SPACE() + 'Y') + def Z(self): return(self.SPACE() + 'Z') + def A(self): return(self.SPACE() + 'A') + def B(self): return(self.SPACE() + 'B') + def C(self): return(self.SPACE() + 'C') + def CENTRE_X(self): return(self.SPACE() + 'I') + def CENTRE_Y(self): return(self.SPACE() + 'J') + def CENTRE_Z(self): return(self.SPACE() + 'K') + def RADIUS(self): return(self.SPACE() + 'R') + def TIME(self): return(self.SPACE() + 'P') + + def PROBE_TOWARDS_WITH_SIGNAL(self): return('G38.2' + self.SPACE()) + def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return('G38.3' + self.SPACE()) + def PROBE_AWAY_WITH_SIGNAL(self): return('G38.4' + self.SPACE()) + def PROBE_AWAY_WITHOUT_SIGNAL(self): return('G38.5' + self.SPACE()) + + def MACHINE_COORDINATES(self): return('G53' + self.SPACE()) + + def EXTRUDER_ON(self): return('M101') # deprecated + def EXTRUDER_OFF(self): return('M103') + def EXTRUDER_TEMP(self, degree_celsius): return('M104 S' + '%s' % degree_celsius) + def EXTRUDER_TEMP_WAIT(self, degree_celsius): return('M109 S' + '%s' % degree_celsius) + def READ_EXTRUDER_TEMP(self): return('M105') + def EXTRUDER_SPEED_PWM(self, speed_in_PWM): return('M108 S' + '%s' % speed_in_PWM) # deprecated + def EXTRUDER_SPEED_RPM(self, speed_in_RPM): return('M108 P' + '%s' % speed_in_RPM) # deprecated + + def STEPPERS_OFF(self): return(self.SPACE() + 'M118') + + # Wait for all temperature and slow-changing variables to reach set values + def ALL_WAIT(self): return(self.SPACE() + 'M116') + + def FAN_ON(self): return(self.SPACE() + 'M106') + def FAN_OFF(self): return(self.SPACE() + 'M107') + + def VALVE_OPEN(self, delay): return(self.SPACE() + ('M126 P' + '%' % delay)) + def VALVE_CLOSE(self, delay): return(self.SPACE() + ('M127 P' + '%' % delay)) + + def BUILD_BED_TEMP(self, degree_celsius): return('M140 S' + '%s' % degree_celsius) + def BED_HOLDING_PRESSURE(self, pressure): return('M142 S' + '%s' % pressure) + + def CHAMBER_TEMP(self, degree_celsius): return('M141 S' + '%s' % degree_celsius) + +# The following codes are listed on the reprap wiki page at http://reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes but require more study. # -#G28 G Y Xnnn Ynnn Znnn Move to origin (on specified axes only, if X/Y/Z parameters are present) -#M105 M N none Request current extruder and base temperatures (in Celsius) -#M110 M N none Set current line number to Nxxx value preceeding command -#M111 M N Snnn Set debug level bitfield to value of parameter (default 6) -#M112 M N none Emergency stop (stop immediately, discarding any buffered commands) -#M113 M N Snnn Set Extruder PWM (to value defined by pot, or to parameter value if present) -#M114 M N none Get Current Position (return current X, Y, Z and E values) -#M117 M N none Get Zero Position (return X, Y, Z and E values of endstop hits) +# G28 G Y Xnnn Ynnn Znnn Move to origin (on specified axes only, if X/Y/Z parameters are present) +# M105 M N none Request current extruder and base temperatures (in Celsius) +# M110 M N none Set current line number to Nxxx value preceeding command +# M111 M N Snnn Set debug level bitfield to value of parameter (default 6) +# M112 M N none Emergency stop (stop immediately, discarding any buffered commands) +# M113 M N Snnn Set Extruder PWM (to value defined by pot, or to parameter value if present) +# M114 M N none Get Current Position (return current X, Y, Z and E values) +# M117 M N none Get Zero Position (return X, Y, Z and E values of endstop hits) codes = Codes() diff --git a/scripts/addons/cam/nc/nc.py b/scripts/addons/cam/nc/nc.py index c4632fa82..1860ad516 100644 --- a/scripts/addons/cam/nc/nc.py +++ b/scripts/addons/cam/nc/nc.py @@ -21,688 +21,785 @@ ncFLOOD = 2 ################################################################################ + + class Creator: - def __init__(self): - pass + def __init__(self): + pass + + ############################################################################ + # Internals + + def file_open(self, name): + # self.buffer=[] + self.file = open(name, 'w') + self.filename = name + + def file_close(self): + # self.file.write(''.join(self.buffer)) + # self.buffer=[] + self.file.close() + + def write(self, s): + self.file.write(s) + # self.buffer.append(s) + # if len(self.buffer)>100000: + # self.file.write(''.join(self.buffer)) + # self.buffer=[] + + def writem(self, a): + self.file.write(''.join(a)) + # self.buffer.extend(a) + ############################################################################ + # Programs + + def program_begin(self, id, name=''): + """Begin a program""" + pass + + def add_stock(self, type_name, params): + pass + + def program_stop(self, optional=False): + """Stop the machine""" + pass + + def program_end(self): + """End the program""" + pass + + def flush_nc(self): + """Flush all pending codes""" + pass + + ############################################################################ + # Subprograms + + def sub_begin(self, id, name=''): + """Begin a subprogram""" + pass + + def sub_call(self, id): + """Call a subprogram""" + pass + + def sub_end(self): + """Return from a subprogram""" + pass + + ############################################################################ + # Settings + + def imperial(self): + """Set imperial units""" + pass + + def metric(self): + """Set metric units""" + pass + + def absolute(self): + """Set absolute coordinates""" + pass + + def incremental(self): + """Set incremental coordinates""" + pass + + def polar(self, on=True): + """Set polar coordinates""" + pass + + def set_plane(self, plane): + """Set plane""" + pass + + def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Set temporary origin G92""" + pass + + def remove_temporary_origin(self): + """Remote temporary origin G92.1""" + pass + + ############################################################################ + # Tools + + def tool_change(self, id): + """Change the tool""" + pass + + def tool_defn(self, id, name='', params=None): + """Define a tool""" + pass + + def offset_radius(self, id, radius=None): + """Set tool radius offsetting""" + pass + + def offset_length(self, id, length=None): + """Set tool length offsetting""" + pass + + def current_tool(self): + return None + + ############################################################################ + # Datums + + def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Shift the datum""" + pass - ############################################################################ - ## Internals + def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Set the datum""" + pass - def file_open(self, name): - #self.buffer=[] - self.file = open(name, 'w') - self.filename = name + def workplane(self, id): + """Set the workplane""" + pass - def file_close(self): - #self.file.write(''.join(self.buffer)) - #self.buffer=[] - self.file.close() + def clearanceplane(self, z=None): + """set clearance plane""" + pass - def write(self, s): - self.file.write(s) - #self.buffer.append(s) - #if len(self.buffer)>100000: - # self.file.write(''.join(self.buffer)) - # self.buffer=[] + ############################################################################ + # APT360 like Transformation Definitions + # These definitions were created while looking at Irvin Kraal's book on APT + # - Numerical Control Progamming in APT - page 211 - def writem(self, a): - self.file.write(''.join(a)) - #self.buffer.extend(a) - ############################################################################ - ## Programs + def matrix(self, a1=None, b1=None, c1=None, a2=None, b2=None, c2=None, a3=None, b3=None, c3=None): + """Create a matrix for transformations""" + pass - def program_begin(self, id, name=''): - """Begin a program""" - pass + def translate(self, x=None, y=None, z=None): + """Translate in x,y,z direction""" + pass - def add_stock(self, type_name, params): - pass + def rotate(self, xyrot=None, yzrot=None, zxrot=None, angle=None): + """Rotate about a coordinate axis""" + pass - def program_stop(self, optional=False): - """Stop the machine""" - pass + def scale(self, k=None): + """Scale by factor k""" + pass - def program_end(self): - """End the program""" - pass + def matrix_product(self, matrix1=None, matrix2=None): + """Create matrix that is the product of two other matrices""" + pass - def flush_nc(self): - """Flush all pending codes""" - pass + def mirror_plane(self, plane1=None, plane2=None, plane3=None): + """Mirror image about one or more coordinate planes""" + pass - ############################################################################ - ## Subprograms - - def sub_begin(self, id, name=''): - """Begin a subprogram""" - pass - - def sub_call(self, id): - """Call a subprogram""" - pass - - def sub_end(self): - """Return from a subprogram""" - pass - - ############################################################################ - ## Settings - - def imperial(self): - """Set imperial units""" - pass - - def metric(self): - """Set metric units""" - pass - - def absolute(self): - """Set absolute coordinates""" - pass - - def incremental(self): - """Set incremental coordinates""" - pass - - def polar(self, on=True): - """Set polar coordinates""" - pass - - def set_plane(self, plane): - """Set plane""" - pass - - def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): - """Set temporary origin G92""" - pass - - def remove_temporary_origin(self): - """Remote temporary origin G92.1""" - pass - - ############################################################################ - ## Tools - - def tool_change(self, id): - """Change the tool""" - pass - - def tool_defn(self, id, name='', params=None): - """Define a tool""" - pass - - def offset_radius(self, id, radius=None): - """Set tool radius offsetting""" - pass - - def offset_length(self, id, length=None): - """Set tool length offsetting""" - pass - - def current_tool(self): - return None - - ############################################################################ - ## Datums - - def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): - """Shift the datum""" - pass - - def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): - """Set the datum""" - pass - - def workplane(self, id): - """Set the workplane""" - pass - - def clearanceplane(self,z=None): - """set clearance plane""" - pass - - ############################################################################ - ## APT360 like Transformation Definitions - ## These definitions were created while looking at Irvin Kraal's book on APT - ## - Numerical Control Progamming in APT - page 211 - - def matrix(self,a1=None,b1=None,c1=None,a2=None,b2=None,c2=None,a3=None,b3=None,c3=None): - """Create a matrix for transformations""" - pass - def translate(self,x=None,y=None,z=None): - """Translate in x,y,z direction""" - pass - def rotate(self,xyrot=None,yzrot=None,zxrot=None,angle=None): - """Rotate about a coordinate axis""" - pass - def scale(self,k=None): - """Scale by factor k""" - pass - def matrix_product(self,matrix1=None,matrix2=None): - """Create matrix that is the product of two other matrices""" - pass - def mirror_plane(self,plane1=None,plane2=None,plane3=None): - """Mirror image about one or more coordinate planes""" - pass - def mirror_line(self,line=None): - """Mirror about a line""" - pass - - - ############################################################################ - ## Rates + Modes - - def feedrate(self, f): - """Set the feedrate""" - pass - - def feedrate_hv(self, fh, fv): - """Set the horizontal and vertical feedrates""" - pass - - def spindle(self, s, clockwise=True): - """Set the spindle speed""" - pass - - def coolant(self, mode=0): - """Set the coolant mode""" - pass - - def gearrange(self, gear=0): - """Set the gear range""" - pass - - ############################################################################ - ## Moves - - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): - """Rapid move""" - pass - - def feed(self, x=None, y=None, z=None, a = None, b = None, c = None): - """Feed move""" - pass - - def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - """Clockwise arc move""" - pass - - def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - """Counterclockwise arc move""" - pass - - def dwell(self, t): - """Dwell""" - pass + def mirror_line(self, line=None): + """Mirror about a line""" + pass - def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): - """Rapid relative to home position""" - pass + ############################################################################ + ## Rates + Modes - def rapid_unhome(self): - """Return from rapid home""" - pass - - def set_machine_coordinates(self): - """Set machine coordinates""" - pass + def feedrate(self, f): + """Set the feedrate""" + pass - ############################################################################ - ## Cutter radius compensation + def feedrate_hv(self, fh, fv): + """Set the horizontal and vertical feedrates""" + pass - def use_CRC(self): - """CRC""" - return False + def spindle(self, s, clockwise=True): + """Set the spindle speed""" + pass - ############################################################################ - ## Cycles + def coolant(self, mode=0): + """Set the coolant mode""" + pass - def pattern(self): - """Simple pattern eg. circle, rect""" - pass + def gearrange(self, gear=0): + """Set the gear range""" + pass - def pocket(self): - """Pocket routine""" - pass + ############################################################################ + # Moves - def profile(self): - """Profile routine""" - pass + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Rapid move""" + pass - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): - """Drilling routines""" - pass + def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Feed move""" + pass - # original prototype was: - # def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None): - # - # current call is like so: - # tap(x=10, y=10, z=0, tap_mode=0, depth=12.7, standoff=6.35, direction=0, pitch=1.25) - # just add tap_mode & direction parameters + def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + """Clockwise arc move""" + pass - def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): - """Tapping routines""" - pass + def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + """Counterclockwise arc move""" + pass - def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): - """Boring routines""" - pass + def dwell(self, t): + """Dwell""" + pass - def end_canned_cycle(self): - pass + def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): + """Rapid relative to home position""" + pass - ############################################################################ - ## Misc + def rapid_unhome(self): + """Return from rapid home""" + pass - def comment(self, text): - """Insert a comment""" - pass + def set_machine_coordinates(self): + """Set machine coordinates""" + pass - def insert(self, text): - """APT style INSERT statement""" - pass + ############################################################################ + # Cutter radius compensation - def block_delete(self, on=False): - """block to ignore if block delete switch is on""" - pass + def use_CRC(self): + """CRC""" + return False - def variable(self, id): - """Insert a variable""" - pass + ############################################################################ + # Cycles - def variable_set(self, id, value): - """Set a variable""" - pass + def pattern(self): + """Simple pattern eg. circle, rect""" + pass - def probe_linear_centre_outside(self, x1=None, y1=None, depth=None, x2=None, y2=None ): - pass + def pocket(self): + """Pocket routine""" + pass - def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None ): - pass + def profile(self): + """Profile routine""" + pass - def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): - pass + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + """Drilling routines""" + pass - def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): - pass + # original prototype was: + # def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None): + # + # current call is like so: + # tap(x=10, y=10, z=0, tap_mode=0, depth=12.7, standoff=6.35, direction=0, pitch=1.25) + # just add tap_mode & direction parameters - def open_log_file(self, xml_file_name=None ): - pass + def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): + """Tapping routines""" + pass - def log_coordinate(self, x=None, y=None, z=None): - pass + def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): + """Boring routines""" + pass - def log_message(self, message=None): - pass + def end_canned_cycle(self): + pass - def close_log_file(self): - pass + ############################################################################ + # Misc - def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): - pass + def comment(self, text): + """Insert a comment""" + pass - def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): - pass + def insert(self, text): + """APT style INSERT statement""" + pass - def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): - pass + def block_delete(self, on=False): + """block to ignore if block delete switch is on""" + pass - def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance ): - pass + def variable(self, id): + """Insert a variable""" + pass - ############################################################################ - ## NC code creator for additive machines like RepRap + def variable_set(self, id, value): + """Set a variable""" + pass + def probe_linear_centre_outside(self, x1=None, y1=None, depth=None, x2=None, y2=None): + pass - def wipe(self): - """wipe routine""" - pass + def probe_single_point(self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None): + pass - def extruder_on(self): - """Turn on the extruder""" - pass + def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): + pass - def extruder_off(self): - """turn off the extruder""" - pass + def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): + pass - def set_extruder_flowrate(self, flowrate): - """Set the flowrate for the extruder""" - pass + def open_log_file(self, xml_file_name=None): + pass - def extruder_temp(self, temp): - """Set the extruder temp in celsius""" - pass + def log_coordinate(self, x=None, y=None, z=None): + pass - def fan_on(self): - """turn on the cooling fan""" - pass + def log_message(self, message=None): + pass - def fan_off(self): - """turn off the cooling fan""" - pass + def close_log_file(self): + pass - def build_bed_temp(self, temp): - """Set the bed temp in celsius""" - pass + def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): + pass - def chamber_temp(self, temp): - """Set the chamber temp in celsius""" - pass + def rapid_to_intersection(self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): + pass - def begin_ncblock(self): - # if the moves have come from backplotting nc code, then the nc code text can be given with these three functions - pass + def rapid_to_rotated_coordinate(self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): + pass - def end_ncblock(self): - pass + def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance): + pass - def add_text(self, s, col, cdata): - pass + ############################################################################ + # NC code creator for additive machines like RepRap + + def wipe(self): + """wipe routine""" + pass + + def extruder_on(self): + """Turn on the extruder""" + pass + + def extruder_off(self): + """turn off the extruder""" + pass + + def set_extruder_flowrate(self, flowrate): + """Set the flowrate for the extruder""" + pass + + def extruder_temp(self, temp): + """Set the extruder temp in celsius""" + pass + + def fan_on(self): + """turn on the cooling fan""" + pass + + def fan_off(self): + """turn off the cooling fan""" + pass + + def build_bed_temp(self, temp): + """Set the bed temp in celsius""" + pass + + def chamber_temp(self, temp): + """Set the chamber temp in celsius""" + pass + + def begin_ncblock(self): + # if the moves have come from backplotting nc code, then the nc code text can be given with these three functions + pass + + def end_ncblock(self): + pass + + def add_text(self, s, col, cdata): + pass ################################################################################ + creator = Creator() ############################################################################ -## Internals +# Internals + def write(s): - creator.write(s) + creator.write(s) + def output(filename): - creator.file_open(filename) + creator.file_open(filename) ############################################################################ -## Programs +# Programs + def program_begin(id, name=''): - creator.program_begin(id, name) + creator.program_begin(id, name) + def add_stock(type_name, params): - creator.add_stock(type_name, params) + creator.add_stock(type_name, params) + def program_stop(optional=False): - creator.program_stop(optional) + creator.program_stop(optional) + def program_end(): - creator.program_end() + creator.program_end() + def flush_nc(): - creator.flush_nc() + creator.flush_nc() ############################################################################ -## Subprograms +# Subprograms + def sub_begin(id, name=''): - creator.sub_begin(id, name) + creator.sub_begin(id, name) + def sub_call(id): - creator.sub_call(id) + creator.sub_call(id) + def sub_end(): - creator.sub_end() + creator.sub_end() ############################################################################ -## Settings +# Settings + def imperial(): - creator.imperial() + creator.imperial() + def metric(): - creator.metric() + creator.metric() + def absolute(): - creator.absolute() + creator.absolute() + def incremental(): - creator.incremental() + creator.incremental() + def polar(on=True): - creator.polar(on) + creator.polar(on) + def set_plane(plane): - creator.set_plane(plane) + creator.set_plane(plane) + def set_temporary_origin(x=None, y=None, z=None, a=None, b=None, c=None): - creator.set_temporary_origin(x,y,z,a,b,c) + creator.set_temporary_origin(x, y, z, a, b, c) + def remove_temporary_origin(): - creator.remove_temporary_origin() + creator.remove_temporary_origin() ############################################################################ -## Tools +# Tools + def tool_change(id): - creator.tool_change(id) + creator.tool_change(id) + def tool_defn(id, name='', params=None): - creator.tool_defn(id, name, params) + creator.tool_defn(id, name, params) + def offset_radius(id, radius=None): - creator.offset_radius(id, radius) + creator.offset_radius(id, radius) + def offset_length(id, length=None): - creator.offset_length(id, length) + creator.offset_length(id, length) + def current_tool(self): - return creator.current_tool() + return creator.current_tool() ############################################################################ -## Datums +# Datums + def datum_shift(x=None, y=None, z=None, a=None, b=None, c=None): - creator.datum_shift(x, y, z, a, b, c) + creator.datum_shift(x, y, z, a, b, c) + def datum_set(x=None, y=None, z=None, a=None, b=None, c=None): - creator.datum_set(x, y, z, a, b, c) + creator.datum_set(x, y, z, a, b, c) + def workplane(id): - creator.workplane(id) + creator.workplane(id) + def clearanceplane(z=None): - creator.clearanceplane(z) + creator.clearanceplane(z) ############################################################################ -## APT360 like Transformation Definitions -## These definitions were created while looking at Irvin Kraal's book on APT -## - Numerical Control Progamming in APT - page 211 +# APT360 like Transformation Definitions +# These definitions were created while looking at Irvin Kraal's book on APT +# - Numerical Control Progamming in APT - page 211 -def matrix(a1=None,b1=None,c1=None,a2=None,b2=None,c2=None,a3=None,b3=None,c3=None): - creator.matrix(a1,b1,c1,a2,b2,c2,a3,b3,c3) -def translate(x=None,y=None,z=None): - creator.translate(x,y,z) +def matrix(a1=None, b1=None, c1=None, a2=None, b2=None, c2=None, a3=None, b3=None, c3=None): + creator.matrix(a1, b1, c1, a2, b2, c2, a3, b3, c3) + + +def translate(x=None, y=None, z=None): + creator.translate(x, y, z) + + +def rotate(xyrot=None, yzrot=None, zxrot=None, angle=None): + creator.rotate(xyrot, yzrot, zxrot, angle) -def rotate(xyrot=None,yzrot=None,zxrot=None,angle=None): - creator.rotate(xyrot,yzrot,zxrot,angle) def scale(k=None): - creator.scale(k) + creator.scale(k) -def matrix_product(matrix1=None,matrix2=None): - creator.matrix_product(matrix1,matrix2) -def mirror_plane(plane1=None,plane2=None,plane3=None): - creator.mirror_plane(plane1,plane2,plane3) +def matrix_product(matrix1=None, matrix2=None): + creator.matrix_product(matrix1, matrix2) -def mirror_line(line=None): - creator.mirror_line(line) +def mirror_plane(plane1=None, plane2=None, plane3=None): + creator.mirror_plane(plane1, plane2, plane3) + + +def mirror_line(line=None): + creator.mirror_line(line) ############################################################################ ## Rates + Modes def feedrate(f): - creator.feedrate(f) + creator.feedrate(f) + def feedrate_hv(fh, fv): - creator.feedrate_hv(fh, fv) + creator.feedrate_hv(fh, fv) + def spindle(s, clockwise=True): - creator.spindle(s, clockwise) + creator.spindle(s, clockwise) + def coolant(mode=0): - creator.coolant(mode) + creator.coolant(mode) + def gearrange(gear=0): - creator.gearrange(gear) + creator.gearrange(gear) ############################################################################ -## Moves +# Moves + def rapid(x=None, y=None, z=None, a=None, b=None, c=None): - creator.rapid(x, y, z, a, b, c) + creator.rapid(x, y, z, a, b, c) + + +def feed(x=None, y=None, z=None, a=None, b=None, c=None): + creator.feed(x, y, z) -def feed(x=None, y=None, z=None, a = None, b = None, c = None): - creator.feed(x, y, z) def arc_cw(x=None, y=None, z=None, i=None, j=None, k=None, r=None): - creator.arc_cw(x, y, z, i, j, k, r) + creator.arc_cw(x, y, z, i, j, k, r) + def arc_ccw(x=None, y=None, z=None, i=None, j=None, k=None, r=None): - creator.arc_ccw(x, y, z, i, j, k, r) + creator.arc_ccw(x, y, z, i, j, k, r) + def dwell(t): - creator.dwell(t) + creator.dwell(t) + def rapid_home(x=None, y=None, z=None, a=None, b=None, c=None): - creator.rapid_home(x, y, z, a, b, c) + creator.rapid_home(x, y, z, a, b, c) + def rapid_unhome(): - creator.rapid_unhome() + creator.rapid_unhome() + def set_machine_coordinates(): - creator.set_machine_coordinates() + creator.set_machine_coordinates() ############################################################################ -## Cutter radius compensation +# Cutter radius compensation + def use_CRC(): - return creator.use_CRC() + return creator.use_CRC() + def CRC_nominal_path(): - return creator.CRC_nominal_path() + return creator.CRC_nominal_path() + + +def start_CRC(left=True, radius=0.0): + creator.start_CRC(left, radius) -def start_CRC(left = True, radius = 0.0): - creator.start_CRC(left, radius) def end_CRC(): - creator.end_CRC() + creator.end_CRC() ############################################################################ -## Cycles +# Cycles + def pattern(): - creator.pattern() + creator.pattern() + def pocket(): - creator.pocket() + creator.pocket() + def profile(): - creator.profile() + creator.profile() + -def drill(x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): - creator.drill(x, y, dwell, depthparams, retract_mode, spindle_mode, internal_coolant_on, rapid_to_clearance) +def drill(x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + creator.drill(x, y, dwell, depthparams, retract_mode, spindle_mode, + internal_coolant_on, rapid_to_clearance) def tap(x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): - creator.tap(x, y, z, zretract, depth, standoff, dwell_bottom, pitch, stoppos, spin_in, spin_out, tap_mode, direction) + creator.tap(x, y, z, zretract, depth, standoff, dwell_bottom, + pitch, stoppos, spin_in, spin_out, tap_mode, direction) + def bore(x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): - creator.bore(x, y, z, zretract, depth, standoff, dwell_Bottom, feed_in, feed_out, stoppos, shift_back, shift_right, backbore, stop) + creator.bore(x, y, z, zretract, depth, standoff, dwell_Bottom, feed_in, + feed_out, stoppos, shift_back, shift_right, backbore, stop) + def end_canned_cycle(): - creator.end_canned_cycle() + creator.end_canned_cycle() + def peck(count, first, last=None, step=0.0): - pecks = [] - peck = first - if (last == None) : last = first - for i in range(0,count): - pecks.append(peck) - if (peck - step > last) : peck -= step - return pecks + pecks = [] + peck = first + if (last == None): + last = first + for i in range(0, count): + pecks.append(peck) + if (peck - step > last): + peck -= step + return pecks ############################################################################ -## Misc +# Misc + def comment(text): - creator.comment(text) + creator.comment(text) + def insert(text): - creator.insert(text) + creator.insert(text) + def block_delete(on=False): - creator.block_delete(on) + creator.block_delete(on) + def variable(id): - creator.variable(id) + creator.variable(id) + def variable_set(id, value): - creator.variable_set(id, value) + creator.variable_set(id, value) + + +def probe_single_point(point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None): + creator.probe_single_point(point_along_edge_x, point_along_edge_y, depth, retracted_point_x, retracted_point_y, destination_point_x, + destination_point_y, intersection_variable_x, intersection_variable_y, probe_offset_x_component, probe_offset_y_component) -def probe_single_point(point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None ): - creator.probe_single_point(point_along_edge_x, point_along_edge_y, depth, retracted_point_x, retracted_point_y, destination_point_x, destination_point_y, intersection_variable_x, intersection_variable_y, probe_offset_x_component, probe_offset_y_component ) def probe_downward_point(x=None, y=None, depth=None, intersection_variable_z=None): - creator.probe_downward_point(x, y, depth, intersection_variable_z) + creator.probe_downward_point(x, y, depth, intersection_variable_z) + + +def report_probe_results(x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): + creator.report_probe_results(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, + y4, z4, x5, y5, z5, x6, y6, z6, xml_file_name) -def report_probe_results(x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): - creator.report_probe_results(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4, x5, y5, z5, x6, y6, z6, xml_file_name) -def open_log_file(xml_file_name=None ): - creator.open_log_file(xml_file_name) +def open_log_file(xml_file_name=None): + creator.open_log_file(xml_file_name) + def log_coordinate(x=None, y=None, z=None): - creator.log_coordinate(x, y, z) + creator.log_coordinate(x, y, z) + def log_message(message=None): - creator.log_message(message) + creator.log_message(message) + def close_log_file(): - creator.close_log_file() + creator.close_log_file() + def rapid_to_midpoint(x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): - creator.rapid_to_midpoint(x1, y1, z1, x2, y2, z2) + creator.rapid_to_midpoint(x1, y1, z1, x2, y2, z2) + def rapid_to_intersection(x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub): - creator.rapid_to_intersection(x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub) + creator.rapid_to_intersection(x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, + intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub) + def rapid_to_rotated_coordinate(x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final): - creator.rapid_to_rotated_coordinate(x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final) + creator.rapid_to_rotated_coordinate( + x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final) -def set_path_control_mode(mode, motion_blending_tolerance, naive_cam_tolerance ): - creator.set_path_control_mode(mode, motion_blending_tolerance, naive_cam_tolerance ) + +def set_path_control_mode(mode, motion_blending_tolerance, naive_cam_tolerance): + creator.set_path_control_mode(mode, motion_blending_tolerance, naive_cam_tolerance) ############################################################################ -## NC code creator for additive machines like RepRap +# NC code creator for additive machines like RepRap + def wipe(): - creator.wipe() + creator.wipe() + def extruder_on(): - creator.extruder_on() + creator.extruder_on() + def extruder_off(): - creator.extruder_off() + creator.extruder_off() + def set_extruder_flowrate(flowrate): - creator.set_extruder_flowrate(flowrate) + creator.set_extruder_flowrate(flowrate) + def extruder_temp(temp=None): - creator.extruder_temp(temp) + creator.extruder_temp(temp) + def fan_on(): - creator.fan_on() + creator.fan_on() + def fan_off(): - creator.fan_off() + creator.fan_off() + def build_bed_temp(temp=None): - creator.build_bed_temp(temp) + creator.build_bed_temp(temp) + def chamber_temp(temp=None): - creator.chamber_temp(temp) + creator.chamber_temp(temp) diff --git a/scripts/addons/cam/nc/nc_read.py b/scripts/addons/cam/nc/nc_read.py index b8330107f..06ddd11e1 100644 --- a/scripts/addons/cam/nc/nc_read.py +++ b/scripts/addons/cam/nc/nc_read.py @@ -8,6 +8,7 @@ import math count = 0 + class Parser: def __init__(self, writer): @@ -23,23 +24,31 @@ def __del__(self): self.file_in.close() ############################################################################ - ## Internals + # Internals def readline(self): self.line = self.file_in.readline().rstrip() - if (len(self.line)) : return True - else : return False + if (len(self.line)): + return True + else: + return False def set_current_pos(self, x, y, z): - if (x != None) : - if self.absolute_flag or self.currentx == None: self.currentx = x - else: self.currentx = self.currentx + x - if (y != None) : - if self.absolute_flag or self.currenty == None: self.currenty = y - else: self.currenty = self.currenty + y - if (z != None) : - if self.absolute_flag or self.currentz == None: self.currentz = z - else: self.currentz = self.currentz + z + if (x != None): + if self.absolute_flag or self.currentx == None: + self.currentx = x + else: + self.currentx = self.currentx + x + if (y != None): + if self.absolute_flag or self.currenty == None: + self.currenty = y + else: + self.currenty = self.currenty + y + if (z != None): + if self.absolute_flag or self.currentz == None: + self.currentz = z + else: + self.currentz = self.currentz + z # def add_line(self, x, y, z, a, b, c): # if (x == None and y == None and z == None and a == None and b == None and c == None) : return @@ -98,10 +107,11 @@ def Parse(self, name): if self.t != None: if (self.m6 == True) or (self.need_m6_for_t_change == False): - self.writer.tool_change( self.t ) + self.writer.tool_change(self.t) if (self.drill): - if self.z != None: self.drillz = self.z + if self.z != None: + self.drillz = self.z self.writer.rapid(self.x, self.y, self.r) self.writer.feed(self.x, self.y, self.drillz) self.writer.feed(self.x, self.y, self.r) @@ -111,7 +121,7 @@ def Parse(self, name): else: if (self.move and not self.no_move): - if (self.arc==0): + if (self.arc == 0): if self.path_col == "feed": self.writer.feed(self.x, self.y, self.z) else: @@ -125,15 +135,17 @@ def Parse(self, name): else: if (self.arc_centre_positive == True) and (self.oldx != None) and (self.oldy != None): x = self.oldx - if self.x != None: x = self.x + if self.x != None: + x = self.x if (self.x > self.oldx) != (self.arc > 0): j = -j y = self.oldy - if self.y != None: y = self.y + if self.y != None: + y = self.y if (self.y > self.oldy) != (self.arc < 0): i = -i - #fix centre point + # fix centre point r = math.sqrt(i*i + j*j) p0 = area.Point(self.oldx, self.oldy) p1 = area.Point(x, y) @@ -143,7 +155,8 @@ def Parse(self, name): d = math.sqrt(r*r - h*h) n = area.Point(-v.y, v.x) n.normalize() - if self.arc == -1: d = -d + if self.arc == -1: + d = -d c = p0 + (v * 0.5) + (n * d) i = c.x j = c.y @@ -164,10 +177,11 @@ def Parse(self, name): self.writer.arc_cw(self.x, self.y, self.z, i, j, k) else: self.writer.arc_ccw(self.x, self.y, self.z, i, j, k) - if self.x != None: self.oldx = self.x - if self.y != None: self.oldy = self.y - if self.z != None: self.oldz = self.z + if self.x != None: + self.oldx = self.x + if self.y != None: + self.oldy = self.y + if self.z != None: + self.oldz = self.z self.writer.end_ncblock() - - diff --git a/scripts/addons/cam/nc/nclathe_read.py b/scripts/addons/cam/nc/nclathe_read.py index 25130d107..83d93cf87 100644 --- a/scripts/addons/cam/nc/nclathe_read.py +++ b/scripts/addons/cam/nc/nclathe_read.py @@ -15,10 +15,10 @@ def __init__(self): self.absolute_flag = True ############################################################################ - ## Internals + # Internals def files_open(self, name, oname=None): - if (oname == None ): + if (oname == None): oname = (name+'.nc.xml') self.file_in = open(name, 'r') self.file_out = open(oname, 'w') @@ -34,14 +34,16 @@ def files_close(self): def readline(self): self.line = self.file_in.readline().rstrip() - if (len(self.line)) : return True - else : return False + if (len(self.line)): + return True + else: + return False def write(self, s): self.file_out.write(s) ############################################################################ - ## Xml + # Xml def begin_ncblock(self): self.file_out.write('\t\n') @@ -54,94 +56,118 @@ def add_text(self, s, col=None, cdata=False): s.replace('"', '"') s.replace('<', '<') s.replace('>', '>') - if (cdata) : (cd1, cd2) = ('') - else : (cd1, cd2) = ('', '') - if (col != None) : self.file_out.write('\t\t'+cd1+s+cd2+'\n') - else : self.file_out.write('\t\t'+cd1+s+cd2+'\n') + if (cdata): + (cd1, cd2) = ('') + else: + (cd1, cd2) = ('', '') + if (col != None): + self.file_out.write('\t\t'+cd1+s+cd2+'\n') + else: + self.file_out.write('\t\t'+cd1+s+cd2+'\n') def set_mode(self, units=None): self.file_out.write('\t\t\n') def set_tool(self, number=None): self.file_out.write('\t\t\n') + if (number != None): + self.file_out.write(' number="'+str(number)+'"') + self.file_out.write(' />\n') def begin_path(self, col=None): - if (col != None) : self.file_out.write('\t\t\n') - else : self.file_out.write('\t\t\n') + if (col != None): + self.file_out.write('\t\t\n') + else: + self.file_out.write('\t\t\n') def end_path(self): self.file_out.write('\t\t\n') def add_line(self, x=None, y=None, z=None, a=None, b=None, c=None): - if (x == None and y == None and z == None and a == None and b == None and c == None) : return + if (x == None and y == None and z == None and a == None and b == None and c == None): + return self.file_out.write('\t\t\t\n') - def add_lathe_increment_line(self, u=None, w=None): -# needed for representing U and W moves in lathe code- these are non modal incremental moves -# U == X and W == Z - if (u == None and w == None ) : return + # needed for representing U and W moves in lathe code- these are non modal incremental moves + # U == X and W == Z + if (u == None and w == None): + return self.file_out.write('\t\t\t\n') - - - - - def add_arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, d=None): - if (x == None and y == None and z == None and i == None and j == None and k == None and r == None and d == None) : return + if (x == None and y == None and z == None and i == None and j == None and k == None and r == None and d == None): + return self.file_out.write('\t\t\t\n') def incremental(self): diff --git a/scripts/addons/cam/nc/num_reader.py b/scripts/addons/cam/nc/num_reader.py index 62c722cca..c9bd9cc91 100644 --- a/scripts/addons/cam/nc/num_reader.py +++ b/scripts/addons/cam/nc/num_reader.py @@ -4,6 +4,7 @@ # a base class for hpgl parsers, and maybe others + class NumReader(nc.Parser): def __init__(self): @@ -37,7 +38,7 @@ def add_word(self, color): self.parse_word = "" def Parse(self, name, oname=None): - self.files_open(name,oname) + self.files_open(name, oname) while self.readline(): self.begin_ncblock() @@ -59,4 +60,3 @@ def Parse(self, name, oname=None): self.end_ncblock() self.files_close() - diff --git a/scripts/addons/cam/nc/printbot3d.py b/scripts/addons/cam/nc/printbot3d.py index a450f818c..ac99559e9 100644 --- a/scripts/addons/cam/nc/printbot3d.py +++ b/scripts/addons/cam/nc/printbot3d.py @@ -8,6 +8,8 @@ import math ################################################################################ + + class CreatorPrintbot(iso_modal.CreatorIsoModal): def __init__(self): @@ -20,7 +22,7 @@ def write_blocknum(self): pass def set_plane(self, plane): - pass + pass def workplane(self, id): pass @@ -38,7 +40,7 @@ def set_extruder_flowrate(self, flowrate): self.spindle(flowrate, True) def extruder_temp(self, temp): - self.write((maker.codes.EXTRUDER_TEMP(temp)) + ('\n')) + self.write((maker.codes.EXTRUDER_TEMP(temp)) + ('\n')) # General def rapid(x=None, y=None, z=None, a=None, b=None, c=None): @@ -50,4 +52,5 @@ def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): ################################################################################ + nc.creator = CreatorPrintbot() diff --git a/scripts/addons/cam/nc/printbot3d_read.py b/scripts/addons/cam/nc/printbot3d_read.py index 79c3869f2..775e1ecee 100644 --- a/scripts/addons/cam/nc/printbot3d_read.py +++ b/scripts/addons/cam/nc/printbot3d_read.py @@ -3,6 +3,7 @@ # based on the iso reader + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/recreator.py b/scripts/addons/cam/nc/recreator.py index 5b25bf009..ce5f670bf 100644 --- a/scripts/addons/cam/nc/recreator.py +++ b/scripts/addons/cam/nc/recreator.py @@ -2,6 +2,7 @@ units = 1.0 + class Redirector(nc.Creator): def __init__(self, original): @@ -11,16 +12,19 @@ def __init__(self, original): self.x = None self.y = None self.z = None - if original.x != None: self.x = original.x * units - if original.y != None: self.y = original.y * units - if original.z != None: self.z = original.z * units + if original.x != None: + self.x = original.x * units + if original.y != None: + self.y = original.y * units + if original.z != None: + self.z = original.z * units self.imperial = False def cut_path(self): pass ############################################################################ - ## Programs + # Programs def write(self, s): self.original.write(s) @@ -53,7 +57,7 @@ def flush_nc(self): self.original.flush_nc() ############################################################################ - ## Subprograms + # Subprograms def sub_begin(self, id, name=None): self.cut_path() @@ -74,7 +78,7 @@ def enable_output(self): self.original.enable_output() ############################################################################ - ## Settings + # Settings def imperial(self): self.cut_path() @@ -103,14 +107,14 @@ def set_plane(self, plane): def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): self.cut_path() - self.original.set_temporary_origin(x,y,z,a,b,c) + self.original.set_temporary_origin(x, y, z, a, b, c) def remove_temporary_origin(self): self.cut_path() self.original.remove_temporary_origin() ############################################################################ - ## Tools + # Tools def tool_change(self, id): self.cut_path() @@ -129,7 +133,7 @@ def offset_length(self, id, length=None): self.original.offset_length(id, length) ############################################################################ - ## Datums + # Datums def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): self.cut_path() @@ -167,14 +171,17 @@ def gearrange(self, gear=0): self.original.gearrange(gear) ############################################################################ - ## Moves + # Moves def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): self.cut_path() self.original.rapid(x, y, z, a, b, c) - if x != None: self.x = x * units - if y != None: self.y = y * units - if z != None: self.z = z * units + if x != None: + self.x = x * units + if y != None: + self.y = y * units + if z != None: + self.z = z * units def cut_path(self): pass @@ -182,13 +189,16 @@ def cut_path(self): def z2(self, z): return z - def feed(self, x=None, y=None, z=None, a = None, b = None, c = None): + def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): px = self.x py = self.y pz = self.z - if x != None: self.x = x * units - if y != None: self.y = y * units - if z != None: self.z = z * units + if x != None: + self.x = x * units + if y != None: + self.y = y * units + if z != None: + self.z = z * units if self.x == None or self.y == None or self.z == None: self.cut_path() self.original.feed(x, y, z) @@ -199,15 +209,18 @@ def feed(self, x=None, y=None, z=None, a = None, b = None, c = None): self.original.feed(self.x/units, self.y/units, self.z2(self.z)/units) return - def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw = True): + def arc(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None, ccw=True): if self.x == None or self.y == None or self.z == None: raise "first attached move can't be an arc" px = self.x py = self.y pz = self.z - if x != None: self.x = x * units - if y != None: self.y = y * units - if z != None: self.z = z * units + if x != None: + self.x = x * units + if y != None: + self.y = y * units + if z != None: + self.z = z * units def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.arc(x, y, z, i, j, k, r, False) @@ -228,12 +241,12 @@ def rapid_unhome(self): self.original.rapid_unhome() ############################################################################ - ## Cutter radius compensation + # Cutter radius compensation def use_CRC(self): return self.original.use_CRC() - def start_CRC(self, left = True, radius = 0.0): + def start_CRC(self, left=True, radius=0.0): self.cut_path() self.original.start_CRC(left, radius) @@ -242,7 +255,7 @@ def end_CRC(self): self.original.end_CRC() ############################################################################ - ## Cycles + # Cycles def pattern(self): self.cut_path() @@ -259,30 +272,33 @@ def profile(self): self.cut_path() self.original.profile() - def circular_pocket(self, x=None, y=None, ToolDiameter=None, HoleDiameter=None, ClearanceHeight=None, StartHeight=None, MaterialTop=None, FeedRate=None, SpindleRPM=None, HoleDepth=None, DepthOfCut=None, StepOver=None ): + def circular_pocket(self, x=None, y=None, ToolDiameter=None, HoleDiameter=None, ClearanceHeight=None, StartHeight=None, MaterialTop=None, FeedRate=None, SpindleRPM=None, HoleDepth=None, DepthOfCut=None, StepOver=None): self.cut_path() - self.circular_pocket(x, y, ToolDiameter, HoleDiameter, ClearanceHeight, StartHeight, MaterialTop, FeedRate, SpindleRPM, HoleDepth, DepthOfCut, StepOver) + self.circular_pocket(x, y, ToolDiameter, HoleDiameter, ClearanceHeight, StartHeight, + MaterialTop, FeedRate, SpindleRPM, HoleDepth, DepthOfCut, StepOver) - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): self.cut_path() - self.original.drill(x, y, dwell, depthparams, spindle_mode, internal_coolant_on, rapid_to_clearance) + self.original.drill(x, y, dwell, depthparams, spindle_mode, + internal_coolant_on, rapid_to_clearance) # argument list adapted for compatibility with Tapping module # wild guess - I'm unsure about the purpose of this file and wether this works -haberlerm def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): self.cut_path() - self.original.tap( x, y, self.z2(z), self.z2(zretract), depth, standoff, dwell_bottom, pitch, stoppos, spin_in, spin_out, tap_mode, direction) - + self.original.tap(x, y, self.z2(z), self.z2(zretract), depth, standoff, + dwell_bottom, pitch, stoppos, spin_in, spin_out, tap_mode, direction) def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, feed_in=None, feed_out=None, stoppos=None, shift_back=None, shift_right=None, backbore=False, stop=False): self.cut_path() - self.original.bore(x, y, self.z2(z), self.z2(zretract), depth, standoff, dwell_Bottom, feed_in, feed_out, stoppos, shift_back, shift_right, backbore, stop) + self.original.bore(x, y, self.z2(z), self.z2(zretract), depth, standoff, dwell_Bottom, + feed_in, feed_out, stoppos, shift_back, shift_right, backbore, stop) def end_canned_cycle(self): self.original.end_canned_cycle() ############################################################################ - ## Misc + # Misc def comment(self, text): self.cut_path() diff --git a/scripts/addons/cam/nc/rez2.py b/scripts/addons/cam/nc/rez2.py index 8f9ee1ed4..bbaa82882 100644 --- a/scripts/addons/cam/nc/rez2.py +++ b/scripts/addons/cam/nc/rez2.py @@ -11,6 +11,8 @@ import circular_pocket as circular ################################################################################ + + class Creator(nc.Creator): def __init__(self): @@ -36,145 +38,150 @@ def __init__(self): self.y = 0 self.z = 500 - self.x1=0 - self.x2=0 - self.y1=0 - self.y2=0 - self.SPACE=' ' + self.x1 = 0 + self.x2 = 0 + self.y1 = 0 + self.y2 = 0 + self.SPACE = ' ' self.fmt = self.FORMAT_MM() - pass + pass ############################################################################ - ## Internals + # Internals def FORMAT_IN(self): return('%.5f') def FORMAT_MM(self): return('%.3f') - def COMMENT(self,comment): return( ('(%s)' % comment ) ) + def COMMENT(self, comment): return(('(%s)' % comment)) def write_feedrate(self): - #self.write(self.f) + # self.write(self.f) self.f = '' def write_preps(self): - #self.write(self.g) + # self.write(self.g) self.g = '' def write_misc(self): #if (len(self.m)) : self.write(self.m.pop()) - pass + pass def write_blocknum(self): #self.write(iso.BLOCK % self.n) - #self.write(iso.SPACE) + # self.write(iso.SPACE) #self.n += 10 - pass + pass def write_spindle(self): - #self.write(self.s) + # self.write(self.s) #self.s = '' - pass + pass ############################################################################ - ## Programs + # Programs def program_begin(self, id, name=''): #self.write((iso.PROGRAM % id) + iso.SPACE + (iso.COMMENT % name)) #self.write("// program v jazyku"+self.SPACE+"REZ\nMA 0.0000 , 0.0000\n") - self.write("// program v jazyku"+self.SPACE+"REZ\nGO_LEFT\nMA 0.0000 , 0.0000\n") - #self.write('\n') + self.write("// program v jazyku"+self.SPACE+"REZ\nGO_LEFT\nMA 0.0000 , 0.0000\n") + # self.write('\n') def program_stop(self, optional=False): - #self.write_blocknum() + # self.write_blocknum() #if (optional) : self.write(iso.STOP_OPTIONAL + '\n') - #else : self.write(iso.STOP + '\n') - self.write("// stop programu v jazyku REZ\n") + # else : self.write(iso.STOP + '\n') + self.write("// stop programu v jazyku REZ\n") def program_end(self): - #self.write_blocknum() + # self.write_blocknum() + #self.write(iso.PROGRAM_END + '\n') + self.write('PL_OFF \n') + self.write('PARK \n') + self.write('FS \n') + self.write('MA 0.0000 , 0.0000\n') + self.write("// koniec programu v jazyku REZ\n") #self.write(iso.PROGRAM_END + '\n') - self.write('PL_OFF \n') - self.write('PARK \n') - self.write('FS \n') - self.write('MA 0.0000 , 0.0000\n') - self.write("// koniec programu v jazyku REZ\n") - #self.write(iso.PROGRAM_END + '\n') + def flush_nc(self): - #self.write_blocknum() + # self.write_blocknum() self.write_preps() self.write_misc() - #self.write('\n') + # self.write('\n') ############################################################################ - ## Subprograms + # Subprograms def sub_begin(self, id, name=''): #self.write((iso.PROGRAM % id) + iso.SPACE + (iso.COMMENT % name)) - #self.write('\n') - pass + # self.write('\n') + pass + def sub_call(self, id): - #self.write_blocknum() + # self.write_blocknum() #self.write((iso.SUBPROG_CALL % id) + '\n') - #self.write('\n') - pass + # self.write('\n') + pass def sub_end(self): - #self.write_blocknum() + # self.write_blocknum() #self.write(iso.SUBPROG_END + '\n') - #self.write('\n') - pass + # self.write('\n') + pass ############################################################################ - ## Settings + # Settings def imperial(self): #self.g += iso.IMPERIAL #self.fmt = iso.FORMAT_IN - #self.write('\n') - pass + # self.write('\n') + pass def metric(self): #self.g += iso.METRIC #self.fmt = iso.FORMAT_MM - #self.write('\n') - pass + # self.write('\n') + pass def absolute(self): #self.g += iso.ABSOLUTE - #self.write('\n') - pass + # self.write('\n') + pass def incremental(self): #self.g += iso.INCREMENTAL - #self.write('\n') - pass + # self.write('\n') + pass + def polar(self, on=True): #if (on) : self.g += iso.POLAR_ON - #else : self.g += iso.POLAR_OFF - #self.write('\n') - pass + # else : self.g += iso.POLAR_OFF + # self.write('\n') + pass + def set_plane(self, plane): #if (plane == 0) : self.g += iso.PLANE_XY - #elif (plane == 1) : self.g += iso.PLANE_XZ - #elif (plane == 2) : self.g += iso.PLANE_YZ - #self.write('\n') - pass + # elif (plane == 1) : self.g += iso.PLANE_XZ + # elif (plane == 2) : self.g += iso.PLANE_YZ + # self.write('\n') + pass ############################################################################ - ## Tools + # Tools def tool_change(self, id): - #self.write_blocknum() + # self.write_blocknum() #self.write((iso.TOOL % id) + '\n') - #self.write('\n') - pass + # self.write('\n') + pass + def tool_defn(self, id, name='', params=None): - pass + pass def offset_radius(self, id, radius=None): - pass + pass def offset_length(self, id, length=None): - pass + pass ############################################################################ - ## Datums + # Datums def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): pass @@ -185,142 +192,143 @@ def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 # These are selected by values from 1 to 9 inclusive. def workplane(self, id): - #if ((id >= 1) and (id <= 6)): - # self.g += iso.WORKPLANE % (id + iso.WORKPLANE_BASE) - #if ((id >= 7) and (id <= 9)): - # self.g += ((iso.WORKPLANE % (6 + iso.WORKPLANE_BASE)) + ('.%i' % (id - 6))) - pass + # if ((id >= 1) and (id <= 6)): + # self.g += iso.WORKPLANE % (id + iso.WORKPLANE_BASE) + # if ((id >= 7) and (id <= 9)): + # self.g += ((iso.WORKPLANE % (6 + iso.WORKPLANE_BASE)) + ('.%i' % (id - 6))) + pass ############################################################################ ## Rates + Modes def feedrate(self, f): #self.f = iso.FEEDRATE + (self.fmt % f) #self.fhv = False - pass + pass def feedrate_hv(self, fh, fv): #self.fh = fh #self.fv = fv #self.fhv = True - pass + pass + def calc_feedrate_hv(self, h, v): #l = math.sqrt(h*h+v*v) #if (h == 0) : self.f = iso.FEEDRATE + (self.fmt % self.fv) - #elif (v == 0) : self.f = iso.FEEDRATE + (self.fmt % self.fh) - #else: - # self.f = iso.FEEDRATE + (self.fmt % (self.fh * l * min([1/h, 1/v]))) - pass + # elif (v == 0) : self.f = iso.FEEDRATE + (self.fmt % self.fh) + # else: + # self.f = iso.FEEDRATE + (self.fmt % (self.fh * l * min([1/h, 1/v]))) + pass def spindle(self, s, clockwise): - #if s < 0: clockwise = not clockwise - #s = abs(s) + #if s < 0: clockwise = not clockwise + #s = abs(s) #self.s = iso.SPINDLE % s - #if clockwise: - # self.s = self.s + iso.SPINDLE_CW - #else: - # self.s = self.s + iso.SPINDLE_CCW - pass + # if clockwise: + # self.s = self.s + iso.SPINDLE_CW + # else: + # self.s = self.s + iso.SPINDLE_CCW + pass def coolant(self, mode=0): #if (mode <= 0) : self.m.append(iso.COOLANT_OFF) - #elif (mode == 1) : self.m.append(iso.COOLANT_MIST) - #elif (mode == 2) : self.m.append(iso.COOLANT_FLOOD) - pass + # elif (mode == 1) : self.m.append(iso.COOLANT_MIST) + # elif (mode == 2) : self.m.append(iso.COOLANT_FLOOD) + pass def gearrange(self, gear=0): #if (gear <= 0) : self.m.append(iso.GEAR_OFF) - #elif (gear <= 4) : self.m.append(iso.GEAR % (gear + GEAR_BASE)) - pass + # elif (gear <= 4) : self.m.append(iso.GEAR % (gear + GEAR_BASE)) + pass ############################################################################ - ## Moves + # Moves - #def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): - def rapid(self, x=0.0000, y=0.0000, z=0.0000, a=0.0000, b=0.0000, c=0.0000,how=False): - #self.write_blocknum() + # def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): + def rapid(self, x=0.0000, y=0.0000, z=0.0000, a=0.0000, b=0.0000, c=0.0000, how=False): + # self.write_blocknum() if (x == None): - if (y == None): - return - print 'rychlopsuv' - print x - print y - print z - print a - print b - print c - self.write('PL_OFF\n') - self.write('PARK\n') - self.write('FS\n') - self.write('MA ') - #self.write_preps() - #if (x != None): + if (y == None): + return + print 'rychlopsuv' + print x + print y + print z + print a + print b + print c + self.write('PL_OFF\n') + self.write('PARK\n') + self.write('FS\n') + self.write('MA ') + # self.write_preps() + # if (x != None): # dx = x - self.x # self.write(iso.X + (self.fmt % x)) # self.x = x - #if (y != None): + # if (y != None): # dy = y - self.y # self.write(iso.Y + (self.fmt % y)) # self.y = y - #if (z != None): + # if (z != None): # dz = z - self.z # self.write(iso.Z + (self.fmt % z)) # self.z = z #if (a != None) : self.write(iso.A + (iso.FORMAT_ANG % a)) #if (b != None) : self.write(iso.B + (iso.FORMAT_ANG % b)) #if (c != None) : self.write(iso.C + (iso.FORMAT_ANG % c)) - #self.write_spindle() - #self.write_misc() - self.write((' %.4f' %x)) - #self.write(('%f' %x) ) - self.write(' , ') - self.write((' %.4f' %y)) - self.write('\n') - self.write('SS\n') - self.write('AD_W\n') - self.write('PL_ON') - self.write('\n') - self.x=x - self.y=y - - def feed(self, x=0.0000, y=0.0000, z=0.0000,how=False): + # self.write_spindle() + # self.write_misc() + self.write((' %.4f' % x)) + #self.write(('%f' %x) ) + self.write(' , ') + self.write((' %.4f' % y)) + self.write('\n') + self.write('SS\n') + self.write('AD_W\n') + self.write('PL_ON') + self.write('\n') + self.x = x + self.y = y + + def feed(self, x=0.0000, y=0.0000, z=0.0000, how=False): if self.same_xyz(x, y, z): - return + return if (x == None): - if (y == None): - return - - print 'MA rez' - print x - print y - print z - #self.write_blocknum() - #self.write(iso.FEED) - #self.write_preps() + if (y == None): + return + + print 'MA rez' + print x + print y + print z + # self.write_blocknum() + # self.write(iso.FEED) + # self.write_preps() #dx = dy = dz = 0 - #if (x != None): + # if (x != None): # dx = x - self.x # self.write(iso.X + (self.fmt % x)) # self.x = x - #if (y != None): + # if (y != None): # dy = y - self.y # self.write(iso.Y + (self.fmt % y)) # self.y = y - #if (z != None): + # if (z != None): # dz = z - self.z # self.write(iso.Z + (self.fmt % z)) # self.z = z #if (self.fhv) : self.calc_feedrate_hv(math.sqrt(dx*dx+dy*dy), math.fabs(dz)) - #self.write_feedrate() - #self.write_spindle() - #self.write_misc() + # self.write_feedrate() + # self.write_spindle() + # self.write_misc() self.write('MA ') - self.write((' %.4f' %x)) - #self.write(('%f' %x) ) - self.write(' , ') - self.write((' %.4f' %y)) - self.write('\n') - self.x=x - self.y=y + self.write((' %.4f' % x)) + #self.write(('%f' %x) ) + self.write(' , ') + self.write((' %.4f' % y)) + self.write('\n') + self.x = x + self.y = y def same_xyz(self, x=None, y=None, z=None): if (x != None): @@ -336,96 +344,98 @@ def same_xyz(self, x=None, y=None, z=None): return True def arc(self, cw, x=0.0000, y=0.0000, z=0.0000, i=0.0000, j=0.0000, k=0.0000, r=0.0000): - if self.same_xyz(x, y, z): return - if (x == None): - if (y == None): - return - #self.write_blocknum() - print 'ARC rez' - print x - print y - print z - print i - print j - print k - print r - self.write('C ') - # if cw: self.write(iso.ARC_CW) - # else: self.write(iso.ARC_CCW) - # self.write_preps() - #if (x != None): + if self.same_xyz(x, y, z): + return + if (x == None): + if (y == None): + return + # self.write_blocknum() + print 'ARC rez' + print x + print y + print z + print i + print j + print k + print r + self.write('C ') + # if cw: self.write(iso.ARC_CW) + # else: self.write(iso.ARC_CCW) + # self.write_preps() + # if (x != None): # self.write(iso.X + (self.fmt % x)) # self.x = x - #if (y != None): + # if (y != None): # self.write(iso.Y + (self.fmt % y)) # self.y = y - #if (z != None): - # self.write(iso.Z + (self.fmt % z)) - # self.z = z + # if (z != None): + # self.write(iso.Z + (self.fmt % z)) + # self.z = z #if (i != None) : self.write(iso.CENTRE_X + (self.fmt % i)) #if (j != None) : self.write(iso.CENTRE_Y + (self.fmt % j)) #if (k != None) : self.write(iso.CENTRE_Z + (self.fmt % k)) #if (r != None) : self.write(iso.RADIUS + (self.fmt % r)) # use horizontal feed rate #if (self.fhv) : self.calc_feedrate_hv(1, 0) - #self.write_feedrate() - #self.write_spindle() - #self.write_misc() - self.write((' %.4f' %(self.x + i))) - #self.write(('%f' %x) ) - self.write(' , ') - self.write((' %.4f' %(self.y + j))) - - self.write(' , ') - angle=0.0000 - - self.x1=-i - self.y1=-j - self.x2=x-(self.x+i) - self.y2=y-(self.y+j) - - dx=self.x1*self.x2 - dy=self.y1*self.y2 - ssucin=dx+dy - r=math.sqrt((i*i)+(j*j)) - - #dx=i*(x-(self.x + i)) - #dy=j*(y-(self.y + j)) - #ssucin=dx+dy - #r=math.sqrt((i*i)+(j*j)) - ratio=ssucin/(r*r) - angle= math.acos(ratio) * 180 / math.pi - print 'angle' - print angle + # self.write_feedrate() + # self.write_spindle() + # self.write_misc() + self.write((' %.4f' % (self.x + i))) + #self.write(('%f' %x) ) + self.write(' , ') + self.write((' %.4f' % (self.y + j))) + + self.write(' , ') + angle = 0.0000 + + self.x1 = -i + self.y1 = -j + self.x2 = x-(self.x+i) + self.y2 = y-(self.y+j) + + dx = self.x1*self.x2 + dy = self.y1*self.y2 + ssucin = dx+dy + r = math.sqrt((i*i)+(j*j)) + + #dx=i*(x-(self.x + i)) + #dy=j*(y-(self.y + j)) + # ssucin=dx+dy + # r=math.sqrt((i*i)+(j*j)) + ratio = ssucin/(r*r) + angle = math.acos(ratio) * 180 / math.pi + print 'angle' + print angle # while(angle>=90.0001): # if(cw): self.write((' -90.0000\n' )) # else: self.write((' 90.0000\n')) - #self.write(('90.0000\n' )) + #self.write(('90.0000\n' )) # self.write('C ') # self.write((' %.4f' %( self.x + i) )) # self.write(' , ') # self.write((' %.4f' %( self.y + j))) # self.write(' , ') # angle-=90 - if(cw): self.write((' %.4f' %(-angle))) - else: self.write((' %.4f' %(angle))) - - - #self.write((', %.4f' % )) - self.write('\n') - self.x=x - self.y=y - self.write('// stred') - self.write((' %f' %i)) - self.write(' ') - self.write( (' %f' %j)) - self.write('\n') - - self.write('// koniec') - self.write((' %f' %self.x)) - self.write(' ') - self.write( (' %f' %self.y)) - self.write('\n') + if(cw): + self.write((' %.4f' % (-angle))) + else: + self.write((' %.4f' % (angle))) + + # self.write((', %.4f' % )) + self.write('\n') + self.x = x + self.y = y + self.write('// stred') + self.write((' %f' % i)) + self.write(' ') + self.write((' %f' % j)) + self.write('\n') + + self.write('// koniec') + self.write((' %f' % self.x)) + self.write(' ') + self.write((' %f' % self.y)) + self.write('\n') def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.arc(True, x, y, z, i, j, k, r) @@ -447,7 +457,7 @@ def rapid_unhome(self): pass ############################################################################ - ## Cycles + # Cycles def pattern(self): pass @@ -458,54 +468,56 @@ def pocket(self): def profile(self): pass - def circular_pocket(self, x=None, y=None, ToolDiameter=None, HoleDiameter=None, ClearanceHeight=None, StartHeight=None, MaterialTop=None, FeedRate=None, SpindleRPM=None, HoleDepth=None, DepthOfCut=None, StepOver=None ): - self.write_preps() - circular.pocket.block_number = self.n - (self.g_code, self.n) = circular.pocket.GeneratePath( x,y, ToolDiameter, HoleDiameter, ClearanceHeight, StartHeight, MaterialTop, FeedRate, SpindleRPM, HoleDepth, DepthOfCut, StepOver ) - self.write(self.g_code) - - # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). - # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to - # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. - # Instead, this routine combines the Z value and the depth value to determine the bottom of - # the hole. - # - # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts - # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. - # - # The peck_depth value is the incremental depth (Q value) that tells the peck drilling - # cycle how deep to go on each peck until the full depth is achieved. - # - # NOTE: This routine forces the mode to absolute mode so that the values passed into - # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't - # revert it. I must set the mode so that I can be sure the values I'm passing in make - # sense to the end-machine. - # - def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): - if (standoff == None): - # This is a bad thing. All the drilling cycles need a retraction (and starting) height. - return - - if (z == None): return # We need a Z value as well. This input parameter represents the top of the hole + def circular_pocket(self, x=None, y=None, ToolDiameter=None, HoleDiameter=None, ClearanceHeight=None, StartHeight=None, MaterialTop=None, FeedRate=None, SpindleRPM=None, HoleDepth=None, DepthOfCut=None, StepOver=None): + self.write_preps() + circular.pocket.block_number = self.n + (self.g_code, self.n) = circular.pocket.GeneratePath(x, y, ToolDiameter, HoleDiameter, + ClearanceHeight, StartHeight, MaterialTop, FeedRate, SpindleRPM, HoleDepth, DepthOfCut, StepOver) + self.write(self.g_code) + + # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). + # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to + # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. + # Instead, this routine combines the Z value and the depth value to determine the bottom of + # the hole. + # + # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts + # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. + # + # The peck_depth value is the incremental depth (Q value) that tells the peck drilling + # cycle how deep to go on each peck until the full depth is achieved. + # + # NOTE: This routine forces the mode to absolute mode so that the values passed into + # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't + # revert it. I must set the mode so that I can be sure the values I'm passing in make + # sense to the end-machine. + # + def drill(self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None): + if (standoff == None): + # This is a bad thing. All the drilling cycles need a retraction (and starting) height. + return + + if (z == None): + return # We need a Z value as well. This input parameter represents the top of the hole self.write_blocknum() self.write_preps() - if (peck_depth != 0): - # We're pecking. Let's find a tree. - self.write(iso.PECK_DRILL + iso.SPACE + iso.PECK_DEPTH + (self.fmt % peck_depth )) - else: - # We're either just drilling or drilling with dwell. - if (dwell == 0): - # We're just drilling. - self.write(iso.DRILL + iso.SPACE) - else: - # We're drilling with dwell. - self.write(iso.DRILL_WITH_DWELL + (iso.FORMAT_DWELL % dwell) + iso.SPACE) - - # Set the retraction point to the 'standoff' distance above the starting z height. - retract_height = z + standoff - #self.write(iso.RETRACT + (self.fmt % retract_height)) + if (peck_depth != 0): + # We're pecking. Let's find a tree. + self.write(iso.PECK_DRILL + iso.SPACE + iso.PECK_DEPTH + (self.fmt % peck_depth)) + else: + # We're either just drilling or drilling with dwell. + if (dwell == 0): + # We're just drilling. + self.write(iso.DRILL + iso.SPACE) + else: + # We're drilling with dwell. + self.write(iso.DRILL_WITH_DWELL + (iso.FORMAT_DWELL % dwell) + iso.SPACE) + + # Set the retraction point to the 'standoff' distance above the starting z height. + retract_height = z + standoff + #self.write(iso.RETRACT + (self.fmt % retract_height)) if (x != None): dx = x - self.x self.write(iso.X + (self.fmt % x) + iso.SPACE) @@ -515,11 +527,13 @@ def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=Non self.write(iso.Y + (self.fmt % y) + iso.SPACE) self.y = y - dz = (z + standoff) - self.z - # In the end, we will be standoff distance above the z value passed in. - self.write(iso.Z + (self.fmt % (z - depth)) + iso.SPACE) # This is the 'z' value for the bottom of the hole. - self.z = (z + standoff) # We want to remember where z is at the end (at the top of the hole) - self.write(iso.RETRACT + (self.fmt % retract_height)) + dz = (z + standoff) - self.z + # In the end, we will be standoff distance above the z value passed in. + # This is the 'z' value for the bottom of the hole. + self.write(iso.Z + (self.fmt % (z - depth)) + iso.SPACE) + # We want to remember where z is at the end (at the top of the hole) + self.z = (z + standoff) + self.write(iso.RETRACT + (self.fmt % retract_height)) self.write_spindle() self.write_misc() self.write('\n') @@ -529,6 +543,8 @@ def drill(self, x=None, y=None, dwell=None, depthparams = None, retract_mode=Non # pass # argument list adapted for compatibility with Tapping module + + def tap(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, dwell_bottom=None, pitch=None, stoppos=None, spin_in=None, spin_out=None, tap_mode=None, direction=None): pass @@ -536,10 +552,10 @@ def bore(self, x=None, y=None, z=None, zretract=None, depth=None, standoff=None, pass ############################################################################ - ## Misc + # Misc def comment(self, text): - self.write('// ' + (self.COMMENT( text)) + '\n') + self.write('// ' + (self.COMMENT(text)) + '\n') def variable(self, id): return (iso.VARIABLE % id) @@ -550,4 +566,5 @@ def variable_set(self, id, value): ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/rez2_read.py b/scripts/addons/cam/nc/rez2_read.py index 277007c76..b562fbfc2 100644 --- a/scripts/addons/cam/nc/rez2_read.py +++ b/scripts/addons/cam/nc/rez2_read.py @@ -9,21 +9,23 @@ import math ################################################################################ -class Parser(nc.Parser): +class Parser(nc.Parser): + def __init__(self, writer): nc.Parser.__init__(self, writer) - #self.pattern_main = re.compile('(\s+|\w(?:[+])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+])?\d*(?:\.\d*)?)') + # self.pattern_main = re.compile('(\s+|\w(?:[+])?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:[+])?\d*(?:\.\d*)?)') - #rada - self.pattern_main = re.compile(r'(\s+|,|-?\w\+?\d*(?:\.\d*)?|\w#\d+|\(.*?\)|#\d+=\+?\d*(?:\.\d*)?)') - #self.pattern_main = re.compile('\s+\w') - #self.pattern_main = re.compile('(\s+|\w(?:[+])?[+-\w]\d*(?:\.\d*)?|\w\#[+-\w]\d+|\(.*?\)|[\#[+-\w]\d+\=(?:[+])?[+-\w]\d*(?:\.\d*)?)') - #self.pattern_main = re.compile('\s\w[\S]\w,\w[+-\w]\d*\w,\w[+-\w]\d*\w,\w[+-\w]\d*') - #self.pattern_main = re.compile('(\s|\w(?:)?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:)?\d*(?:\.\d*)?)') - #self.pattern_main = re.compile(' ') + # rada + self.pattern_main = re.compile( + r'(\s+|,|-?\w\+?\d*(?:\.\d*)?|\w#\d+|\(.*?\)|#\d+=\+?\d*(?:\.\d*)?)') + #self.pattern_main = re.compile('\s+\w') + # self.pattern_main = re.compile('(\s+|\w(?:[+])?[+-\w]\d*(?:\.\d*)?|\w\#[+-\w]\d+|\(.*?\)|[\#[+-\w]\d+\=(?:[+])?[+-\w]\d*(?:\.\d*)?)') + #self.pattern_main = re.compile('\s\w[\S]\w,\w[+-\w]\d*\w,\w[+-\w]\d*\w,\w[+-\w]\d*') + # self.pattern_main = re.compile('(\s|\w(?:)?\d*(?:\.\d*)?|\w\#\d+|\(.*?\)|\#\d+\=(?:)?\d*(?:\.\d*)?)') + #self.pattern_main = re.compile(' ') self.a = 0 self.b = 0 @@ -39,252 +41,266 @@ def __init__(self, writer): self.x = 0 self.y = 0 self.z = 500 - self.FS = 1 - self.endx=0 - self.endy=0 - self.startx=0 - self.starty=0 - self.x1=0 - self.y1=0 - self.dy=0 - self.dx=0 - self.angle=0 - self.SPACE = ' ' + self.FS = 1 + self.endx = 0 + self.endy = 0 + self.startx = 0 + self.starty = 0 + self.x1 = 0 + self.y1 = 0 + self.dy = 0 + self.dx = 0 + self.angle = 0 + self.SPACE = ' ' def add_text(self, s, col=None): s.replace('&', '&') s.replace('"', '"') s.replace('<', '<') s.replace('>', '>') - s+=self.SPACE+'\n' - if (col != None) : self.file_out.write('\t\t'+s+' \n') - else : self.file_out.write('\t\t'+s+' \n') - #def add_text(self, s, col=None): + s += self.SPACE+'\n' + if (col != None): + self.file_out.write('\t\t'+s+' \n') + else: + self.file_out.write('\t\t'+s+' \n') + # def add_text(self, s, col=None): # if (col != None) : self.file_out.write('\t\t'+s+'\n') # else : self.file_out.write('\t\t'+s+'\n') def Parse(self, name, oname=None): - self.files_open(name,oname) + self.files_open(name, oname) while (self.readline()): self.begin_ncblock() - move = False; - arc = 0; + move = False + arc = 0 path_col = None - col=None - if(self.line[0]=='C'): col = "axis" - if(self.line[0]=='M' and self.line[1]=='A'): col = "feed" - if (self.line[0] == "/" and self.line[1] == "/") : col = "comment" + col = None + if(self.line[0] == 'C'): + col = "axis" + if(self.line[0] == 'M' and self.line[1] == 'A'): + col = "feed" + if (self.line[0] == "/" and self.line[1] == "/"): + col = "comment" - if (self.FS==1 and not (self.line[0]=='S' and self.line[1]=='S') and col=="feed" ): col="rapid" - self.add_text(self.line, col) + if (self.FS == 1 and not (self.line[0] == 'S' and self.line[1] == 'S') and col == "feed"): + col = "rapid" + self.add_text(self.line, col) #words = self.pattern_main.findall(self.line) - words=self.line.split() - #print self.line - #print ' AAAA ' - words[0]=words[0]+self.SPACE - print words - for word in words: + words = self.line.split() + # print self.line + # print ' AAAA ' + words[0] = words[0]+self.SPACE + print words + for word in words: col = None - #if (word[0] == 'A' or word[0] == 'a'): + # if (word[0] == 'A' or word[0] == 'a'): # col = "axis" # self.a = eval(word[1:]) # move = True - #elif (word[0] == 'B' or word[0] == 'b'): + # elif (word[0] == 'B' or word[0] == 'b'): # col = "axis" # self.b = eval(word[1:]) - # move = True + # move = True if (word == ('C'+self.SPACE)): - #print words - col = "axis" - self.startx=self.x - self.starty=self.y - words[0]=words[0]+self.SPACE - words[2]=self.SPACE+words[2]+self.SPACE - words[4]=self.SPACE+words[4]+self.SPACE - - #print 'x,y' - #print self.x - #print self.y - #self.x1=self.x-eval(words[1]) - self.x1=self.x-eval(words[1]) - #j=self.y-eval(words[5]) - #self.y1=self.y-eval(words[3]) - self.y1=self.y-eval(words[3]) - #self.c = eval(word[1:]) - #print 'self x,y' - #print self.x1 - #print self.y1 - self.dx=(self.x1)*1 - self.dy=(self.y1)*0 - #print 'x1' - #print self.x1 - #print 'y1' - #print self.y1 - ssucin=self.dx+self.dy - r=math.sqrt(((self.x1)*(self.x1))+((self.y1)*(self.y1))) - #print 'skalarny sucin' - #print ssucin - #print 'r' - #print r - if (ssucin!=0): - ratio=ssucin/(r*1) - #print 'ratio' - #print ratio - angle= (math.acos(ratio) * 180 / math.pi) - if(self.y1<0):angle=360-angle - elif (self.y1>0): angle=+90 - elif (self.y1<0): angle=-90 - else: angle=0 - #print words[8] - #print 'angles' - #print angle - #if (i<0 and j<0): angle=180+angle - #if (i<0 and j>0): angle=180-angle - #if (j>0): angle=-angle - #print ('reverzacia') - #angle= angle+ eval(words[8]) - #print angle - self.angle=+ angle+ eval(words[5]) - #print self.angle - #if(angle>180): angle=360-angle - angle=self.angle*math.pi/180 - #print eval(words[8]) - self.endx=eval(words[1])+(r*math.cos(angle)) - #j=eval(words[5])+(r*math.sin(angle)) - self.endy=eval(words[3])+(r*math.sin(angle)) - self.x=self.endx - self.y=self.endy - path_col = "feed" - #arc=-eval(words[8])/math.fabs(eval(words[8])) - arc=eval(words[5])/math.fabs(eval(words[5])) - #if(arc==-1): arc=0 - #arc=-1 - #col = "feed" - move = True + # print words + col = "axis" + self.startx = self.x + self.starty = self.y + words[0] = words[0]+self.SPACE + words[2] = self.SPACE+words[2]+self.SPACE + words[4] = self.SPACE+words[4]+self.SPACE + # print 'x,y' + # print self.x + # print self.y + # self.x1=self.x-eval(words[1]) + self.x1 = self.x-eval(words[1]) + # j=self.y-eval(words[5]) + # self.y1=self.y-eval(words[3]) + self.y1 = self.y-eval(words[3]) + #self.c = eval(word[1:]) + # print 'self x,y' + # print self.x1 + # print self.y1 + self.dx = (self.x1)*1 + self.dy = (self.y1)*0 + # print 'x1' + # print self.x1 + # print 'y1' + # print self.y1 + ssucin = self.dx+self.dy + r = math.sqrt(((self.x1)*(self.x1))+((self.y1)*(self.y1))) + # print 'skalarny sucin' + # print ssucin + # print 'r' + # print r + if (ssucin != 0): + ratio = ssucin/(r*1) + # print 'ratio' + # print ratio + angle = (math.acos(ratio) * 180 / math.pi) + if(self.y1 < 0): + angle = 360-angle + elif (self.y1 > 0): + angle = +90 + elif (self.y1 < 0): + angle = -90 + else: + angle = 0 + # print words[8] + # print 'angles' + # print angle + #if (i<0 and j<0): angle=180+angle + #if (i<0 and j>0): angle=180-angle + #if (j>0): angle=-angle + #print ('reverzacia') + #angle= angle+ eval(words[8]) + # print angle + self.angle = + angle + eval(words[5]) + # print self.angle + #if(angle>180): angle=360-angle + angle = self.angle*math.pi/180 + # print eval(words[8]) + self.endx = eval(words[1])+(r*math.cos(angle)) + # j=eval(words[5])+(r*math.sin(angle)) + self.endy = eval(words[3])+(r*math.sin(angle)) + self.x = self.endx + self.y = self.endy + path_col = "feed" + # arc=-eval(words[8])/math.fabs(eval(words[8])) + arc = eval(words[5])/math.fabs(eval(words[5])) + #if(arc==-1): arc=0 + # arc=-1 + #col = "feed" + move = True - elif (word == 'P' and words[1]=='L' and words[4]=='F'): - self.FS=1 - elif (word == 'P' and words[1]=='L' and words[4]=='N'): - self.FS=0 - elif (word == ('FS'+self.SPACE)): - self.FS=1 - elif (word == ('SS'+self.SPACE)): - self.FS=0 + elif (word == 'P' and words[1] == 'L' and words[4] == 'F'): + self.FS = 1 + elif (word == 'P' and words[1] == 'L' and words[4] == 'N'): + self.FS = 0 + elif (word == ('FS'+self.SPACE)): + self.FS = 1 + elif (word == ('SS'+self.SPACE)): + self.FS = 0 elif (word == ('MA'+self.SPACE)): - words[2]=self.SPACE+words[2]+self.SPACE - if (self.FS==1): - path_col = "rapid" - col = "rapid" - else: - path_col = "feed" - col = "feed" - self.x=eval(words[1]) - #self.y=eval(words[6]) - self.y=eval(words[3]) - move=True - #elif (word == 'G1' or word == 'G01' or word == 'g1' or word == 'g01'): + words[2] = self.SPACE+words[2]+self.SPACE + if (self.FS == 1): + path_col = "rapid" + col = "rapid" + else: + path_col = "feed" + col = "feed" + self.x = eval(words[1]) + # self.y=eval(words[6]) + self.y = eval(words[3]) + move = True + # elif (word == 'G1' or word == 'G01' or word == 'g1' or word == 'g01'): # path_col = "feed" # col = "feed" - #elif (word == 'G2' or word == 'G02' or word == 'g2' or word == 'g02' or word == 'G12' or word == 'g12'): + # elif (word == 'G2' or word == 'G02' or word == 'g2' or word == 'g02' or word == 'G12' or word == 'g12'): # path_col = "feed" # col = "feed" # arc = -1 - #elif (word == 'G3' or word == 'G03' or word == 'g3' or word == 'g03' or word == 'G13' or word == 'g13'): + # elif (word == 'G3' or word == 'G03' or word == 'g3' or word == 'g03' or word == 'G13' or word == 'g13'): # path_col = "feed" # col = "feed" # arc = +1 - #elif (word == 'G10' or word == 'g10'): - # move = False - #elif (word == 'L1' or word == 'l1'): - # move = False - #elif (word == 'G20'): + # elif (word == 'G10' or word == 'g10'): + # move = False + # elif (word == 'L1' or word == 'l1'): + # move = False + # elif (word == 'G20'): # col = "prep" # self.set_mode(units=25.4) - #elif (word == 'G21'): + # elif (word == 'G21'): # col = "prep" # self.set_mode(units=1.0) - #elif (word == 'G81' or word == 'g81'): + # elif (word == 'G81' or word == 'g81'): # path_col = "feed" # col = "feed" - #elif (word == 'G82' or word == 'g82'): + # elif (word == 'G82' or word == 'g82'): # path_col = "feed" # col = "feed" - #elif (word == 'G83' or word == 'g83'): + # elif (word == 'G83' or word == 'g83'): # path_col = "feed" # col = "feed" - #elif (word[0] == 'G') : col = "prep" - #elif (word[0] == 'I' or word[0] == 'i'): + # elif (word[0] == 'G') : col = "prep" + # elif (word[0] == 'I' or word[0] == 'i'): # col = "axis" # self.i = eval(word[1:]) # move = True - #elif (word[0] == 'J' or word[0] == 'j'): + # elif (word[0] == 'J' or word[0] == 'j'): # col = "axis" # self.j = eval(word[1:]) # move = True - #elif (word[0] == 'K' or word[0] == 'k'): + # elif (word[0] == 'K' or word[0] == 'k'): # col = "axis" # self.k = eval(word[1:]) # move = True - #elif (word[0] == 'M') : col = "misc" - #elif (word[0] == 'N') : col = "blocknum" - #elif (word[0] == 'O') : col = "program" - #elif (word[0] == 'P' or word[0] == 'p'): + # elif (word[0] == 'M') : col = "misc" + # elif (word[0] == 'N') : col = "blocknum" + # elif (word[0] == 'O') : col = "program" + # elif (word[0] == 'P' or word[0] == 'p'): # col = "axis" # self.p = eval(word[1:]) # move = True - #elif (word[0] == 'Q' or word[0] == 'q'): + # elif (word[0] == 'Q' or word[0] == 'q'): # col = "axis" # self.q = eval(word[1:]) # move = True - #elif (word[0] == 'R' or word[0] == 'r'): + # elif (word[0] == 'R' or word[0] == 'r'): # col = "axis" # self.r = eval(word[1:]) # move = True - #elif (word[0] == 'S' or word[0zypp] == 's'): + # elif (word[0] == 'S' or word[0zypp] == 's'): # col = "axis" # self.s = eval(word[1:]) # move = True - #elif (word[0] == 'T') : col = "tool" - #elif (word[0] == 'X' or word[0] == 'x'): + # elif (word[0] == 'T') : col = "tool" + # elif (word[0] == 'X' or word[0] == 'x'): # col = "axis" # = eval(word[1:]) # move = True - #elif (word[0] == 'Y' or word[0] == 'y'): + # elif (word[0] == 'Y' or word[0] == 'y'): # col = "axis" # self.y = eval(word[1:]) # move = True - #elif (word[0] == 'Z' or word[0] == 'z'): + # elif (word[0] == 'Z' or word[0] == 'z'): # col = "axis" # self.z = eval(word[1:]) # move = True - elif (word[0] == '(') : col = "comment" - elif (word[0] == '#') : col = "variable" - elif (words[0] == ("//"+self.SPACE)) : col = "comment" - elif (words[0] == ("/*"+self.SPACE)) : col = "comment" - #self.add_text(word, col) + elif (word[0] == '('): + col = "comment" + elif (word[0] == '#'): + col = "variable" + elif (words[0] == ("//"+self.SPACE)): + col = "comment" + elif (words[0] == ("/*"+self.SPACE)): + col = "comment" + #self.add_text(word, col) if (move): self.begin_path(path_col) - if (arc) : - #self.add_arc(self.x, self.y, 0.0000, self.i, self.j, 0.0000, arc) - #self.add_arc(self.i, self.j, 0.0000, eval(words[2])-self.x, eval(words[5])-self.y, 0.0000, arc) - #print '' - #print eval(words[2])-self.startx - #print eval(words[6])-self.starty - #self.add_arc(self.endx, self.endy, 0.0000, eval(words[1])-self.startx, eval(words[3])-self.starty, 0.0000, arc) - print arc - self.add_arc(self.endx, self.endy, 0.0000,eval(words[1])-self.startx, eval(words[3])-self.starty, 0.0000,0.0000, arc) + if (arc): + #self.add_arc(self.x, self.y, 0.0000, self.i, self.j, 0.0000, arc) + #self.add_arc(self.i, self.j, 0.0000, eval(words[2])-self.x, eval(words[5])-self.y, 0.0000, arc) + # print '' + # print eval(words[2])-self.startx + # print eval(words[6])-self.starty + #self.add_arc(self.endx, self.endy, 0.0000, eval(words[1])-self.startx, eval(words[3])-self.starty, 0.0000, arc) + print arc + self.add_arc(self.endx, self.endy, 0.0000, eval( + words[1])-self.startx, eval(words[3])-self.starty, 0.0000, 0.0000, arc) - #self.add_arc(self.x, self.y, 0.0000, self.i, self.j, 0.0000, arc) - #self.x=self.i - #self.y=self.j - else : - self.add_line(self.x, self.y) - self.end_path() + #self.add_arc(self.x, self.y, 0.0000, self.i, self.j, 0.0000, arc) + # self.x=self.i + # self.y=self.j + else: + self.add_line(self.x, self.y) + self.end_path() self.end_ncblock() diff --git a/scripts/addons/cam/nc/series1.py b/scripts/addons/cam/nc/series1.py index c33539412..4bda80351 100644 --- a/scripts/addons/cam/nc/series1.py +++ b/scripts/addons/cam/nc/series1.py @@ -3,6 +3,8 @@ import math ################################################################################ + + class Creator(iso_modal.Creator): def __init__(self): @@ -24,8 +26,9 @@ def metric(self): self.fmt.number_of_decimal_places = 2 def SPACE(self): - return('') + return('') ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/series1_read.py b/scripts/addons/cam/nc/series1_read.py index e94a2b4b2..f07ff91f3 100644 --- a/scripts/addons/cam/nc/series1_read.py +++ b/scripts/addons/cam/nc/series1_read.py @@ -3,6 +3,7 @@ # use the iso reader, but with i_and_j_always_positive + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/shopbot_mtc.py b/scripts/addons/cam/nc/shopbot_mtc.py index 773c2e101..57276d902 100644 --- a/scripts/addons/cam/nc/shopbot_mtc.py +++ b/scripts/addons/cam/nc/shopbot_mtc.py @@ -19,206 +19,205 @@ import bpy ################################################################################ + + class Creator(nc.Creator): - def __init__(self): - nc.Creator.__init__(self) - - s=bpy.context.scene - cm=s.cam_machine - self.fmt = Format() - self.ffmt = Format(number_of_decimal_places = 2) - self.sfmt = Format(number_of_decimal_places = 1) - self.unitscale = 1 - self.startx = cm.starting_position.x - self.starty = cm.starting_position.y - self.startz = cm.starting_position.z - self.tcx = cm.mtc_position.x - self.tcy = cm.mtc_position.y - self.tcz = cm.mtc_position.z - self.endx = cm.ending_position.x - self.endy = cm.ending_position.y - self.endz = cm.ending_position.z - self.metric_flag = False - self.absolute_flag = True - - ############################################################################ - ## Codes - - def SPACE(self): return('') - def COMMENT(self,comment): return( '\' %s' % comment ) - def TOOL(self): return('T%i' + self.SPACE() + 'M06') - - ############################################################################ - ## Internals - - def write_feedrate(self): - self.f.write(self) - - def write_spindle(self): - self.s.write(self) - - ############################################################################ - ## Programs - - def program_begin(self, id, name=''): - self.writem([self.SPACE() , self.COMMENT(name)]) - self.write('\n\n') - self.write('IF %(25)=1 THEN GOTO UNIT_ERROR\n') - self.write('\n') - - def program_end(self): - self.write('C7\n') - self.write('JZ,' + self.fmt.string(self.endz) + '\n') - self.write('J2,' + self.fmt.string(self.endx) + ',' + self.fmt.string(self.endy) + '\n') - self.write('END\n') - self.write('\'\n') - self.write('UNIT_ERROR:\n') - self.write('C#,91\n') - self.write('END\n') - - def flush_nc(self): - return - - ############################################################################ - ## Subprograms - - - ############################################################################ - ## Settings - - def imperial(self): - self.fmt.number_of_decimal_places = 4 - self.unitscale = 0.0254 - self.metric_flag=False - - def metric(self): - self.metric_flag=True - self.unitscale = 1 - self.fmt.number_of_decimal_places = 3 - - def absolute(self): - self.absolute_flag = True - self.write('SA\n') - - def incremental(self): - self.absolute_flag = False - self.write('SR\n') - - def set_plane(self, plane): - return - - ############################################################################ - ## new graphics origin- make a new coordinate system and snap it onto the geometry - ## the toolpath generated should be translated - - - ############################################################################ - ## Tools - - def tool_change(self, id): - self.write('C7\n') - self.write('&Tool=' + self.fmt.string(id) + '\n') - self.write('JZ,' + (self.fmt.string(self.tcz)) + '\n') - self.write('J2,' + (self.fmt.string(self.tcx)) + ',' + (self.fmt.string(self.tcy)) + '\n') - self.write('\' Use tool '+ self.fmt.string(id) + '\n') - self.write('PAUSE\n') - self.write('C2\n') - self.t = id - self.write('C6\n') - self.write('PAUSE 2\n') - - ############################################################################ - ## Datums - - - ############################################################################ - ## Rates + Modes - - def feedrate(self, f): - if (self.metric_flag == True): - self.write('MS,' + (self.sfmt.string(f)) + ',' + (self.sfmt.string(f)) + '\n') - else: - self.write('MS,' + (self.sfmt.string(f*self.unitscale)) + ',' + (self.sfmt.string(f*self.unitscale)) + '\n') - - def spindle(self, s, clockwise): - self.write('TR,' + self.fmt.string(s) + '\n') - - ############################################################################ - ## Moves - - def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): - # different commands for X only, or Y only, or Z only, or (X and Y), or (X, Y, and Z) - if (x != None and y != None and z != None): - self.write('J3,' + (self.fmt.string(x * self.unitscale))) - self.write(',' + (self.fmt.string(y * self.unitscale))) - self.write(',' + (self.fmt.string(z * self.unitscale))) - self.write('\n') - elif (x != None and y != None and z == None): - self.write('J2,' + (self.fmt.string(x * self.unitscale))) - self.write(',' + (self.fmt.string(y * self.unitscale))) - self.write('\n') - elif (x != None): - self.write('JX,' + (self.fmt.string(x * self.unitscale)) + '\n') - elif (y != None): - self.write('JY,' + (self.fmt.string(y * self.unitscale)) + '\n') - elif (z != None): - self.write('JZ,' + (self.fmt.string(z * self.unitscale)) + '\n') - - def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): - if (x != None and y != None and z != None): - self.write('M3,' + (self.fmt.string(x * self.unitscale))) - self.write(',' + (self.fmt.string(y * self.unitscale))) - self.write(',' + (self.fmt.string(z * self.unitscale))) - self.write('\n') - elif (x != None and y != None and z == None): - self.write('M2,' + (self.fmt.string(x * self.unitscale))) - self.write(',' + (self.fmt.string(y * self.unitscale))) - self.write('\n') - elif (x != None): - self.write('MX,' + (self.fmt.string(x * self.unitscale)) + '\n') - elif (y != None): - self.write('MY,' + (self.fmt.string(y * self.unitscale)) + '\n') - elif (z != None): - self.write('MZ,' + (self.fmt.string(z * self.unitscale)) + '\n') - - def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): - if (r != None): - self.write('CG,' + self.fmt.string(r * self.unitscale * 2)) - self.write(',' + self.fmt.string(x * self.unitscale)) - self.write(',' + self.fmt.string(y * self.unitscale)) - self.write(', ,') - self.write(',T,1') - self.write('\n') - else: - self.write('CG, ') - self.write(',' + self.fmt.string(x * self.unitscale)) - self.write(',' + self.fmt.string(y * self.unitscale)) - self.write(',' + self.fmt.string(i * self.unitscale)) - self.write(',' + self.fmt.string(j * self.unitscale)) - self.write(',T,1') - self.write('\n') - return - - ############################################################################ - ## CRC - - - ############################################################################ - ## Cycles - - - ############################################################################ - ## Misc - - def comment(self, text): - self.write((self.COMMENT(text) + '\n')) - - def variable(self, id): - return (self.VARIABLE() % id) - - def variable_set(self, id, value): - self.write('&' + (self.VARIABLE() % id) + self.SPACE() + (self.VARIABLE_SET() % value) + '\n') + def __init__(self): + nc.Creator.__init__(self) + + s = bpy.context.scene + cm = s.cam_machine + self.fmt = Format() + self.ffmt = Format(number_of_decimal_places=2) + self.sfmt = Format(number_of_decimal_places=1) + self.unitscale = 1 + self.startx = cm.starting_position.x + self.starty = cm.starting_position.y + self.startz = cm.starting_position.z + self.tcx = cm.mtc_position.x + self.tcy = cm.mtc_position.y + self.tcz = cm.mtc_position.z + self.endx = cm.ending_position.x + self.endy = cm.ending_position.y + self.endz = cm.ending_position.z + self.metric_flag = False + self.absolute_flag = True + + ############################################################################ + # Codes + + def SPACE(self): return('') + def COMMENT(self, comment): return('\' %s' % comment) + def TOOL(self): return('T%i' + self.SPACE() + 'M06') + + ############################################################################ + # Internals + + def write_feedrate(self): + self.f.write(self) + + def write_spindle(self): + self.s.write(self) + + ############################################################################ + # Programs + + def program_begin(self, id, name=''): + self.writem([self.SPACE(), self.COMMENT(name)]) + self.write('\n\n') + self.write('IF %(25)=1 THEN GOTO UNIT_ERROR\n') + self.write('\n') + + def program_end(self): + self.write('C7\n') + self.write('JZ,' + self.fmt.string(self.endz) + '\n') + self.write('J2,' + self.fmt.string(self.endx) + ',' + self.fmt.string(self.endy) + '\n') + self.write('END\n') + self.write('\'\n') + self.write('UNIT_ERROR:\n') + self.write('C#,91\n') + self.write('END\n') + + def flush_nc(self): + return + + ############################################################################ + # Subprograms + + ############################################################################ + # Settings + + def imperial(self): + self.fmt.number_of_decimal_places = 4 + self.unitscale = 0.0254 + self.metric_flag = False + + def metric(self): + self.metric_flag = True + self.unitscale = 1 + self.fmt.number_of_decimal_places = 3 + + def absolute(self): + self.absolute_flag = True + self.write('SA\n') + + def incremental(self): + self.absolute_flag = False + self.write('SR\n') + + def set_plane(self, plane): + return + + ############################################################################ + # new graphics origin- make a new coordinate system and snap it onto the geometry + # the toolpath generated should be translated + + ############################################################################ + # Tools + + def tool_change(self, id): + self.write('C7\n') + self.write('&Tool=' + self.fmt.string(id) + '\n') + self.write('JZ,' + (self.fmt.string(self.tcz)) + '\n') + self.write('J2,' + (self.fmt.string(self.tcx)) + ',' + (self.fmt.string(self.tcy)) + '\n') + self.write('\' Use tool ' + self.fmt.string(id) + '\n') + self.write('PAUSE\n') + self.write('C2\n') + self.t = id + self.write('C6\n') + self.write('PAUSE 2\n') + + ############################################################################ + # Datums + + ############################################################################ + ## Rates + Modes + + def feedrate(self, f): + if (self.metric_flag == True): + self.write('MS,' + (self.sfmt.string(f)) + ',' + (self.sfmt.string(f)) + '\n') + else: + self.write('MS,' + (self.sfmt.string(f*self.unitscale)) + + ',' + (self.sfmt.string(f*self.unitscale)) + '\n') + + def spindle(self, s, clockwise): + self.write('TR,' + self.fmt.string(s) + '\n') + + ############################################################################ + # Moves + + def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): + # different commands for X only, or Y only, or Z only, or (X and Y), or (X, Y, and Z) + if (x != None and y != None and z != None): + self.write('J3,' + (self.fmt.string(x * self.unitscale))) + self.write(',' + (self.fmt.string(y * self.unitscale))) + self.write(',' + (self.fmt.string(z * self.unitscale))) + self.write('\n') + elif (x != None and y != None and z == None): + self.write('J2,' + (self.fmt.string(x * self.unitscale))) + self.write(',' + (self.fmt.string(y * self.unitscale))) + self.write('\n') + elif (x != None): + self.write('JX,' + (self.fmt.string(x * self.unitscale)) + '\n') + elif (y != None): + self.write('JY,' + (self.fmt.string(y * self.unitscale)) + '\n') + elif (z != None): + self.write('JZ,' + (self.fmt.string(z * self.unitscale)) + '\n') + + def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): + if (x != None and y != None and z != None): + self.write('M3,' + (self.fmt.string(x * self.unitscale))) + self.write(',' + (self.fmt.string(y * self.unitscale))) + self.write(',' + (self.fmt.string(z * self.unitscale))) + self.write('\n') + elif (x != None and y != None and z == None): + self.write('M2,' + (self.fmt.string(x * self.unitscale))) + self.write(',' + (self.fmt.string(y * self.unitscale))) + self.write('\n') + elif (x != None): + self.write('MX,' + (self.fmt.string(x * self.unitscale)) + '\n') + elif (y != None): + self.write('MY,' + (self.fmt.string(y * self.unitscale)) + '\n') + elif (z != None): + self.write('MZ,' + (self.fmt.string(z * self.unitscale)) + '\n') + + def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): + if (r != None): + self.write('CG,' + self.fmt.string(r * self.unitscale * 2)) + self.write(',' + self.fmt.string(x * self.unitscale)) + self.write(',' + self.fmt.string(y * self.unitscale)) + self.write(', ,') + self.write(',T,1') + self.write('\n') + else: + self.write('CG, ') + self.write(',' + self.fmt.string(x * self.unitscale)) + self.write(',' + self.fmt.string(y * self.unitscale)) + self.write(',' + self.fmt.string(i * self.unitscale)) + self.write(',' + self.fmt.string(j * self.unitscale)) + self.write(',T,1') + self.write('\n') + return + + ############################################################################ + # CRC + + ############################################################################ + # Cycles + + ############################################################################ + # Misc + + def comment(self, text): + self.write((self.COMMENT(text) + '\n')) + + def variable(self, id): + return (self.VARIABLE() % id) + + def variable_set(self, id, value): + self.write('&' + (self.VARIABLE() % id) + self.SPACE() + + (self.VARIABLE_SET() % value) + '\n') ################################################################################ diff --git a/scripts/addons/cam/nc/siegkx1.py b/scripts/addons/cam/nc/siegkx1.py index 7aa272cdf..5fc50586c 100644 --- a/scripts/addons/cam/nc/siegkx1.py +++ b/scripts/addons/cam/nc/siegkx1.py @@ -11,6 +11,8 @@ import math ################################################################################ + + class Creator(iso_modal.Creator): def __init__(self): diff --git a/scripts/addons/cam/nc/siegkx1_read.py b/scripts/addons/cam/nc/siegkx1_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/siegkx1_read.py +++ b/scripts/addons/cam/nc/siegkx1_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/tnc151.py b/scripts/addons/cam/nc/tnc151.py index 3975e5f45..4dd5cce52 100644 --- a/scripts/addons/cam/nc/tnc151.py +++ b/scripts/addons/cam/nc/tnc151.py @@ -9,6 +9,8 @@ import math ################################################################################ + + class Creator(iso_modal.Creator): def __init__(self): @@ -21,12 +23,12 @@ def __init__(self): self.waiting_t = None self.waiting_for_program_begin = False - ######## Codes + # Codes def SPACE(self): return(' ') def TOOL(self): return('T%i') - ######## Overridden functions + # Overridden functions def write_blocknum(self): self.write(self.BLOCK() % self.n) @@ -50,8 +52,8 @@ def metric(self): self.write(' G71\n') self.fmt.number_of_decimal_places = 3 - # no tool definition lines wanted + def tool_defn(self, id, name='', params=None): pass @@ -87,4 +89,5 @@ def workplane(self, id): pass ################################################################################ + nc.creator = Creator() diff --git a/scripts/addons/cam/nc/tnc151_read.py b/scripts/addons/cam/nc/tnc151_read.py index 075c24e09..2ca0b6f50 100644 --- a/scripts/addons/cam/nc/tnc151_read.py +++ b/scripts/addons/cam/nc/tnc151_read.py @@ -2,6 +2,8 @@ import sys # just use the iso reader + + class Parser(iso.Parser): def __init__(self, writer): iso.Parser.__init__(self, writer) diff --git a/scripts/addons/cam/nc/winpc.py b/scripts/addons/cam/nc/winpc.py index 82a47214b..e92b3de95 100644 --- a/scripts/addons/cam/nc/winpc.py +++ b/scripts/addons/cam/nc/winpc.py @@ -6,6 +6,7 @@ now = datetime.datetime.now() + class Creator(iso_modal.Creator): def __init__(self): iso_modal.Creator.__init__(self) @@ -16,21 +17,25 @@ def __init__(self): def SPACE_STR(self): return ' ' - def TOOL(self): return('T%i' + self.SPACE() ) + def TOOL(self): return('T%i' + self.SPACE()) ############################################################################ -## Internal +# Internal + + def write_spindle(self): return '' ############################################################################ -## Programs +# Programs def program_begin(self, id, comment): if (self.useCrc == False): - self.write( ('(Created with win-pc post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) + self.write(('(Created with win-pc post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) else: - self.write( ('(Created with win-pc Cutter Radius Compensation post processor ' + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n') ) + self.write(('(Created with win-pc Cutter Radius Compensation post processor ' + + str(now.strftime("%Y/%m/%d %H:%M")) + ')' + '\n')) #self.rapid( x=0.0, y=0.0, z=30.0 ) def program_end(self): @@ -38,7 +43,7 @@ def program_end(self): #self.rapid( x=0.0, y=0.0, z=30.0 ) ############################################################################ -## Settings +# Settings def tool_defn(self, id, name='', params=None): #self.write('G43 \n') @@ -55,18 +60,22 @@ def comment(self, text): # These are selected by values from 1 to 9 inclusive. def workplane(self, id): if ((id >= 1) and (id <= 6)): - self.write( (self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + '\t (Select Relative Coordinate System)\n') + self.write((self.WORKPLANE() % (id + self.WORKPLANE_BASE())) + + '\t (Select Relative Coordinate System)\n') if ((id >= 7) and (id <= 9)): - self.write( ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % (id - 6))) + '\t (Select Relative Coordinate System)\n') + self.write(((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + ('.%i' % + (id - 6))) + '\t (Select Relative Coordinate System)\n') ############################################################################ -## Moves +# Moves ############################################################################ ## Rates + Modes -## All feedrates are given in mm/s +# All feedrates are given in mm/s + + def feedrate(self, f): self.f.set(f/60) self.fhv = False @@ -86,10 +95,12 @@ def calc_feedrate_hv(self, h, v): ############################################################################ -## Probe routines - def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None ): +# Probe routines + + + def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None): if (xml_file_name != None): - self.comment('Generate an XML document describing the probed coordinates found'); + self.comment('Generate an XML document describing the probed coordinates found') self.write_blocknum() self.write('(LOGOPEN,') self.write(xml_file_name) @@ -261,7 +272,7 @@ def report_probe_results(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=N self.write_blocknum() self.write('(LOGCLOSE)\n') - def open_log_file(self, xml_file_name=None ): + def open_log_file(self, xml_file_name=None): self.write_blocknum() self.write('(LOGOPEN,') self.write(xml_file_name) @@ -298,8 +309,9 @@ def log_coordinate(self, x=None, y=None, z=None): self.write_blocknum() self.write('(LOG,)\n') - def log_message(self, message=None ): + def log_message(self, message=None): self.write_blocknum() self.write('(LOG,' + message + ')\n') + nc.creator = Creator() From 2c1e4e962fdb17496ce03f0c09e6e9f0b809f0a1 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 14:53:43 -0400 Subject: [PATCH 004/100] Property format, version update Changed the formatting of Properties (e.g. bpy.props.StringProperty) throughout: Before: ``` use_position_definitions: bpy.props.BoolProperty(name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False) ``` After: ``` use_position_definitions: BoolProperty( name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False, ) ``` Added trailing commas to retain formatting if another autoformatter (e.g.: black) is implemented. Removed bpy.props. prefix from Properties that still had it, and added explicit imports when required in ui_panels module. --- scripts/addons/cam/__init__.py | 2031 +++++++++++++----- scripts/addons/cam/autoupdate.py | 15 +- scripts/addons/cam/basrelief.py | 296 ++- scripts/addons/cam/curvecamcreate.py | 992 +++++++-- scripts/addons/cam/curvecamequation.py | 375 +++- scripts/addons/cam/curvecamtools.py | 310 ++- scripts/addons/cam/ops.py | 56 +- scripts/addons/cam/ui_panels/info.py | 21 +- scripts/addons/cam/ui_panels/interface.py | 7 +- scripts/addons/cam/ui_panels/material.py | 61 +- scripts/addons/cam/ui_panels/movement.py | 288 ++- scripts/addons/cam/ui_panels/optimisation.py | 109 +- 12 files changed, 3329 insertions(+), 1232 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index a8c172b17..ea0f6f7fd 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -62,7 +62,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 5), + "version": (1, 0, 6), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", @@ -136,44 +136,49 @@ class CamAddonPreferences(AddonPreferences): default=False, ) - update_source: bpy.props.StringProperty( + update_source: StringProperty( name="Source of updates for the addon", - description="This can be either a github repo link in which case it will download the latest release on there, " - "or an api link like https://api.github.com/repos//blendercam/commits to get from a github repository", + description="This can be either a github repo link in which case " + "it will download the latest release on there, " + "or an api link like " + "https://api.github.com/repos//blendercam/commits" + " to get from a github repository", default="https://github.com/pppalain/blendercam", ) last_update_check: IntProperty( name="Last update time", - default=0 + default=0, ) last_commit_hash: StringProperty( name="Hash of last commit from updater", - default="" + default="", ) just_updated: BoolProperty( name="Set to true on update or initial install", - default=True + default=True, ) new_version_available: StringProperty( name="Set to new version name if one is found", - default="" + default="", ) - default_interface_level: bpy.props.EnumProperty( + default_interface_level: EnumProperty( name="Interface level in new file", description="Choose visible options", - items=[('0', "Basic", "Only show essential options"), - ('1', "Advanced", "Show advanced options"), - ('2', "Complete", "Show all options"), - ('3', "Experimental", "Show experimental options")], + items=[ + ('0', "Basic", "Only show essential options"), + ('1', "Advanced", "Show advanced options"), + ('2', "Complete", "Show all options"), + ('3', "Experimental", "Show experimental options") + ], default='3', ) - default_machine_preset: bpy.props.StringProperty( + default_machine_preset: StringProperty( name="Machine preset in new file", description="So that machine preset choice persists between files", default='', @@ -181,7 +186,8 @@ class CamAddonPreferences(AddonPreferences): def draw(self, context): layout = self.layout - layout.label(text="Use experimental features when you want to help development of Blender CAM:") + layout.label( + text="Use experimental features when you want to help development of Blender CAM:") layout.prop(self, "experimental") layout.prop(self, "update_source") layout.label(text="Choose a preset update source") @@ -205,74 +211,161 @@ def draw(self, context): class machineSettings(bpy.types.PropertyGroup): """stores all data for machines""" - # name = bpy.props.StringProperty(name="Machine Name", default="Machine") - post_processor: EnumProperty(name='Post processor', - items=(('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), - ('MACH3', 'Mach3', 'default mach3'), - ('EMC', 'LinuxCNC - EMC2', - 'Linux based CNC control software - formally EMC2'), - ('FADAL', 'Fadal', 'Fadal VMC'), - ('GRBL', 'grbl', - 'optimized gcode for grbl firmware on Arduino with cnc shield'), - ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), - ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), - ('TNC151', 'Heidenhain TNC151', - 'Post Processor for the Heidenhain TNC151 machine'), - ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), - ('HM50', 'Hafco HM-50', 'Hafco HM-50'), - ('CENTROID', 'Centroid M40', 'Centroid M40'), - ('ANILAM', 'Anilam Crusader M', 'Anilam Crusader M'), - ('GRAVOS', 'Gravos', 'Gravos'), - ('WIN-PC', 'WinPC-NC', 'German CNC by Burkhard Lewetz'), - ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), - ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o')), - description='Post processor', - default='MACH3') + # name = StringProperty(name="Machine Name", default="Machine") + post_processor: EnumProperty( + name='Post processor', + items=( + ('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), + ('MACH3', 'Mach3', 'default mach3'), + ('EMC', 'LinuxCNC - EMC2', + 'Linux based CNC control software - formally EMC2'), + ('FADAL', 'Fadal', 'Fadal VMC'), + ('GRBL', 'grbl', + 'optimized gcode for grbl firmware on Arduino with cnc shield'), + ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), + ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), + ('TNC151', 'Heidenhain TNC151', + 'Post Processor for the Heidenhain TNC151 machine'), + ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), + ('HM50', 'Hafco HM-50', 'Hafco HM-50'), + ('CENTROID', 'Centroid M40', 'Centroid M40'), + ('ANILAM', 'Anilam Crusader M', 'Anilam Crusader M'), + ('GRAVOS', 'Gravos', 'Gravos'), + ('WIN-PC', 'WinPC-NC', 'German CNC by Burkhard Lewetz'), + ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), + ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o') + ), + description='Post processor', + default='MACH3', + ) # units = EnumProperty(name='Units', items = (('IMPERIAL', '')) # position definitions: - use_position_definitions: bpy.props.BoolProperty(name="Use position definitions", - description="Define own positions for op start, " - "toolchange, ending position", - default=False) - starting_position: bpy.props.FloatVectorProperty(name='Start position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", update=updateMachine) - mtc_position: bpy.props.FloatVectorProperty(name='MTC position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", update=updateMachine) - ending_position: bpy.props.FloatVectorProperty(name='End position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", update=updateMachine) - - working_area: bpy.props.FloatVectorProperty(name='Work Area', default=(0.500, 0.500, 0.100), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", update=updateMachine) - feedrate_min: bpy.props.FloatProperty(name="Feedrate minimum /min", default=0.0, min=0.00001, max=320000, - precision=cam.constants.PRECISION, unit='LENGTH') - feedrate_max: bpy.props.FloatProperty(name="Feedrate maximum /min", default=2, min=0.00001, max=320000, - precision=cam.constants.PRECISION, unit='LENGTH') - feedrate_default: bpy.props.FloatProperty(name="Feedrate default /min", default=1.5, min=0.00001, max=320000, - precision=cam.constants.PRECISION, unit='LENGTH') - hourly_rate: bpy.props.FloatProperty(name="Price per hour", default=100, min=0.005, precision=2) + use_position_definitions: BoolProperty( + name="Use position definitions", + description="Define own positions for op start, " + "toolchange, ending position", + default=False, + ) + starting_position: FloatVectorProperty( + name='Start position', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + mtc_position: FloatVectorProperty( + name='MTC position', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + ending_position: FloatVectorProperty( + name='End position', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + + working_area: FloatVectorProperty( + name='Work Area', + default=(0.500, 0.500, 0.100), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + feedrate_min: FloatProperty( + name="Feedrate minimum /min", + default=0.0, + min=0.00001, + max=320000, + precision=cam.constants.PRECISION, + unit='LENGTH', + ) + feedrate_max: FloatProperty( + name="Feedrate maximum /min", + default=2, + min=0.00001, + max=320000, + precision=cam.constants.PRECISION, + unit='LENGTH', + ) + feedrate_default: FloatProperty( + name="Feedrate default /min", + default=1.5, + min=0.00001, + max=320000, + precision=cam.constants.PRECISION, + unit='LENGTH', + ) + hourly_rate: FloatProperty( + name="Price per hour", + default=100, + min=0.005, + precision=2, + ) # UNSUPPORTED: - spindle_min: bpy.props.FloatProperty(name="Spindle speed minimum RPM", default=5000, min=0.00001, max=320000, - precision=1) - spindle_max: bpy.props.FloatProperty(name="Spindle speed maximum RPM", default=30000, min=0.00001, max=320000, - precision=1) - spindle_default: bpy.props.FloatProperty(name="Spindle speed default RPM", default=15000, min=0.00001, max=320000, - precision=1) - spindle_start_time: bpy.props.FloatProperty(name="Spindle start delay seconds", - description='Wait for the spindle to start spinning before starting ' - 'the feeds , in seconds', - default=0, min=0.0000, max=320000, precision=1) - - axis4: bpy.props.BoolProperty(name="#4th axis", description="Machine has 4th axis", default=0) - axis5: bpy.props.BoolProperty(name="#5th axis", description="Machine has 5th axis", default=0) - - eval_splitting: bpy.props.BoolProperty(name="Split files", - description="split gcode file with large number of operations", - default=True) # split large files - split_limit: IntProperty(name="Operations per file", - description="Split files with larger number of operations than this", min=1000, - max=20000000, default=800000) + spindle_min: FloatProperty( + name="Spindle speed minimum RPM", + default=5000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_max: FloatProperty( + name="Spindle speed maximum RPM", + default=30000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_default: FloatProperty( + name="Spindle speed default RPM", + default=15000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_start_time: FloatProperty( + name="Spindle start delay seconds", + description='Wait for the spindle to start spinning before starting ' + 'the feeds , in seconds', + default=0, + min=0.0000, + max=320000, + precision=1, + ) + + axis4: BoolProperty( + name="#4th axis", + description="Machine has 4th axis", + default=0, + ) + axis5: BoolProperty( + name="#5th axis", + description="Machine has 5th axis", + default=0, + ) + + eval_splitting: BoolProperty( + name="Split files", + description="split gcode file with large number of operations", + default=True, + ) # split large files + split_limit: IntProperty( + name="Operations per file", + description="Split files with larger number of operations than this", + min=1000, + max=20000000, + default=800000, + ) # rotary_axis1 = EnumProperty(name='Axis 1', # items=( @@ -282,80 +375,179 @@ class machineSettings(bpy.types.PropertyGroup): # description='Number 1 rotational axis', # default='X', update = updateOffsetImage) - collet_size: bpy.props.FloatProperty(name="#Collet size", description="Collet size for collision detection", - default=33, min=0.00001, max=320000, precision=cam.constants.PRECISION, unit="LENGTH") - # exporter_start = bpy.props.StringProperty(name="exporter start", default="%") + collet_size: FloatProperty( + name="#Collet size", + description="Collet size for collision detection", + default=33, + min=0.00001, + max=320000, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + # exporter_start = StringProperty(name="exporter start", default="%") # post processor options - output_block_numbers: BoolProperty(name="output block numbers", - description="output block numbers ie N10 at start of line", default=False) + output_block_numbers: BoolProperty( + name="output block numbers", + description="output block numbers ie N10 at start of line", + default=False, + ) - start_block_number: IntProperty(name="start block number", description="the starting block number ie 10", - default=10) + start_block_number: IntProperty( + name="start block number", + description="the starting block number ie 10", + default=10, + ) - block_number_increment: IntProperty(name="block number increment", - description="how much the block number should increment for the next line", - default=10) + block_number_increment: IntProperty( + name="block number increment", + description="how much the block number should " + "increment for the next line", + default=10, + ) - output_tool_definitions: BoolProperty(name="output tool definitions", description="output tool definitions", - default=True) + output_tool_definitions: BoolProperty( + name="output tool definitions", + description="output tool definitions", + default=True, + ) - output_tool_change: BoolProperty(name="output tool change commands", - description="output tool change commands ie: Tn M06", default=True) + output_tool_change: BoolProperty( + name="output tool change commands", + description="output tool change commands ie: Tn M06", + default=True, + ) - output_g43_on_tool_change: BoolProperty(name="output G43 on tool change", - description="output G43 on tool change line", default=False) + output_g43_on_tool_change: BoolProperty( + name="output G43 on tool change", + description="output G43 on tool change line", + default=False, + ) class PackObjectsSettings(bpy.types.PropertyGroup): """stores all data for machines""" - sheet_fill_direction: EnumProperty(name='Fill direction', - items=(('X', 'X', 'Fills sheet in X axis direction'), - ('Y', 'Y', 'Fills sheet in Y axis direction')), - description='Fill direction of the packer algorithm', - default='Y') - sheet_x: FloatProperty(name="X size", description="Sheet size", min=0.001, max=10, default=0.5, - precision=cam.constants.PRECISION, unit="LENGTH") - sheet_y: FloatProperty(name="Y size", description="Sheet size", min=0.001, max=10, default=0.5, - precision=cam.constants.PRECISION, unit="LENGTH") - distance: FloatProperty(name="Minimum distance", - description="minimum distance between objects(should be at least cutter diameter!)", - min=0.001, max=10, default=0.01, precision=cam.constants.PRECISION, unit="LENGTH") - tolerance: FloatProperty(name="Placement Tolerance", - description="Tolerance for placement: smaller value slower placemant", - min=0.001, max=0.02, default=0.005, precision=cam.constants.PRECISION, unit="LENGTH") - rotate: bpy.props.BoolProperty( - name="enable rotation", description="Enable rotation of elements", default=True) - rotate_angle: FloatProperty(name="Placement Angle rotation step", - description="bigger rotation angle,faster placemant", default=0.19635 * 4, - min=math.pi/180, - max=math.pi, precision=5, - subtype="ANGLE", unit="ROTATION") + sheet_fill_direction: EnumProperty( + name='Fill direction', + items=( + ('X', 'X', 'Fills sheet in X axis direction'), + ('Y', 'Y', 'Fills sheet in Y axis direction') + ), + description='Fill direction of the packer algorithm', + default='Y', + ) + sheet_x: FloatProperty( + name="X size", + description="Sheet size", + min=0.001, + max=10, + default=0.5, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + sheet_y: FloatProperty( + name="Y size", + description="Sheet size", + min=0.001, + max=10, + default=0.5, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + distance: FloatProperty( + name="Minimum distance", + description="minimum distance between objects(should be " + "at least cutter diameter!)", + min=0.001, + max=10, + default=0.01, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + tolerance: FloatProperty( + name="Placement Tolerance", + description="Tolerance for placement: smaller value slower placemant", + min=0.001, + max=0.02, + default=0.005, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + rotate: BoolProperty( + name="enable rotation", + description="Enable rotation of elements", + default=True, + ) + rotate_angle: FloatProperty( + name="Placement Angle rotation step", + description="bigger rotation angle,faster placemant", + default=0.19635 * 4, + min=math.pi/180, + max=math.pi, + precision=5, + subtype="ANGLE", + unit="ROTATION", + ) class SliceObjectsSettings(bpy.types.PropertyGroup): """stores all data for machines""" - slice_distance: FloatProperty(name="Slicing distance", - description="slices distance in z, should be most often thickness of plywood sheet.", - min=0.001, max=10, default=0.005, precision=cam.constants.PRECISION, unit="LENGTH") - slice_above0: bpy.props.BoolProperty( - name="Slice above 0", description="only slice model above 0", default=False) - slice_3d: bpy.props.BoolProperty(name="3d slice", description="for 3d carving", default=False) - indexes: bpy.props.BoolProperty( - name="add indexes", description="adds index text of layer + index", default=True) + slice_distance: FloatProperty( + name="Slicing distance", + description="slices distance in z, should be most often " + "thickness of plywood sheet.", + min=0.001, + max=10, + default=0.005, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + slice_above0: BoolProperty( + name="Slice above 0", + description="only slice model above 0", + default=False, + ) + slice_3d: BoolProperty( + name="3d slice", + description="for 3d carving", + default=False, + ) + indexes: BoolProperty( + name="add indexes", + description="adds index text of layer + index", + default=True, + ) class import_settings(bpy.types.PropertyGroup): - split_layers: BoolProperty(name="Split Layers", description="Save every layer as single Objects in Collection", - default=False) - subdivide: BoolProperty(name="Subdivide", - description="Only Subdivide gcode segments that are bigger than 'Segment length' ", - default=False) - output: bpy.props.EnumProperty(name="output type", items=( - ('mesh', 'Mesh', 'Make a mesh output'), ('curve', 'Curve', 'Make curve output')), default='curve') - max_segment_size: FloatProperty(name="", description="Only Segments bigger then this value get subdivided", - default=0.001, min=0.0001, max=1.0, unit="LENGTH") + split_layers: BoolProperty( + name="Split Layers", + description="Save every layer as single Objects in Collection", + default=False, + ) + subdivide: BoolProperty( + name="Subdivide", + description="Only Subdivide gcode segments that are " + "bigger than 'Segment length' ", + default=False, + ) + output: EnumProperty( + name="output type", + items=( + ('mesh', 'Mesh', 'Make a mesh output'), + ('curve', 'Curve', 'Make curve output') + ), + default='curve', + ) + max_segment_size: FloatProperty( + name="", + description="Only Segments bigger then this value get subdivided", + default=0.001, + min=0.0001, + max=1.0, + unit="LENGTH", + ) def isValid(o, context): @@ -546,7 +738,8 @@ def getStrategyList(scene, context): ('OUTLINEFILL', 'Outline Fill', 'Detect outline and fill it with paths as pocket. Then sample these paths on the 3d surface'), ('CARVE', 'Project curve to surface', 'Engrave the curve path to surface'), - ('WATERLINE', 'Waterline - Roughing -below zero', 'Waterline paths - constant z below zero'), + ('WATERLINE', 'Waterline - Roughing -below zero', + 'Waterline paths - constant z below zero'), ('CURVE', 'Curve to Path', 'Curve object gets converted directly to path'), ('MEDIAL_AXIS', 'Medial axis', 'Medial axis, must be used with V or ball cutter, for engraving various width shapes with a single stroke ') @@ -562,411 +755,954 @@ def getStrategyList(scene, context): class camOperation(bpy.types.PropertyGroup): - material: bpy.props.PointerProperty(type=CAM_MATERIAL_Properties) - info: bpy.props.PointerProperty(type=CAM_INFO_Properties) - optimisation: bpy.props.PointerProperty(type=CAM_OPTIMISATION_Properties) - movement: bpy.props.PointerProperty(type=CAM_MOVEMENT_Properties) + material: PointerProperty( + type=CAM_MATERIAL_Properties + ) + info: PointerProperty( + type=CAM_INFO_Properties + ) + optimisation: PointerProperty( + type=CAM_OPTIMISATION_Properties + ) + movement: PointerProperty( + type=CAM_MOVEMENT_Properties + ) - name: bpy.props.StringProperty(name="Operation Name", default="Operation", update=updateRest) - filename: bpy.props.StringProperty(name="File name", default="Operation", update=updateRest) - auto_export: bpy.props.BoolProperty(name="Auto export", - description="export files immediately after path calculation", default=True) - remove_redundant_points: bpy.props.BoolProperty( + name: StringProperty( + name="Operation Name", + default="Operation", + update=updateRest, + ) + filename: StringProperty( + name="File name", + default="Operation", + update=updateRest, + ) + auto_export: BoolProperty( + name="Auto export", + description="export files immediately after path calculation", + default=True, + ) + remove_redundant_points: BoolProperty( name="Symplify Gcode", description="Remove redundant points sharing the same angle" " as the start vector", - default=False) - simplify_tol: bpy.props.IntProperty(name="Tolerance", description='lower number means more precise', default=50, - min=1, max=1000) - hide_all_others: bpy.props.BoolProperty( + default=False, + ) + simplify_tol: IntProperty( + name="Tolerance", + description='lower number means more precise', + default=50, + min=1, + max=1000, + ) + hide_all_others: BoolProperty( name="Hide all others", description="Hide all other tool pathes except toolpath" " assotiated with selected CAM operation", - default=False) - parent_path_to_object: bpy.props.BoolProperty( + default=False, + ) + parent_path_to_object: BoolProperty( name="Parent path to object", description="Parent generated CAM path to source object", - default=False) - object_name: bpy.props.StringProperty(name='Object', description='object handled by this operation', - update=updateOperationValid) - collection_name: bpy.props.StringProperty(name='Collection', - description='Object collection handled by this operation', - update=updateOperationValid) - curve_object: bpy.props.StringProperty(name='Curve source', - description='curve which will be sampled along the 3d object', - update=operationValid) - curve_object1: bpy.props.StringProperty(name='Curve target', - description='curve which will serve as attractor for the cutter when the cutter follows the curve', - update=operationValid) - source_image_name: bpy.props.StringProperty( - name='image_source', description='image source', update=operationValid) - geometry_source: EnumProperty(name='Source of data', - items=( - ('OBJECT', 'object', 'a'), ('COLLECTION', - 'Collection of objects', 'a'), - ('IMAGE', 'Image', 'a')), - description='Geometry source', - default='OBJECT', update=updateOperationValid) - cutter_type: EnumProperty(name='Cutter', - items=( - ('END', 'End', 'end - flat cutter'), - ('BALLNOSE', 'Ballnose', 'ballnose cutter'), - ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), - ('VCARVE', 'V-carve', 'v carve cutter'), - ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), - ('CYLCONE', 'Cylinder cone', 'Cylinder end with a Cone for Parallel - X'), - ('LASER', 'Laser', 'Laser cutter'), - ('PLASMA', 'Plasma', 'Plasma cutter'), - ('CUSTOM', 'Custom-EXPERIMENTAL', 'modelled cutter - not well tested yet.')), - description='Type of cutter used', - default='END', update=updateZbufferImage) - cutter_object_name: bpy.props.StringProperty(name='Cutter object', - description='object used as custom cutter for this operation', - update=updateZbufferImage) - - machine_axes: EnumProperty(name='Number of axes', - items=( - ('3', '3 axis', 'a'), - ('4', '#4 axis - EXPERIMENTAL', 'a'), - ('5', '#5 axis - EXPERIMENTAL', 'a')), - description='How many axes will be used for the operation', - default='3', update=updateStrategy) - strategy: EnumProperty(name='Strategy', - items=getStrategyList, - description='Strategy', - update=updateStrategy) - - strategy4axis: EnumProperty(name='4 axis Strategy', - items=( - ('PARALLELR', 'Parallel around 1st rotary axis', - 'Parallel lines around first rotary axis'), - ('PARALLEL', 'Parallel along 1st rotary axis', - 'Parallel lines along first rotary axis'), - ('HELIX', 'Helix around 1st rotary axis', 'Helix around rotary axis'), - ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just applied to the 4th axis'), - ('CROSS', 'Cross', 'Cross paths')), - description='#Strategy', - default='PARALLEL', - update=updateStrategy) - strategy5axis: EnumProperty(name='Strategy', - items=( - ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just rotated by 4+5th axes'), - ), - description='5 axis Strategy', - default='INDEXED', - update=updateStrategy) - - rotary_axis_1: EnumProperty(name='Rotary axis', - items=( - ('X', 'X', ''), - ('Y', 'Y', ''), - ('Z', 'Z', ''), - ), - description='Around which axis rotates the first rotary axis', - default='X', - update=updateStrategy) - rotary_axis_2: EnumProperty(name='Rotary axis 2', - items=( - ('X', 'X', ''), - ('Y', 'Y', ''), - ('Z', 'Z', ''), - ), - description='Around which axis rotates the second rotary axis', - default='Z', - update=updateStrategy) - - skin: FloatProperty(name="Skin", description="Material to leave when roughing ", min=0.0, max=1.0, default=0.0, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateOffsetImage) - inverse: bpy.props.BoolProperty(name="Inverse milling", description="Male to female model conversion", - default=False, update=updateOffsetImage) - array: bpy.props.BoolProperty(name="Use array", - description="Create a repetitive array for producing the same thing manytimes", - default=False, update=updateRest) - array_x_count: bpy.props.IntProperty(name="X count", description="X count", default=1, min=1, max=32000, - update=updateRest) - array_y_count: bpy.props.IntProperty(name="Y count", description="Y count", default=1, min=1, max=32000, - update=updateRest) - array_x_distance: FloatProperty(name="X distance", description="distance between operation origins", min=0.00001, - max=1.0, default=0.01, precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - array_y_distance: FloatProperty(name="Y distance", description="distance between operation origins", min=0.00001, - max=1.0, default=0.01, precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) + default=False, + ) + object_name: StringProperty( + name='Object', + description='object handled by this operation', + update=updateOperationValid, + ) + collection_name: StringProperty( + name='Collection', + description='Object collection handled by this operation', + update=updateOperationValid, + ) + curve_object: StringProperty( + name='Curve source', + description='curve which will be sampled along the 3d object', + update=operationValid, + ) + curve_object1: StringProperty( + name='Curve target', + description='curve which will serve as attractor for the ' + 'cutter when the cutter follows the curve', + update=operationValid, + ) + source_image_name: StringProperty( + name='image_source', + description='image source', + update=operationValid, + ) + geometry_source: EnumProperty( + name='Source of data', + items=( + ('OBJECT', 'object', 'a'), + ('COLLECTION', 'Collection of objects', 'a'), + ('IMAGE', 'Image', 'a') + ), + description='Geometry source', + default='OBJECT', + update=updateOperationValid, + ) + cutter_type: EnumProperty( + name='Cutter', + items=( + ('END', 'End', 'end - flat cutter'), + ('BALLNOSE', 'Ballnose', 'ballnose cutter'), + ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), + ('VCARVE', 'V-carve', 'v carve cutter'), + ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), + ('CYLCONE', 'Cylinder cone', + 'Cylinder end with a Cone for Parallel - X'), + ('LASER', 'Laser', 'Laser cutter'), + ('PLASMA', 'Plasma', 'Plasma cutter'), + ('CUSTOM', 'Custom-EXPERIMENTAL', + 'modelled cutter - not well tested yet.') + ), + description='Type of cutter used', + default='END', + update=updateZbufferImage, + ) + cutter_object_name: StringProperty( + name='Cutter object', + description='object used as custom cutter for this operation', + update=updateZbufferImage, + ) + + machine_axes: EnumProperty( + name='Number of axes', + items=( + ('3', '3 axis', 'a'), + ('4', '#4 axis - EXPERIMENTAL', 'a'), + ('5', '#5 axis - EXPERIMENTAL', 'a') + ), + description='How many axes will be used for the operation', + default='3', + update=updateStrategy, + ) + strategy: EnumProperty( + name='Strategy', + items=getStrategyList, + description='Strategy', + update=updateStrategy, + ) + + strategy4axis: EnumProperty( + name='4 axis Strategy', + items=( + ('PARALLELR', 'Parallel around 1st rotary axis', + 'Parallel lines around first rotary axis'), + ('PARALLEL', 'Parallel along 1st rotary axis', + 'Parallel lines along first rotary axis'), + ('HELIX', 'Helix around 1st rotary axis', + 'Helix around rotary axis'), + ('INDEXED', 'Indexed 3-axis', + 'all 3 axis strategies, just applied to the 4th axis'), + ('CROSS', 'Cross', 'Cross paths') + ), + description='#Strategy', + default='PARALLEL', + update=updateStrategy, + ) + strategy5axis: EnumProperty( + name='Strategy', + items=( + ('INDEXED', 'Indexed 3-axis', + 'all 3 axis strategies, just rotated by 4+5th axes'), + ), + description='5 axis Strategy', + default='INDEXED', + update=updateStrategy, + ) + + rotary_axis_1: EnumProperty( + name='Rotary axis', + items=( + ('X', 'X', ''), + ('Y', 'Y', ''), + ('Z', 'Z', ''), + ), + description='Around which axis rotates the first rotary axis', + default='X', + update=updateStrategy, + ) + rotary_axis_2: EnumProperty( + name='Rotary axis 2', + items=( + ('X', 'X', ''), + ('Y', 'Y', ''), + ('Z', 'Z', ''), + ), + description='Around which axis rotates the second rotary axis', + default='Z', + update=updateStrategy, + ) + + skin: FloatProperty( + name="Skin", + description="Material to leave when roughing ", + min=0.0, + max=1.0, + default=0.0, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + inverse: BoolProperty( + name="Inverse milling", + description="Male to female model conversion", + default=False, + update=updateOffsetImage, + ) + array: BoolProperty( + name="Use array", + description="Create a repetitive array for producing the " + "same thing many times", + default=False, + update=updateRest, + ) + array_x_count: IntProperty( + name="X count", + description="X count", + default=1, + min=1, + max=32000, + update=updateRest, + ) + array_y_count: IntProperty( + name="Y count", + description="Y count", + default=1, + min=1, + max=32000, + update=updateRest, + ) + array_x_distance: FloatProperty( + name="X distance", + description="distance between operation origins", + min=0.00001, + max=1.0, + default=0.01, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + array_y_distance: FloatProperty( + name="Y distance", + description="distance between operation origins", + min=0.00001, + max=1.0, + default=0.01, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # pocket options - pocket_option: EnumProperty(name='Start Position', items=( - ('INSIDE', 'Inside', 'a'), ('OUTSIDE', 'Outside', 'a')), - description='Pocket starting position', default='INSIDE', update=updateRest) - pocketToCurve: bpy.props.BoolProperty(name="Pocket to curve", - description="generates a curve instead of a path", - default=False, update=updateRest) + pocket_option: EnumProperty( + name='Start Position', + items=( + ('INSIDE', 'Inside', 'a'), + ('OUTSIDE', 'Outside', 'a') + ), + description='Pocket starting position', + default='INSIDE', + update=updateRest, + ) + pocketToCurve: BoolProperty( + name="Pocket to curve", + description="generates a curve instead of a path", + default=False, + update=updateRest, + ) # Cutout - cut_type: EnumProperty(name='Cut', - items=(('OUTSIDE', 'Outside', 'a'), ('INSIDE', - 'Inside', 'a'), ('ONLINE', 'On line', 'a')), - description='Type of cutter used', default='OUTSIDE', update=updateRest) - outlines_count: bpy.props.IntProperty(name="Outlines count", description="Outlines count", default=1, - min=1, max=32, update=updateCutout) - straight: bpy.props.BoolProperty(name="Overshoot Style", - description="Use overshoot cutout instead of conventional rounded", - default=False, update=updateRest) + cut_type: EnumProperty( + name='Cut', + items=( + ('OUTSIDE', 'Outside', 'a'), + ('INSIDE', 'Inside', 'a'), + ('ONLINE', 'On line', 'a') + ), + description='Type of cutter used', + default='OUTSIDE', + update=updateRest, + ) + outlines_count: IntProperty( + name="Outlines count", + description="Outlines count", + default=1, + min=1, + max=32, + update=updateCutout, + ) + straight: BoolProperty( + name="Overshoot Style", + description="Use overshoot cutout instead of conventional rounded", + default=False, + update=updateRest, + ) # cutter - cutter_id: IntProperty(name="Tool number", description="For machines which support tool change based on tool id", - min=0, max=10000, default=1, update=updateRest) - cutter_diameter: FloatProperty(name="Cutter diameter", description="Cutter diameter = 2x cutter radius", - min=0.000001, max=10, default=0.003, precision=cam.constants.PRECISION, unit="LENGTH", - update=updateOffsetImage) - cylcone_diameter: FloatProperty(name="Bottom Diameter", description="Bottom diameter", - min=0.000001, max=10, default=0.003, precision=cam.constants.PRECISION, unit="LENGTH", - update=updateOffsetImage) - cutter_length: FloatProperty(name="#Cutter length", description="#not supported#Cutter length", min=0.0, max=100.0, - default=25.0, precision=cam.constants.PRECISION, unit="LENGTH", update=updateOffsetImage) - cutter_flutes: IntProperty(name="Cutter flutes", description="Cutter flutes", min=1, max=20, default=2, - update=updateChipload) - cutter_tip_angle: FloatProperty(name="Cutter v-carve angle", description="Cutter v-carve angle", min=0.0, - max=180.0, default=60.0, precision=cam.constants.PRECISION, update=updateOffsetImage) - ball_radius: FloatProperty(name="Ball radius", description="Radius of", min=0.0, - max=0.035, default=0.001, unit="LENGTH", precision=cam.constants.PRECISION, update=updateOffsetImage) + cutter_id: IntProperty( + name="Tool number", + description="For machines which support tool change based on tool id", + min=0, + max=10000, + default=1, + update=updateRest, + ) + cutter_diameter: FloatProperty( + name="Cutter diameter", + description="Cutter diameter = 2x cutter radius", + min=0.000001, + max=10, + default=0.003, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cylcone_diameter: FloatProperty( + name="Bottom Diameter", + description="Bottom diameter", + min=0.000001, + max=10, + default=0.003, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cutter_length: FloatProperty( + name="#Cutter length", + description="#not supported#Cutter length", + min=0.0, + max=100.0, + default=25.0, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cutter_flutes: IntProperty( + name="Cutter flutes", + description="Cutter flutes", + min=1, + max=20, + default=2, + update=updateChipload, + ) + cutter_tip_angle: FloatProperty( + name="Cutter v-carve angle", + description="Cutter v-carve angle", + min=0.0, + max=180.0, + default=60.0, + precision=cam.constants.PRECISION, + update=updateOffsetImage, + ) + ball_radius: FloatProperty( + name="Ball radius", + description="Radius of", + min=0.0, + max=0.035, + default=0.001, + unit="LENGTH", + precision=cam.constants.PRECISION, + update=updateOffsetImage, + ) # ball_cone_flute: FloatProperty(name="BallCone Flute Length", description="length of flute", min=0.0, # max=0.1, default=0.017, unit="LENGTH", precision=cam.constants.PRECISION, update=updateOffsetImage) - bull_corner_radius: FloatProperty(name="Bull Corner Radius", description="Radius tool bit corner", min=0.0, - max=0.035, default=0.005, unit="LENGTH", precision=cam.constants.PRECISION, - update=updateOffsetImage) + bull_corner_radius: FloatProperty( + name="Bull Corner Radius", + description="Radius tool bit corner", + min=0.0, + max=0.035, + default=0.005, + unit="LENGTH", + precision=cam.constants.PRECISION, + update=updateOffsetImage, + ) cutter_description: StringProperty( - name="Tool Description", default="", update=updateOffsetImage) - - Laser_on: bpy.props.StringProperty(name="Laser ON string", default="M68 E0 Q100") - Laser_off: bpy.props.StringProperty(name="Laser OFF string", default="M68 E0 Q0") - Laser_cmd: bpy.props.StringProperty(name="Laser command", default="M68 E0 Q") - Laser_delay: bpy.props.FloatProperty(name="Laser ON Delay", - description="time after fast move to turn on laser and let machine stabilize", - default=0.2) - Plasma_on: bpy.props.StringProperty(name="Plasma ON string", default="M03") - Plasma_off: bpy.props.StringProperty(name="Plasma OFF string", default="M05") - Plasma_delay: bpy.props.FloatProperty(name="Plasma ON Delay", - description="time after fast move to turn on Plasma and let machine stabilize", - default=0.1) - Plasma_dwell: bpy.props.FloatProperty(name="Plasma dwell time", description="Time to dwell and warm up the torch", - default=0.0) + name="Tool Description", + default="", + update=updateOffsetImage, + ) + + Laser_on: StringProperty( + name="Laser ON string", + default="M68 E0 Q100", + ) + Laser_off: StringProperty( + name="Laser OFF string", + default="M68 E0 Q0", + ) + Laser_cmd: StringProperty( + name="Laser command", + default="M68 E0 Q", + ) + Laser_delay: FloatProperty( + name="Laser ON Delay", + description="time after fast move to turn on laser and " + "let machine stabilize", + default=0.2, + ) + Plasma_on: StringProperty( + name="Plasma ON string", + default="M03", + ) + Plasma_off: StringProperty( + name="Plasma OFF string", + default="M05", + ) + Plasma_delay: FloatProperty( + name="Plasma ON Delay", + description="time after fast move to turn on Plasma and " + "let machine stabilize", + default=0.1, + ) + Plasma_dwell: FloatProperty( + name="Plasma dwell time", + description="Time to dwell and warm up the torch", + default=0.0, + ) # steps - dist_between_paths: bpy.props.FloatProperty(name="Distance between toolpaths", default=0.001, min=0.00001, max=32, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - dist_along_paths: bpy.props.FloatProperty(name="Distance along toolpaths", default=0.0002, min=0.00001, max=32, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - parallel_angle: bpy.props.FloatProperty(name="Angle of paths", default=0, min=-360, max=360, precision=0, - subtype="ANGLE", unit="ROTATION", update=updateRest) - old_rotation_A: bpy.props.FloatProperty(name="A axis angle", - description="old value of Rotate A axis\nto specified angle", default=0, - min=-360, max=360, precision=0, subtype="ANGLE", unit="ROTATION", - update=updateRest) - - old_rotation_B: bpy.props.FloatProperty(name="A axis angle", - description="old value of Rotate A axis\nto specified angle", default=0, - min=-360, max=360, precision=0, subtype="ANGLE", unit="ROTATION", - update=updateRest) - - rotation_A: bpy.props.FloatProperty(name="A axis angle", description="Rotate A axis\nto specified angle", default=0, - min=-360, max=360, precision=0, - subtype="ANGLE", unit="ROTATION", update=updateRotation) - enable_A: bpy.props.BoolProperty(name="Enable A axis", description="Rotate A axis", default=False, - update=updateRotation) - A_along_x: bpy.props.BoolProperty( - name="A Along X ", description="A Parallel to X", default=True, update=updateRest) - - rotation_B: bpy.props.FloatProperty(name="B axis angle", description="Rotate B axis\nto specified angle", default=0, - min=-360, max=360, precision=0, - subtype="ANGLE", unit="ROTATION", update=updateRotation) - enable_B: bpy.props.BoolProperty(name="Enable B axis", description="Rotate B axis", default=False, - update=updateRotation) + dist_between_paths: FloatProperty( + name="Distance between toolpaths", + default=0.001, + min=0.00001, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + dist_along_paths: FloatProperty( + name="Distance along toolpaths", + default=0.0002, + min=0.00001, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + parallel_angle: FloatProperty( + name="Angle of paths", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + old_rotation_A: FloatProperty( + name="A axis angle", + description="old value of Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + old_rotation_B: FloatProperty( + name="A axis angle", + description="old value of Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + rotation_A: FloatProperty( + name="A axis angle", + description="Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRotation, + ) + enable_A: BoolProperty( + name="Enable A axis", + description="Rotate A axis", + default=False, + update=updateRotation, + ) + A_along_x: BoolProperty( + name="A Along X ", + description="A Parallel to X", + default=True, + update=updateRest, + ) + + rotation_B: FloatProperty( + name="B axis angle", + description="Rotate B axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRotation, + ) + enable_B: BoolProperty( + name="Enable B axis", + description="Rotate B axis", + default=False, + update=updateRotation, + ) # carve only - carve_depth: bpy.props.FloatProperty(name="Carve depth", default=0.001, min=-.100, max=32, precision=cam.constants.PRECISION, - unit="LENGTH", update=updateRest) + carve_depth: FloatProperty( + name="Carve depth", + default=0.001, + min=-.100, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # drill only - drill_type: EnumProperty(name='Holes on', items=( - ('MIDDLE_SYMETRIC', 'Middle of symetric curves', - 'a'), ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), - ('ALL_POINTS', 'All points in curve', 'a')), description='Strategy to detect holes to drill', - default='MIDDLE_SYMETRIC', update=updateRest) + drill_type: EnumProperty( + name='Holes on', + items=( + ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), + ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), + ('ALL_POINTS', 'All points in curve', 'a') + ), + description='Strategy to detect holes to drill', + default='MIDDLE_SYMETRIC', + update=updateRest, + ) # waterline only - slice_detail: bpy.props.FloatProperty(name="Distance betwen slices", default=0.001, min=0.00001, max=32, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - waterline_fill: bpy.props.BoolProperty(name="Fill areas between slices", - description="Fill areas between slices in waterline mode", default=True, - update=updateRest) - waterline_project: bpy.props.BoolProperty(name="Project paths - not recomended", - description="Project paths in areas between slices", default=True, - update=updateRest) + slice_detail: FloatProperty( + name="Distance betwen slices", + default=0.001, + min=0.00001, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + waterline_fill: BoolProperty( + name="Fill areas between slices", + description="Fill areas between slices in waterline mode", + default=True, + update=updateRest, + ) + waterline_project: BoolProperty( + name="Project paths - not recomended", + description="Project paths in areas between slices", + default=True, + update=updateRest, + ) # movement and ramps - use_layers: bpy.props.BoolProperty(name="Use Layers", description="Use layers for roughing", default=True, - update=updateRest) - stepdown: bpy.props.FloatProperty(name="", description="Layer height", default=0.01, min=0.00001, max=32, precision=cam.constants.PRECISION, - unit="LENGTH", update=updateRest) - lead_in: bpy.props.FloatProperty(name="Lead in radius", - description="Lead out radius for torch or laser to turn off", - min=0.00, max=1, default=0.0, precision=cam.constants.PRECISION, unit="LENGTH") - lead_out: bpy.props.FloatProperty(name="Lead out radius", - description="Lead out radius for torch or laser to turn off", - min=0.00, max=1, default=0.0, precision=cam.constants.PRECISION, unit="LENGTH") - profile_start: bpy.props.IntProperty(name="Start point", description="Start point offset", min=0, default=0, - update=updateRest) - - # helix_angle: bpy.props.FloatProperty(name="Helix ramp angle", default=3*math.pi/180, min=0.00001, max=math.pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) - - minz: bpy.props.FloatProperty(name="Operation depth end", - default=-0.01, min=-3, max=3, precision=cam.constants.PRECISION, - unit="LENGTH", - update=updateRest) - - minz_from: bpy.props.EnumProperty(name='Set max depth from', - description='Set maximum operation depth', - items=( - ('OBJECT', 'Object', 'Set max operation depth from Object'), - ('MATERIAL', 'Material', 'Set max operation depth from Material'), - ('CUSTOM', 'Custom', 'Custom max depth'), - ), - default='OBJECT', - update=updateRest - ) - - start_type: bpy.props.EnumProperty(name='Start type', - items=( - ('ZLEVEL', 'Z level', 'Starts on a given Z level'), - ('OPERATIONRESULT', 'Rest milling', - 'For rest milling, operations have to be put in chain for this to work well.'), - ), - description='Starting depth', - default='ZLEVEL', - update=updateStrategy) - - maxz: bpy.props.FloatProperty(name="Operation depth start", description='operation starting depth', default=0, - min=-3, max=10, precision=cam.constants.PRECISION, unit="LENGTH", - update=updateRest) # EXPERIMENTAL - - first_down: bpy.props.BoolProperty(name="First down", - description="First go down on a contour, then go to the next one", - default=False, update=cam.utils.update_operation) + use_layers: BoolProperty( + name="Use Layers", + description="Use layers for roughing", + default=True, + update=updateRest, + ) + stepdown: FloatProperty( + name="", + description="Layer height", + default=0.01, + min=0.00001, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + lead_in: FloatProperty( + name="Lead in radius", + description="Lead out radius for torch or laser to turn off", + min=0.00, + max=1, + default=0.0, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + lead_out: FloatProperty( + name="Lead out radius", + description="Lead out radius for torch or laser to turn off", + min=0.00, + max=1, + default=0.0, + precision=cam.constants.PRECISION, + unit="LENGTH", + ) + profile_start: IntProperty( + name="Start point", + description="Start point offset", + min=0, + default=0, + update=updateRest, + ) + + # helix_angle: FloatProperty(name="Helix ramp angle", default=3*math.pi/180, min=0.00001, max=math.pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) + + minz: FloatProperty( + name="Operation depth end", + default=-0.01, + min=-3, + max=3, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + minz_from: EnumProperty( + name='Set max depth from', + description='Set maximum operation depth', + items=( + ('OBJECT', 'Object', 'Set max operation depth from Object'), + ('MATERIAL', 'Material', 'Set max operation depth from Material'), + ('CUSTOM', 'Custom', 'Custom max depth'), + ), + default='OBJECT', + update=updateRest, + ) + + start_type: EnumProperty( + name='Start type', + items=( + ('ZLEVEL', 'Z level', 'Starts on a given Z level'), + ('OPERATIONRESULT', 'Rest milling', + 'For rest milling, operations have to be ' + 'put in chain for this to work well.'), + ), + description='Starting depth', + default='ZLEVEL', + update=updateStrategy, + ) + + maxz: FloatProperty( + name="Operation depth start", + description='operation starting depth', + default=0, + min=-3, + max=10, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # EXPERIMENTAL + + first_down: BoolProperty( + name="First down", + description="First go down on a contour, then go to the next one", + default=False, + update=cam.utils.update_operation, + ) ####################################################### # Image related #################################################### - source_image_scale_z: bpy.props.FloatProperty(name="Image source depth scale", default=0.01, min=-1, max=1, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateZbufferImage) - source_image_size_x: bpy.props.FloatProperty(name="Image source x size", default=0.1, min=-10, max=10, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateZbufferImage) - source_image_offset: bpy.props.FloatVectorProperty(name='Image offset', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", update=updateZbufferImage) - source_image_crop: bpy.props.BoolProperty(name="Crop source image", - description="Crop source image - the position of the sub-rectangle is relative to the whole image, so it can be used for e.g. finishing just a part of an image", - default=False, update=updateZbufferImage) - source_image_crop_start_x: bpy.props.FloatProperty(name='crop start x', default=0, min=0, max=100, - precision=cam.constants.PRECISION, subtype='PERCENTAGE', - update=updateZbufferImage) - source_image_crop_start_y: bpy.props.FloatProperty(name='crop start y', default=0, min=0, max=100, - precision=cam.constants.PRECISION, subtype='PERCENTAGE', - update=updateZbufferImage) - source_image_crop_end_x: bpy.props.FloatProperty(name='crop end x', default=100, min=0, max=100, - precision=cam.constants.PRECISION, subtype='PERCENTAGE', - update=updateZbufferImage) - source_image_crop_end_y: bpy.props.FloatProperty(name='crop end y', default=100, min=0, max=100, - precision=cam.constants.PRECISION, subtype='PERCENTAGE', - update=updateZbufferImage) + source_image_scale_z: FloatProperty( + name="Image source depth scale", + default=0.01, + min=-1, + max=1, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateZbufferImage, + ) + source_image_size_x: FloatProperty( + name="Image source x size", + default=0.1, + min=-10, + max=10, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateZbufferImage, + ) + source_image_offset: FloatVectorProperty( + name='Image offset', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=updateZbufferImage, + ) + + source_image_crop: BoolProperty( + name="Crop source image", + description="Crop source image - the position of the sub-rectangle " + "is relative to the whole image, so it can be used for e.g. " + "finishing just a part of an image", + default=False, + update=updateZbufferImage, + ) + source_image_crop_start_x: FloatProperty( + name='crop start x', + default=0, + min=0, + max=100, + precision=cam.constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_start_y: FloatProperty( + name='crop start y', + default=0, + min=0, + max=100, + precision=cam.constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_end_x: FloatProperty( + name='crop end x', + default=100, + min=0, + max=100, + precision=cam.constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_end_y: FloatProperty( + name='crop end y', + default=100, + min=0, + max=100, + precision=cam.constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) ######################################################### # Toolpath and area related ##################################################### - ambient_behaviour: EnumProperty(name='Ambient', items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), - description='handling ambient surfaces', default='ALL', update=updateZbufferImage) + ambient_behaviour: EnumProperty( + name='Ambient', + items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), + description='handling ambient surfaces', + default='ALL', + update=updateZbufferImage, + ) - ambient_radius: FloatProperty(name="Ambient radius", - description="Radius around the part which will be milled if ambient is set to Around", - min=0.0, max=100.0, default=0.01, precision=cam.constants.PRECISION, unit="LENGTH", - update=updateRest) + ambient_radius: FloatProperty( + name="Ambient radius", + description="Radius around the part which will be milled if " + "ambient is set to Around", + min=0.0, + max=100.0, + default=0.01, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # ambient_cutter = EnumProperty(name='Borders',items=(('EXTRAFORCUTTER', 'Extra for cutter', "Extra space for cutter is cut around the segment"),('ONBORDER', "Cutter on edge", "Cutter goes exactly on edge of ambient with it's middle") ,('INSIDE', "Inside segment", 'Cutter stays within segment') ),description='handling of ambient and cutter size',default='INSIDE') - use_limit_curve: bpy.props.BoolProperty(name="Use limit curve", description="A curve limits the operation area", - default=False, update=updateRest) - ambient_cutter_restrict: bpy.props.BoolProperty(name="Cutter stays in ambient limits", - description="Cutter doesn't get out from ambient limits otherwise goes on the border exactly", - default=True, - update=updateRest) # restricts cutter inside ambient only - limit_curve: bpy.props.StringProperty(name='Limit curve', - description='curve used to limit the area of the operation', - update=updateRest) + use_limit_curve: BoolProperty( + name="Use limit curve", + description="A curve limits the operation area", + default=False, + update=updateRest, + ) + ambient_cutter_restrict: BoolProperty( + name="Cutter stays in ambient limits", + description="Cutter doesn't get out from ambient limits otherwise " + "goes on the border exactly", + default=True, + update=updateRest, + ) # restricts cutter inside ambient only + limit_curve: StringProperty( + name='Limit curve', + description='curve used to limit the area of the operation', + update=updateRest, + ) # feeds - feedrate: FloatProperty(name="Feedrate", description="Feedrate in units per minute", min=0.00005, max=50.0, default=1.0, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateChipload) - plunge_feedrate: FloatProperty(name="Plunge speed ", description="% of feedrate", min=0.1, max=100.0, default=50.0, - precision=1, subtype='PERCENTAGE', update=updateRest) - plunge_angle: bpy.props.FloatProperty(name="Plunge angle", - description="What angle is allready considered to plunge", - default=math.pi / 6, min=0, max=math.pi * 0.5, precision=0, subtype="ANGLE", - unit="ROTATION", update=updateRest) - spindle_rpm: FloatProperty(name="Spindle rpm", description="Spindle speed ", min=0, max=60000, default=12000, - update=updateChipload) + feedrate: FloatProperty( + name="Feedrate", + description="Feedrate in units per minute", + min=0.00005, + max=50.0, + default=1.0, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateChipload, + ) + plunge_feedrate: FloatProperty( + name="Plunge speed ", + description="% of feedrate", + min=0.1, + max=100.0, + default=50.0, + precision=1, + subtype='PERCENTAGE', + update=updateRest, + ) + plunge_angle: FloatProperty( + name="Plunge angle", + description="What angle is allready considered to plunge", + default=math.pi / 6, + min=0, + max=math.pi * 0.5, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + spindle_rpm: FloatProperty( + name="Spindle rpm", + description="Spindle speed ", + min=0, + max=60000, + default=12000, + update=updateChipload, + ) # optimization and performance - do_simulation_feedrate: bpy.props.BoolProperty(name="Adjust feedrates with simulation EXPERIMENTAL", - description="Adjust feedrates with simulation", default=False, - update=updateRest) - - dont_merge: bpy.props.BoolProperty(name="Dont merge outlines when cutting", - description="this is usefull when you want to cut around everything", - default=False, update=updateRest) - - pencil_threshold: bpy.props.FloatProperty(name="Pencil threshold", default=0.00002, min=0.00000001, max=1, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - crazy_threshold1: bpy.props.FloatProperty(name="min engagement", default=0.02, min=0.00000001, max=100, - precision=cam.constants.PRECISION, update=updateRest) - crazy_threshold5: bpy.props.FloatProperty(name="optimal engagement", default=0.3, min=0.00000001, max=100, - precision=cam.constants.PRECISION, update=updateRest) - crazy_threshold2: bpy.props.FloatProperty(name="max engagement", default=0.5, min=0.00000001, max=100, - precision=cam.constants.PRECISION, update=updateRest) - crazy_threshold3: bpy.props.FloatProperty(name="max angle", default=2, min=0.00000001, max=100, - precision=cam.constants.PRECISION, update=updateRest) - crazy_threshold4: bpy.props.FloatProperty(name="test angle step", default=0.05, min=0.00000001, max=100, - precision=cam.constants.PRECISION, update=updateRest) + do_simulation_feedrate: BoolProperty( + name="Adjust feedrates with simulation EXPERIMENTAL", + description="Adjust feedrates with simulation", + default=False, + update=updateRest, + ) + + dont_merge: BoolProperty( + name="Dont merge outlines when cutting", + description="this is usefull when you want to cut around everything", + default=False, + update=updateRest, + ) + + pencil_threshold: FloatProperty( + name="Pencil threshold", + default=0.00002, + min=0.00000001, + max=1, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + crazy_threshold1: FloatProperty( + name="min engagement", + default=0.02, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + update=updateRest, + ) + crazy_threshold5: FloatProperty( + name="optimal engagement", + default=0.3, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + update=updateRest, + ) + crazy_threshold2: FloatProperty( + name="max engagement", + default=0.5, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + update=updateRest, + ) + crazy_threshold3: FloatProperty( + name="max angle", + default=2, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + update=updateRest, + ) + crazy_threshold4: FloatProperty( + name="test angle step", + default=0.05, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + update=updateRest, + ) # Add pocket operation to medial axis - add_pocket_for_medial: bpy.props.BoolProperty(name="Add pocket operation", - description="clean unremoved material after medial axis", - default=True, - update=updateRest) - - add_mesh_for_medial: bpy.props.BoolProperty(name="Add Medial mesh", - description="Medial operation returns mesh for editing and further processing", - default=False, - update=updateRest) + add_pocket_for_medial: BoolProperty( + name="Add pocket operation", + description="clean unremoved material after medial axis", + default=True, + update=updateRest, + ) + + add_mesh_for_medial: BoolProperty( + name="Add Medial mesh", + description="Medial operation returns mesh for editing and " + "further processing", + default=False, + update=updateRest, + ) #### - medial_axis_threshold: bpy.props.FloatProperty(name="Long vector threshold", default=0.001, min=0.00000001, - max=100, precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) - medial_axis_subdivision: bpy.props.FloatProperty(name="Fine subdivision", default=0.0002, min=0.00000001, max=100, - precision=cam.constants.PRECISION, unit="LENGTH", update=updateRest) + medial_axis_threshold: FloatProperty( + name="Long vector threshold", + default=0.001, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + medial_axis_subdivision: FloatProperty( + name="Fine subdivision", + default=0.0002, + min=0.00000001, + max=100, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # calculations # bridges - use_bridges: bpy.props.BoolProperty(name="Use bridges", description="use bridges in cutout", default=False, - update=updateBridges) - bridges_width: bpy.props.FloatProperty(name='width of bridges', default=0.002, unit='LENGTH', precision=cam.constants.PRECISION, - update=updateBridges) - bridges_height: bpy.props.FloatProperty(name='height of bridges', - description="Height from the bottom of the cutting operation", - default=0.0005, unit='LENGTH', precision=cam.constants.PRECISION, update=updateBridges) - bridges_collection_name: bpy.props.StringProperty(name='Bridges Collection', - description='Collection of curves used as bridges', - update=operationValid) - use_bridge_modifiers: BoolProperty(name="use bridge modifiers", - description="include bridge curve modifiers using render level when calculating operation, does not effect original bridge data", - default=True, update=updateBridges) + use_bridges: BoolProperty( + name="Use bridges", + description="use bridges in cutout", + default=False, + update=updateBridges, + ) + bridges_width: FloatProperty( + name='width of bridges', + default=0.002, + unit='LENGTH', + precision=cam.constants.PRECISION, + update=updateBridges, + ) + bridges_height: FloatProperty( + name='height of bridges', + description="Height from the bottom of the cutting operation", + default=0.0005, + unit='LENGTH', + precision=cam.constants.PRECISION, + update=updateBridges, + ) + bridges_collection_name: StringProperty( + name='Bridges Collection', + description='Collection of curves used as bridges', + update=operationValid, + ) + use_bridge_modifiers: BoolProperty( + name="use bridge modifiers", + description="include bridge curve modifiers using render level when " + "calculating operation, does not effect original bridge data", + default=True, + update=updateBridges, + ) # commented this - auto bridges will be generated, but not as a setting of the operation - # bridges_placement = bpy.props.EnumProperty(name='Bridge placement', + # bridges_placement = EnumProperty(name='Bridge placement', # items=( # ('AUTO','Automatic', 'Automatic bridges with a set distance'), # ('MANUAL','Manual', 'Manual placement of bridges'), @@ -975,12 +1711,16 @@ class camOperation(bpy.types.PropertyGroup): # default='AUTO', # update = updateStrategy) # - # bridges_per_curve = bpy.props.IntProperty(name="minimum bridges per curve", description="", default=4, min=1, max=512, update = updateBridges) - # bridges_max_distance = bpy.props.FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=cam.constants.PRECISION, update = updateBridges) - - use_modifiers: BoolProperty(name="use mesh modifiers", - description="include mesh modifiers using render level when calculating operation, does not effect original mesh", - default=True, update=operationValid) + # bridges_per_curve = IntProperty(name="minimum bridges per curve", description="", default=4, min=1, max=512, update = updateBridges) + # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=cam.constants.PRECISION, update = updateBridges) + + use_modifiers: BoolProperty( + name="use mesh modifiers", + description="include mesh modifiers using render level when " + "calculating operation, does not effect original mesh", + default=True, + update=operationValid, + ) # optimisation panel # material settings @@ -989,69 +1729,111 @@ class camOperation(bpy.types.PropertyGroup): ############################################################################## # MATERIAL SETTINGS - min: bpy.props.FloatVectorProperty( - name='Operation minimum', default=(0, 0, 0), unit='LENGTH', precision=cam.constants.PRECISION, - subtype="XYZ") - max: bpy.props.FloatVectorProperty(name='Operation maximum', default=(0, 0, 0), unit='LENGTH', precision=cam.constants.PRECISION, - subtype="XYZ") + min: FloatVectorProperty( + name='Operation minimum', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + ) + max: FloatVectorProperty( + name='Operation maximum', + default=(0, 0, 0), + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + ) # g-code options for operation - output_header: BoolProperty(name="output g-code header", - description="output user defined g-code command header at start of operation", - default=False) + output_header: BoolProperty( + name="output g-code header", + description="output user defined g-code command header" + " at start of operation", + default=False, + ) - gcode_header: StringProperty(name="g-code header", - description="g-code commands at start of operation. Use ; for line breaks", - default="G53 G0") + gcode_header: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="G53 G0", + ) - enable_dust: BoolProperty(name="Dust collector", - description="output user defined g-code command header at start of operation", - default=False) + enable_dust: BoolProperty( + name="Dust collector", + description="output user defined g-code command header" + " at start of operation", + default=False, + ) - gcode_start_dust_cmd: StringProperty(name="Start dust collector", - description="commands to start dust collection. Use ; for line breaks", - default="M100") + gcode_start_dust_cmd: StringProperty( + name="Start dust collector", + description="commands to start dust collection. Use ; for line breaks", + default="M100", + ) - gcode_stop_dust_cmd: StringProperty(name="Stop dust collector", - description="command to stop dust collection. Use ; for line breaks", - default="M101") + gcode_stop_dust_cmd: StringProperty( + name="Stop dust collector", + description="command to stop dust collection. Use ; for line breaks", + default="M101", + ) - enable_hold: BoolProperty(name="Hold down", - description="output hold down command at start of operation", - default=False) + enable_hold: BoolProperty( + name="Hold down", + description="output hold down command at start of operation", + default=False, + ) - gcode_start_hold_cmd: StringProperty(name="g-code header", - description="g-code commands at start of operation. Use ; for line breaks", - default="M102") + gcode_start_hold_cmd: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="M102", + ) - gcode_stop_hold_cmd: StringProperty(name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", - default="M103") + gcode_stop_hold_cmd: StringProperty( + name="g-code header", + description="g-code commands at end operation. Use ; for line breaks", + default="M103", + ) - enable_mist: BoolProperty(name="Mist", - description="Mist command at start of operation", - default=False) + enable_mist: BoolProperty( + name="Mist", + description="Mist command at start of operation", + default=False, + ) - gcode_start_mist_cmd: StringProperty(name="g-code header", - description="g-code commands at start of operation. Use ; for line breaks", - default="M104") + gcode_start_mist_cmd: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="M104", + ) - gcode_stop_mist_cmd: StringProperty(name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", - default="M105") + gcode_stop_mist_cmd: StringProperty( + name="g-code header", + description="g-code commands at end operation. Use ; for line breaks", + default="M105", + ) - output_trailer: BoolProperty(name="output g-code trailer", - description="output user defined g-code command trailer at end of operation", - default=False) + output_trailer: BoolProperty( + name="output g-code trailer", + description="output user defined g-code command trailer" + " at end of operation", + default=False, + ) - gcode_trailer: StringProperty(name="g-code trailer", - description="g-code commands at end of operation. Use ; for line breaks", - default="M02") + gcode_trailer: StringProperty( + name="g-code trailer", + description="g-code commands at end of operation." + " Use ; for line breaks", + default="M02", + ) # internal properties ########################################### - # testing = bpy.props.IntProperty(name="developer testing ", description="This is just for script authors for help in coding, keep 0", default=0, min=0, max=512) + # testing = IntProperty(name="developer testing ", description="This is just for script authors for help in coding, keep 0", default=0, min=0, max=512) offset_image = numpy.array([], dtype=float) zbuffer_image = numpy.array([], dtype=float) @@ -1060,54 +1842,116 @@ class camOperation(bpy.types.PropertyGroup): operation_limit = sgeometry.Polygon() borderwidth = 50 object = None - path_object_name: bpy.props.StringProperty(name='Path object', description='actual cnc path') + path_object_name: StringProperty( + name='Path object', + description='actual cnc path' + ) # update and tags and related - changed: bpy.props.BoolProperty(name="True if any of the operation settings has changed", - description="mark for update", default=False) - update_zbufferimage_tag: bpy.props.BoolProperty(name="mark zbuffer image for update", - description="mark for update", default=True) - update_offsetimage_tag: bpy.props.BoolProperty(name="mark offset image for update", description="mark for update", - default=True) - update_silhouete_tag: bpy.props.BoolProperty(name="mark silhouete image for update", description="mark for update", - default=True) - update_ambient_tag: bpy.props.BoolProperty(name="mark ambient polygon for update", description="mark for update", - default=True) - update_bullet_collision_tag: bpy.props.BoolProperty(name="mark bullet collisionworld for update", - description="mark for update", default=True) - - valid: bpy.props.BoolProperty( - name="Valid", description="True if operation is ok for calculation", default=True) - changedata: bpy.props.StringProperty( - name='changedata', description='change data for checking if stuff changed.') + changed: BoolProperty( + name="True if any of the operation settings has changed", + description="mark for update", + default=False, + ) + update_zbufferimage_tag: BoolProperty( + name="mark zbuffer image for update", + description="mark for update", + default=True, + ) + update_offsetimage_tag: BoolProperty( + name="mark offset image for update", + description="mark for update", + default=True, + ) + update_silhouete_tag: BoolProperty( + name="mark silhouete image for update", + description="mark for update", + default=True, + ) + update_ambient_tag: BoolProperty( + name="mark ambient polygon for update", + description="mark for update", + default=True, + ) + update_bullet_collision_tag: BoolProperty( + name="mark bullet collisionworld for update", + description="mark for update", + default=True, + ) + + valid: BoolProperty( + name="Valid", + description="True if operation is ok for calculation", + default=True, + ) + changedata: StringProperty( + name='changedata', + description='change data for checking if stuff changed.', + ) # process related data - computing: bpy.props.BoolProperty(name="Computing right now", description="", default=False) - pid: bpy.props.IntProperty(name="process id", description="Background process id", default=-1) - outtext: bpy.props.StringProperty(name='outtext', description='outtext', default='') + computing: BoolProperty( + name="Computing right now", + description="", + default=False, + ) + pid: IntProperty( + name="process id", + description="Background process id", + default=-1, + ) + outtext: StringProperty( + name='outtext', + description='outtext', + default='', + ) # this type is defined just to hold reference to operations for chains class opReference(bpy.types.PropertyGroup): - name: bpy.props.StringProperty(name="Operation name", default="Operation") + name: StringProperty( + name="Operation name", + default="Operation", + ) computing = False # for UiList display # chain is just a set of operations which get connected on export into 1 file. class camChain(bpy.types.PropertyGroup): - index: bpy.props.IntProperty( - name="index", description="index in the hard-defined camChains", default=-1) - active_operation: bpy.props.IntProperty(name="active operation", description="active operation in chain", - default=-1) - name: bpy.props.StringProperty(name="Chain Name", default="Chain") - filename: bpy.props.StringProperty(name="File name", default="Chain") # filename of - valid: bpy.props.BoolProperty( - name="Valid", description="True if whole chain is ok for calculation", default=True) - computing: bpy.props.BoolProperty(name="Computing right now", description="", default=False) + index: IntProperty( + name="index", + description="index in the hard-defined camChains", + default=-1, + ) + active_operation: IntProperty( + name="active operation", + description="active operation in chain", + default=-1, + ) + name: StringProperty( + name="Chain Name", + default="Chain", + ) + filename: StringProperty( + name="File name", + default="Chain", + ) # filename of + valid: BoolProperty( + name="Valid", + description="True if whole chain is ok for calculation", + default=True, + ) + computing: BoolProperty( + name="Computing right now", + description="", + default=False, + ) # this is to hold just operation names. - operations: bpy.props.CollectionProperty(type=opReference) + operations: CollectionProperty( + type=opReference, + ) class CAM_CUTTER_MT_presets(Menu): @@ -1594,28 +2438,47 @@ def register(): s = bpy.types.Scene - s.cam_chains = bpy.props.CollectionProperty(type=camChain) - s.cam_active_chain = bpy.props.IntProperty( - name="CAM Active Chain", description="The selected chain") + s.cam_chains = CollectionProperty( + type=camChain, + ) + s.cam_active_chain = IntProperty( + name="CAM Active Chain", + description="The selected chain", + ) - s.cam_operations = bpy.props.CollectionProperty(type=camOperation) + s.cam_operations = CollectionProperty( + type=camOperation, + ) - s.cam_active_operation = bpy.props.IntProperty(name="CAM Active Operation", description="The selected operation", - update=updateOperation) - s.cam_machine = bpy.props.PointerProperty(type=machineSettings) + s.cam_active_operation = IntProperty( + name="CAM Active Operation", + description="The selected operation", + update=updateOperation, + ) + s.cam_machine = PointerProperty( + type=machineSettings, + ) - s.cam_import_gcode = bpy.props.PointerProperty(type=import_settings) + s.cam_import_gcode = PointerProperty( + type=import_settings, + ) - s.cam_text = bpy.props.StringProperty() + s.cam_text = StringProperty() bpy.app.handlers.frame_change_pre.append(ops.timer_update) bpy.app.handlers.load_post.append(check_operations_on_load) # bpy.types.INFO_HT_header.append(header_info) - s.cam_pack = bpy.props.PointerProperty(type=PackObjectsSettings) + s.cam_pack = PointerProperty( + type=PackObjectsSettings, + ) - s.cam_slice = bpy.props.PointerProperty(type=SliceObjectsSettings) + s.cam_slice = PointerProperty( + type=SliceObjectsSettings, + ) - bpy.types.Scene.interface = bpy.props.PointerProperty(type=CAM_INTERFACE_Properties) + bpy.types.Scene.interface = PointerProperty( + type=CAM_INTERFACE_Properties, + ) basrelief.register() diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 25e0a2c0f..970d8bdb3 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -5,6 +5,8 @@ import pathlib import zipfile import bpy +from bpy.props import StringProperty + import re import io import os @@ -114,7 +116,8 @@ def execute(self, context): commit_sha = commit_list[0]['sha'] if bpy.context.preferences.addons['cam'].preferences.last_commit_hash != commit_sha: # get zipball from this commit - zip_url = update_source.replace("/commits", f"/zipball/{commit_sha}") + zip_url = update_source.replace( + "/commits", f"/zipball/{commit_sha}") self.install_zip_from_url(zip_url) bpy.context.preferences.addons['cam'].preferences.last_commit_hash = commit_sha bpy.ops.wm.save_userpref() @@ -130,9 +133,11 @@ def install_zip_from_url(self, zip_url): for fileinfo in files: filename = fileinfo.filename if fileinfo.is_dir() == False: - path_pos = filename.replace("\\", "/").find("/scripts/addons/cam/") + path_pos = filename.replace( + "\\", "/").find("/scripts/addons/cam/") if path_pos != -1: - relative_path = filename[path_pos+len("/scripts/addons/cam/"):] + relative_path = filename[path_pos + + len("/scripts/addons/cam/"):] out_path = cam_addon_path / relative_path print(out_path) # check folder exists @@ -162,7 +167,9 @@ class UpdateSourceOperator(bpy.types.Operator): bl_idname = "render.cam_set_update_source" bl_label = "Set blendercam update source" - new_source: bpy.props.StringProperty(default='') + new_source: StringProperty( + default='', + ) def execute(self, context): bpy.context.preferences.addons['cam'].preferences.update_source = self.new_source diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index d6551a807..f6dc8030c 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -48,7 +48,8 @@ def restrictbuf(inbuf, outbuf): # scale down array.... # if dx<2: # restricted= # num=restricted.shape[0]*restricted.shape[1] - outbuf[:] = (inbuf[::2, ::2]+inbuf[1::2, ::2]+inbuf[::2, 1::2]+inbuf[1::2, 1::2])/4.0 + outbuf[:] = (inbuf[::2, ::2]+inbuf[1::2, ::2] + + inbuf[::2, 1::2]+inbuf[1::2, 1::2])/4.0 elif NUMPYALG: # numpy method yrange = numpy.arange(0, outy) @@ -78,7 +79,8 @@ def restrictbuf(inbuf, outbuf): # scale down array.... indices[0] = sxstartrange.repeat(outy) - indices[1] = systartrange.repeat(outx).reshape(outx, outy).swapaxes(0, 1).flatten() + indices[1] = systartrange.repeat(outx).reshape( + outx, outy).swapaxes(0, 1).flatten() # systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) # syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 @@ -153,7 +155,8 @@ def prolongate(inbuf, outbuf): indices = numpy.arange(outx*outy*2).reshape((2, outx*outy)) indices[0] = sxstartrange.repeat(outy) - indices[1] = systartrange.repeat(outx).reshape(outx, outy).swapaxes(0, 1).flatten() + indices[1] = systartrange.repeat(outx).reshape( + outx, outy).swapaxes(0, 1).flatten() # systartrange=numpy.max(0,numpy.ceil(syrange-xfiltersize)) # syendrange=numpy.min(numpy.floor(syrange+xfiltersize),iny-1)+1 @@ -225,9 +228,11 @@ def calculate_defect(D, U, F): U[1:-1, :-2] - U[1:-1, 2:] + 4*U[1:-1, 1:-1] # sides D[1:-1, 0] = F[1:-1, 0] - U[:-2, 0] - U[2:, 0] - U[1:-1, 1] + 3*U[1:-1, 0] - D[1:-1, -1] = F[1:-1, -1] - U[:-2, -1] - U[2:, -1] - U[1:-1, -2] + 3*U[1:-1, -1] + D[1:-1, -1] = F[1:-1, -1] - U[:-2, -1] - \ + U[2:, -1] - U[1:-1, -2] + 3*U[1:-1, -1] D[0, 1:-1] = F[0, 1:-1] - U[0, :-2] - U[0, :-2] - U[1, 1:-1] + 3*U[0, 1:-1] - D[-1, 1:-1] = F[-1, 1:-1] - U[-1, :-2] - U[-1, :-2] - U[-1, 1:-1] + 3*U[-1, 1:-1] + D[-1, 1:-1] = F[-1, 1:-1] - U[-1, :-2] - \ + U[-1, :-2] - U[-1, 1:-1] + 3*U[-1, 1:-1] # coners D[0, 0] = F[0, 0] - U[0, 1] - U[1, 0] + 2*U[0, 0] D[0, -1] = F[0, -1] - U[1, -1] - U[0, -2] + 2*U[0, -1] @@ -399,7 +404,8 @@ def asolve(b, x): def atimes(x, res): - res[1:-1, 1:-1] = x[:-2, 1:-1]+x[2:, 1:-1]+x[1:-1, :-2]+x[1:-1, 2:] - 4*x[1:-1, 1:-1] + res[1:-1, 1:-1] = x[:-2, 1:-1]+x[2:, 1:-1] + \ + x[1:-1, :-2]+x[1:-1, 2:] - 4*x[1:-1, 1:-1] # sides res[1:-1, 0] = x[0:-2, 0]+x[2:, 0]+x[1:-1, 1] - 3*x[1:-1, 0] res[1:-1, -1] = x[0:-2, -1]+x[2:, -1]+x[1:-1, -2] - 3*x[1:-1, -1] @@ -664,7 +670,8 @@ def buildMesh(mesh_z, br): ob = bpy.data.objects['BasReliefMesh'] ob.select_set(True) bpy.context.view_layer.objects.active = ob - bpy.context.active_object.dimensions = (br.widthmm/1000, br.heightmm/1000, br.thicknessmm/1000) + bpy.context.active_object.dimensions = ( + br.widthmm/1000, br.heightmm/1000, br.thicknessmm/1000) bpy.context.active_object.location = (float( br.justifyx)*br.widthmm/1000, float(br.justifyy)*br.heightmm/1000, float(br.justifyz)*br.thicknessmm/1000) @@ -719,7 +726,8 @@ def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view scene.render.resolution_x = x scene.render.resolution_y = y scene.render.resolution_percentage = 100 - bpy.ops.render.render(animation=False, write_still=False, use_viewport=True, layer="", scene="") + bpy.ops.render.render(animation=False, write_still=False, + use_viewport=True, layer="", scene="") if our_renderer is not None: nodes.remove(our_renderer) if our_viewer is not None: @@ -769,7 +777,8 @@ def problemAreas(br): # it' ok, we can treat neg and positive silh separately here: a = br.attenuation - planar = nar < (nar.min()+0.0001) # numpy.logical_or(silhxplanar,silhyplanar)# + # numpy.logical_or(silhxplanar,silhyplanar)# + planar = nar < (nar.min()+0.0001) # sqrt for silhouettes recovery: sqrarx = numpy.abs(gx) for iter in range(0, br.silhouette_exponent): @@ -859,7 +868,8 @@ def relief(br): # it' ok, we can treat neg and positive silh separately here: a = br.attenuation - planar = nar < (nar.min()+0.0001) # numpy.logical_or(silhxplanar,silhyplanar)# + # numpy.logical_or(silhxplanar,silhyplanar)# + planar = nar < (nar.min()+0.0001) # sqrt for silhouettes recovery: sqrarx = numpy.abs(gx) for iter in range(0, br.silhouette_exponent): @@ -927,7 +937,8 @@ def filterwindow(x, y, cx=0, cy=0): # , curve=None): # v=(abs((cx-x)/(cx))+abs((cy-y)/(cy))) # return v - mask = numpy.fromfunction(filterwindow, divg.shape, cx=crow, cy=ccol) # , curve=cur) + mask = numpy.fromfunction( + filterwindow, divg.shape, cx=crow, cy=ccol) # , curve=cur) mask = numpy.sqrt(mask) # for x in range(mask.shape[0]): # for y in range(mask.shape[1]): @@ -970,64 +981,213 @@ def filterwindow(x, y, cx=0, cy=0): # , curve=None): class BasReliefsettings(bpy.types.PropertyGroup): - use_image_source: bpy.props.BoolProperty(name="Use image source", description="", default=False) - source_image_name: bpy.props.StringProperty(name='Image source', description='image source') - view_layer_name: bpy.props.StringProperty( - name='View layer source', description='Make a bas-relief from whatever is on this view layer') - bit_diameter: FloatProperty(name="Diameter of ball end in mm", description="Diameter of bit which will be used for carving", - min=0.01, max=50.0, default=3.175, precision=PRECISION) - pass_per_radius: bpy.props.IntProperty( - name="Passes per radius", description="Amount of passes per radius\n(more passes, more mesh precision)", default=2, min=1, max=10) - widthmm: bpy.props.IntProperty(name="Desired width in mm", default=200, min=5, max=4000) - heightmm: bpy.props.IntProperty(name="Desired height in mm", default=150, min=5, max=4000) - thicknessmm: bpy.props.IntProperty(name="Thickness in mm", default=15, min=5, max=100) - - justifyx: bpy.props.EnumProperty(name="X", items=[( - '1', 'Left', '', 0), ('-0.5', 'Centered', '', 1), ('-1', 'Right', '', 2)], default='-1') - justifyy: bpy.props.EnumProperty(name="Y", items=[( - '1', 'Bottom', '', 0), ('-0.5', 'Centered', '', 2), ('-1', 'Top', '', 1), ], default='-1') - justifyz: bpy.props.EnumProperty(name="Z", items=[( - '-1', 'Below 0', '', 0), ('-0.5', 'Centered', '', 2), ('1', 'Above 0', '', 1), ], default='-1') - - depth_exponent: FloatProperty(name="Depth exponent", description="Initial depth map is taken to this power. Higher = sharper relief", - min=0.5, max=10.0, default=1.0, precision=PRECISION) + use_image_source: BoolProperty( + name="Use image source", + description="", + default=False, + ) + source_image_name: StringProperty( + name='Image source', + description='image source', + ) + view_layer_name: StringProperty( + name='View layer source', + description='Make a bas-relief from whatever is on this view layer', + ) + bit_diameter: FloatProperty( + name="Diameter of ball end in mm", + description="Diameter of bit which will be used for carving", + min=0.01, + max=50.0, + default=3.175, + precision=PRECISION, + ) + pass_per_radius: IntProperty( + name="Passes per radius", + description="Amount of passes per radius\n(more passes, " + "more mesh precision)", + default=2, + min=1, + max=10, + ) + widthmm: IntProperty( + name="Desired width in mm", + default=200, + min=5, + max=4000, + ) + heightmm: IntProperty( + name="Desired height in mm", + default=150, + min=5, + max=4000, + ) + thicknessmm: IntProperty( + name="Thickness in mm", + default=15, + min=5, + max=100, + ) + + justifyx: EnumProperty( + name="X", + items=[ + ('1', 'Left', '', 0), + ('-0.5', 'Centered', '', 1), + ('-1', 'Right', '', 2) + ], + default='-1', + ) + justifyy: EnumProperty( + name="Y", + items=[ + ('1', 'Bottom', '', 0), + ('-0.5', 'Centered', '', 2), + ('-1', 'Top', '', 1), + ], + default='-1', + ) + justifyz: EnumProperty( + name="Z", + items=[ + ('-1', 'Below 0', '', 0), + ('-0.5', 'Centered', '', 2), + ('1', 'Above 0', '', 1), + ], + default='-1', + ) + + depth_exponent: FloatProperty( + name="Depth exponent", + description="Initial depth map is taken to this power. Higher = " + "sharper relief", + min=0.5, + max=10.0, + default=1.0, + precision=PRECISION, + ) silhouette_threshold: FloatProperty( - name="Silhouette threshold", description="Silhouette threshold", min=0.000001, max=1.0, default=0.003, precision=PRECISION) - recover_silhouettes: bpy.props.BoolProperty( - name="Recover silhouettes", description="", default=True) - silhouette_scale: FloatProperty(name="Silhouette scale", description="Silhouette scale", - min=0.000001, max=5.0, default=0.3, precision=PRECISION) - silhouette_exponent: bpy.props.IntProperty( - name="Silhouette square exponent", description="If lower, true depht distances between objects will be more visibe in the relief", default=3, min=0, max=5) - attenuation: FloatProperty(name="Gradient attenuation", description="Gradient attenuation", - min=0.000001, max=100.0, default=1.0, precision=PRECISION) - min_gridsize: bpy.props.IntProperty(name="Minimum grid size", default=16, min=2, max=512) - smooth_iterations: bpy.props.IntProperty(name="Smooth iterations", default=1, min=1, max=64) - vcycle_iterations: bpy.props.IntProperty( - name="V-cycle iterations", description="set up higher for plananr constraint", default=2, min=1, max=128) - linbcg_iterations: bpy.props.IntProperty( - name="Linbcg iterations", description="set lower for flatter relief, and when using planar constraint", default=5, min=1, max=64) - use_planar: bpy.props.BoolProperty(name="Use planar constraint", description="", default=False) - gradient_scaling_mask_use: bpy.props.BoolProperty( - name="Scale gradients with mask", description="", default=False) - decimate_ratio: FloatProperty(name="Decimate Ratio", description="Simplify the mesh using the Decimate modifier. The lower the value the more simplyfied", - min=0.01, max=1.0, default=0.1, precision=PRECISION) - - gradient_scaling_mask_name: bpy.props.StringProperty( - name='Scaling mask name', description='mask name') - scale_down_before_use: bpy.props.BoolProperty( - name="Scale down image before processing", description="", default=False) + name="Silhouette threshold", + description="Silhouette threshold", + min=0.000001, + max=1.0, + default=0.003, + precision=PRECISION, + ) + recover_silhouettes: BoolProperty( + name="Recover silhouettes", + description="", + default=True, + ) + silhouette_scale: FloatProperty( + name="Silhouette scale", + description="Silhouette scale", + min=0.000001, + max=5.0, + default=0.3, + precision=PRECISION, + ) + silhouette_exponent: IntProperty( + name="Silhouette square exponent", + description="If lower, true depht distances between objects will be " + "more visibe in the relief", + default=3, + min=0, + max=5, + ) + attenuation: FloatProperty( + name="Gradient attenuation", + description="Gradient attenuation", + min=0.000001, + max=100.0, + default=1.0, + precision=PRECISION, + ) + min_gridsize: IntProperty( + name="Minimum grid size", + default=16, + min=2, + max=512, + ) + smooth_iterations: IntProperty( + name="Smooth iterations", + default=1, + min=1, + max=64, + ) + vcycle_iterations: IntProperty( + name="V-cycle iterations", + description="set up higher for plananr constraint", + default=2, + min=1, + max=128, + ) + linbcg_iterations: IntProperty( + name="Linbcg iterations", + description="set lower for flatter relief, and when using " + "planar constraint", + default=5, + min=1, + max=64, + ) + use_planar: BoolProperty( + name="Use planar constraint", + description="", + default=False, + ) + gradient_scaling_mask_use: BoolProperty( + name="Scale gradients with mask", + description="", + default=False, + ) + decimate_ratio: FloatProperty( + name="Decimate Ratio", + description="Simplify the mesh using the Decimate modifier. " + "The lower the value the more simplyfied", + min=0.01, + max=1.0, + default=0.1, + precision=PRECISION, + ) + + gradient_scaling_mask_name: StringProperty( + name='Scaling mask name', + description='mask name', + ) + scale_down_before_use: BoolProperty( + name="Scale down image before processing", + description="", + default=False, + ) scale_down_before: FloatProperty( - name="Image scale", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) - detail_enhancement_use: bpy.props.BoolProperty( - name="Enhance details ", description="enhance details by frequency analysis", default=False) + name="Image scale", + description="Image scale", + min=0.025, + max=1.0, + default=.5, + precision=PRECISION, + ) + detail_enhancement_use: BoolProperty( + name="Enhance details ", + description="enhance details by frequency analysis", + default=False, + ) #detail_enhancement_freq=FloatProperty(name="frequency limit", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) detail_enhancement_amount: FloatProperty( - name="amount", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) + name="amount", + description="Image scale", + min=0.025, + max=1.0, + default=.5, + precision=PRECISION, + ) - advanced: bpy.props.BoolProperty( - name="Advanced options", description="show advanced options", default=True) + advanced: BoolProperty( + name="Advanced options", + description="show advanced options", + default=True, + ) class BASRELIEF_Panel(bpy.types.Panel): @@ -1063,7 +1223,8 @@ def draw(self, context): if br.use_image_source: layout.prop_search(br, 'source_image_name', bpy.data, "images") else: - layout.prop_search(br, 'view_layer_name', bpy.context.scene, "view_layers") + layout.prop_search(br, 'view_layer_name', + bpy.context.scene, "view_layers") layout.prop(br, 'depth_exponent') layout.label(text="Project parameters") layout.prop(br, 'bit_diameter') @@ -1097,7 +1258,8 @@ def draw(self, context): layout.prop(br, 'gradient_scaling_mask_use') if br.advanced: if br.gradient_scaling_mask_use: - layout.prop_search(br, 'gradient_scaling_mask_name', bpy.data, "images") + layout.prop_search( + br, 'gradient_scaling_mask_name', bpy.data, "images") layout.prop(br, 'detail_enhancement_use') if br.detail_enhancement_use: # layout.prop(br,'detail_enhancement_freq') @@ -1179,7 +1341,9 @@ def register(): for p in get_panels(): bpy.utils.register_class(p) s = bpy.types.Scene - s.basreliefsettings = bpy.props.PointerProperty(type=BasReliefsettings) + s.basreliefsettings = PointerProperty( + type=BasReliefsettings, + ) def unregister(): diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index ef55b6a07..d7d8586ff 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -40,22 +40,65 @@ class CamCurveHatch(bpy.types.Operator): bl_label = "CrossHatch curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - angle: bpy.props.FloatProperty(name="angle", default=0, min=- - math.pi/2, max=math.pi/2, precision=4, subtype="ANGLE") - distance: bpy.props.FloatProperty(name="spacing", default=0.015, - min=0, max=3.0, precision=4, unit="LENGTH") - offset: bpy.props.FloatProperty(name="Margin", default=0.001, - min=-1.0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height", default=0.000, - min=-1.0, max=1.0, precision=4, unit="LENGTH") - amount: bpy.props.IntProperty(name="amount", default=10, min=1, max=10000) - hull: bpy.props.BoolProperty(name="Convex Hull", default=False) - contour: bpy.props.BoolProperty(name="Contour Curve", default=False) - contour_separate: bpy.props.BoolProperty(name="Contour separate", default=False) - pocket_type: EnumProperty(name='Type pocket', - items=(('BOUNDS', 'makes a bounds rectangle', 'makes a bounding square'), - ('POCKET', 'Pocket', 'makes a pocket inside a closed loop')), - description='Type of pocket', default='BOUNDS') + angle: FloatProperty( + name="angle", + default=0, min=- + math.pi/2, + max=math.pi/2, + precision=4, + subtype="ANGLE", + ) + distance: FloatProperty( + name="spacing", + default=0.015, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + offset: FloatProperty( + name="Margin", + default=0.001, + min=-1.0, + max=3.0, + precision=4, + unit="LENGTH", + ) + height: FloatProperty( + name="Height", + default=0.000, + min=-1.0, + max=1.0, + precision=4, + unit="LENGTH", + ) + amount: IntProperty( + name="amount", + default=10, + min=1, + max=10000, + ) + hull: BoolProperty( + name="Convex Hull", + default=False, + ) + contour: BoolProperty( + name="Contour Curve", + default=False, + ) + contour_separate: BoolProperty( + name="Contour separate", + default=False, + ) + pocket_type: EnumProperty( + name='Type pocket', + items=( + ('BOUNDS', 'makes a bounds rectangle', 'makes a bounding square'), + ('POCKET', 'Pocket', 'makes a pocket inside a closed loop') + ), + description='Type of pocket', + default='BOUNDS', + ) @classmethod def poll(cls, context): @@ -103,15 +146,19 @@ def execute(self, context): width = maxx - minx centerx = (minx+maxx) / 2 diagonal = math.hypot(width, height) - simple.add_bound_rectangle(minx, miny, maxx, maxy, 'crosshatch_bound') + simple.add_bound_rectangle( + minx, miny, maxx, maxy, 'crosshatch_bound') amount = int(2*diagonal/self.distance) + 1 for x in range(amount): distance = x * self.distance - diagonal - coords.append(((distance, diagonal + 0.5), (distance, -diagonal - 0.5))) + coords.append(((distance, diagonal + 0.5), + (distance, -diagonal - 0.5))) - lines = MultiLineString(coords) # create a multilinestring shapely object - rotated = affinity.rotate(lines, self.angle, use_radians=True) # rotate using shapely + # create a multilinestring shapely object + lines = MultiLineString(coords) + rotated = affinity.rotate( + lines, self.angle, use_radians=True) # rotate using shapely translated = affinity.translate( rotated, xoff=centerx, yoff=centery) # move using shapely @@ -161,28 +208,86 @@ class CamCurvePlate(bpy.types.Operator): bl_label = "Sign plate" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - radius: bpy.props.FloatProperty(name="Corner Radius", default=.025, - min=0, max=0.1, precision=4, unit="LENGTH") - width: bpy.props.FloatProperty(name="Width of plate", default=0.3048, - min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of plate", default=0.457, - min=0, max=3.0, precision=4, unit="LENGTH") - hole_diameter: bpy.props.FloatProperty(name="Hole diameter", default=0.01, min=0, max=3.0, precision=4, - unit="LENGTH") - hole_tolerance: bpy.props.FloatProperty(name="Hole V Tolerance", default=0.005, min=0, max=3.0, precision=4, - unit="LENGTH") - hole_vdist: bpy.props.FloatProperty(name="Hole Vert distance", default=0.400, min=0, max=3.0, precision=4, - unit="LENGTH") - hole_hdist: bpy.props.FloatProperty(name="Hole horiz distance", default=0, min=0, max=3.0, precision=4, - unit="LENGTH") - hole_hamount: bpy.props.IntProperty(name="Hole horiz amount", default=1, min=0, max=50) - resolution: bpy.props.IntProperty(name="Spline resolution", default=50, min=3, max=150) - plate_type: EnumProperty(name='Type plate', - items=(('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), - ('COVE', 'Cove corner', 'Makes a plate with circles cut in each corner '), - ('BEVEL', 'Bevel corner', 'Makes a plate with beveled corners '), - ('OVAL', 'Elipse', 'Makes an oval plate')), - description='Type of Plate', default='ROUNDED') + radius: FloatProperty( + name="Corner Radius", + default=.025, + min=0, + max=0.1, + precision=4, + unit="LENGTH", + ) + width: FloatProperty( + name="Width of plate", + default=0.3048, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + height: FloatProperty( + name="Height of plate", + default=0.457, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hole_diameter: FloatProperty( + name="Hole diameter", + default=0.01, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hole_tolerance: FloatProperty( + name="Hole V Tolerance", + default=0.005, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hole_vdist: FloatProperty( + name="Hole Vert distance", + default=0.400, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hole_hdist: FloatProperty( + name="Hole horiz distance", + default=0, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hole_hamount: IntProperty( + name="Hole horiz amount", + default=1, + min=0, + max=50, + ) + resolution: IntProperty( + name="Spline resolution", + default=50, + min=3, + max=150, + ) + plate_type: EnumProperty( + name='Type plate', + items=( + ('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), + ('COVE', 'Cove corner', + 'Makes a plate with circles cut in each corner '), + ('BEVEL', 'Bevel corner', 'Makes a plate with beveled corners '), + ('OVAL', 'Elipse', 'Makes an oval plate') + ), + description='Type of Plate', + default='ROUNDED', + ) def draw(self, context): layout = self.layout @@ -224,8 +329,10 @@ def execute(self, context): simple.active_name("_circ_RT") bpy.context.object.data.resolution_u = self.resolution - simple.select_multiple("_circ") # select the circles for the four corners - utils.polygonConvexHull(context) # perform hull operation on the four corner circles + # select the circles for the four corners + simple.select_multiple("_circ") + # perform hull operation on the four corner circles + utils.polygonConvexHull(context) simple.active_name("plate_base") simple.remove_multiple("_circ") # remove corner circles @@ -271,13 +378,15 @@ def execute(self, context): bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, - location=(right+self.radius, bottom-self.radius, 0), + location=(right+self.radius, + bottom-self.radius, 0), rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_RB") bpy.context.object.data.resolution_u = self.resolution bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, - location=(left-self.radius, top+self.radius, 0), + location=(left-self.radius, + top+self.radius, 0), rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_LT") @@ -285,7 +394,8 @@ def execute(self, context): bpy.ops.curve.simple(align='WORLD', Simple_Type='Rectangle', Simple_width=self.radius*2, Simple_length=self.radius*2, - location=(right+self.radius, top+self.radius, 0), + location=(right+self.radius, + top+self.radius, 0), rotation=(0, 0, 0.785398), outputType='POLY', use_cyclic_u=True, edit_mode=False) simple.active_name("_bev_RT") @@ -329,14 +439,16 @@ def execute(self, context): if self.hole_hamount > 1: if self.hole_hamount % 2 != 0: for x in range(int((self.hole_hamount - 1) / 2)): - dist = self.hole_hdist * (x + 1) # calculate the distance from the middle + # calculate the distance from the middle + dist = self.hole_hdist * (x + 1) simple.duplicate() bpy.context.object.location[0] = dist simple.duplicate() bpy.context.object.location[0] = -dist else: for x in range(int(self.hole_hamount / 2)): - dist = self.hole_hdist * x + self.hole_hdist / 2 # calculate the distance from the middle + dist = self.hole_hdist * x + self.hole_hdist / \ + 2 # calculate the distance from the middle if x == 0: # special case where the original hole only needs to move and not duplicate bpy.context.object.location[0] = dist simple.duplicate() @@ -348,11 +460,13 @@ def execute(self, context): bpy.context.object.location[0] = -dist simple.join_multiple("plate_hole") # join the holes together - simple.select_multiple("plate_") # select everything starting with plate_ + # select everything starting with plate_ + simple.select_multiple("plate_") # Make the plate base active bpy.context.view_layer.objects.active = bpy.data.objects['plate_base'] - utils.polygonBoolean(context, "DIFFERENCE") # Remove holes from the base + # Remove holes from the base + utils.polygonBoolean(context, "DIFFERENCE") simple.remove_multiple("plate_") # Remove temporary base and holes simple.remove_multiple("_") @@ -369,18 +483,58 @@ class CamCurveFlatCone(bpy.types.Operator): bl_label = "Cone flat calculator" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - small_d: bpy.props.FloatProperty( - name="small diameter", default=.025, min=0, max=0.1, precision=4, unit="LENGTH") - large_d: bpy.props.FloatProperty( - name="large diameter", default=0.3048, min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of cone", default=0.457, - min=0, max=3.0, precision=4, unit="LENGTH") - tab: bpy.props.FloatProperty(name="tab witdh", default=0.01, min=0, - max=0.100, precision=4, unit="LENGTH") - intake: bpy.props.FloatProperty(name="intake diameter", default=0, - min=0, max=0.200, precision=4, unit="LENGTH") - intake_skew: bpy.props.FloatProperty(name="intake_skew", default=1, min=0.1, max=4) - resolution: bpy.props.IntProperty(name="Resolution", default=12, min=5, max=200) + small_d: FloatProperty( + name="small diameter", + default=.025, + min=0, + max=0.1, + precision=4, + unit="LENGTH", + ) + large_d: FloatProperty( + name="large diameter", + default=0.3048, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + height: FloatProperty( + name="Height of cone", + default=0.457, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + tab: FloatProperty( + name="tab witdh", + default=0.01, + min=0, + max=0.100, + precision=4, + unit="LENGTH", + ) + intake: FloatProperty( + name="intake diameter", + default=0, + min=0, + max=0.200, + precision=4, + unit="LENGTH", + ) + intake_skew: FloatProperty( + name="intake_skew", + default=1, + min=0.1, + max=4, + ) + resolution: IntProperty( + name="Resolution", + default=12, + min=5, + max=200, + ) def execute(self, context): y = self.small_d / 2 @@ -426,25 +580,75 @@ class CamCurveMortise(bpy.types.Operator): bl_label = "Mortise" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - finger_size: bpy.props.BoolProperty(name="kurf bending only", default=False) - finger_size: bpy.props.FloatProperty(name="Maximum Finger Size", default=0.015, min=0.005, max=3.0, precision=4, - unit="LENGTH") - min_finger_size: bpy.props.FloatProperty(name="Minimum Finger Size", default=0.0025, min=0.001, max=3.0, - precision=4, - unit="LENGTH") - finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4, - unit="LENGTH") - plate_thickness: bpy.props.FloatProperty(name="Drawer plate thickness", default=0.00477, min=0.001, max=3.0, - unit="LENGTH") - side_height: bpy.props.FloatProperty( - name="side height", default=0.05, min=0.001, max=3.0, unit="LENGTH") - flex_pocket: bpy.props.FloatProperty( - name="Flex pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH") - top_bottom: bpy.props.BoolProperty(name="Side Top & bottom fingers", default=True) - opencurve: bpy.props.BoolProperty(name="OpenCurve", default=False) - adaptive: bpy.props.FloatProperty(name="Adaptive angle threshold", default=0.0, min=0.000, max=2, subtype="ANGLE", - unit="ROTATION") - double_adaptive: bpy.props.BoolProperty(name="Double adaptive Pockets", default=False) + finger_size: BoolProperty( + name="kurf bending only", + default=False, + ) + finger_size: FloatProperty( + name="Maximum Finger Size", + default=0.015, + min=0.005, + max=3.0, + precision=4, + unit="LENGTH", + ) + min_finger_size: FloatProperty( + name="Minimum Finger Size", + default=0.0025, + min=0.001, + max=3.0, + precision=4, + unit="LENGTH", + ) + finger_tolerance: FloatProperty( + name="Finger play room", + default=0.000045, + min=0, + max=0.003, + precision=4, + unit="LENGTH", + ) + plate_thickness: FloatProperty( + name="Drawer plate thickness", + default=0.00477, + min=0.001, + max=3.0, + unit="LENGTH", + ) + side_height: FloatProperty( + name="side height", + default=0.05, + min=0.001, + max=3.0, + unit="LENGTH", + ) + flex_pocket: FloatProperty( + name="Flex pocket", + default=0.004, + min=0.000, + max=1.0, + unit="LENGTH", + ) + top_bottom: BoolProperty( + name="Side Top & bottom fingers", + default=True, + ) + opencurve: BoolProperty( + name="OpenCurve", + default=False, + ) + adaptive: FloatProperty( + name="Adaptive angle threshold", + default=0.0, + min=0.000, + max=2, + subtype="ANGLE", + unit="ROTATION", + ) + double_adaptive: BoolProperty( + name="Double adaptive Pockets", + default=False, + ) @classmethod def poll(cls, context): @@ -463,7 +667,8 @@ def execute(self, context): coords = [] for v in obj.data.vertices: # extract X,Y coordinates from the vertices data coords.append((v.co.x, v.co.y)) - line = LineString(coords) # convert coordinates to shapely LineString datastructure + # convert coordinates to shapely LineString datastructure + line = LineString(coords) simple.remove_multiple("-converted") utils.shapelyToCurve('-converted_curve', line, 0.0) shapes = utils.curveToShapely(o1) @@ -518,27 +723,69 @@ class CamCurveInterlock(bpy.types.Operator): bl_label = "Interlock" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - finger_size: bpy.props.FloatProperty(name="Finger Size", default=0.015, min=0.005, max=3.0, precision=4, - unit="LENGTH") - finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4, - unit="LENGTH") - plate_thickness: bpy.props.FloatProperty(name="Plate thickness", default=0.00477, min=0.001, max=3.0, - unit="LENGTH") - opencurve: bpy.props.BoolProperty(name="OpenCurve", default=False) - interlock_type: EnumProperty(name='Type of interlock', - items=(('TWIST', 'Twist', 'Iterlock requires 1/4 turn twist'), - ('GROOVE', 'Groove', 'Simple sliding groove'), - ('PUZZLE', 'Puzzle interlock', 'puzzle good for flat joints')), - description='Type of interlock', - default='GROOVE') - finger_amount: bpy.props.IntProperty(name="Finger Amount", default=2, min=1, max=100) - tangent_angle: bpy.props.FloatProperty(name="Tangent deviation", default=0.0, min=0.000, max=2, subtype="ANGLE", - unit="ROTATION") - fixed_angle: bpy.props.FloatProperty(name="fixed angle", default=0.0, min=0.000, max=2, subtype="ANGLE", - unit="ROTATION") + finger_size: FloatProperty( + name="Finger Size", + default=0.015, + min=0.005, + max=3.0, + precision=4, + unit="LENGTH", + ) + finger_tolerance: FloatProperty( + name="Finger play room", + default=0.000045, + min=0, + max=0.003, + precision=4, + unit="LENGTH", + ) + plate_thickness: FloatProperty( + name="Plate thickness", + default=0.00477, + min=0.001, + max=3.0, + unit="LENGTH", + ) + opencurve: BoolProperty( + name="OpenCurve", + default=False, + ) + interlock_type: EnumProperty( + name='Type of interlock', + items=( + ('TWIST', 'Twist', 'Iterlock requires 1/4 turn twist'), + ('GROOVE', 'Groove', 'Simple sliding groove'), + ('PUZZLE', 'Puzzle interlock', 'puzzle good for flat joints') + ), + description='Type of interlock', + default='GROOVE', + ) + finger_amount: IntProperty( + name="Finger Amount", + default=2, + min=1, + max=100, + ) + tangent_angle: FloatProperty( + name="Tangent deviation", + default=0.0, + min=0.000, + max=2, + subtype="ANGLE", + unit="ROTATION", + ) + fixed_angle: FloatProperty( + name="fixed angle", + default=0.0, + min=0.000, + max=2, + subtype="ANGLE", + unit="ROTATION", + ) def execute(self, context): - print(len(context.selected_objects), "selected object", context.selected_objects) + print(len(context.selected_objects), + "selected object", context.selected_objects) if len(context.selected_objects) > 0 and (context.active_object.type in ['CURVE', 'FONT']): o1 = bpy.context.active_object @@ -552,7 +799,8 @@ def execute(self, context): coords = [] for v in obj.data.vertices: # extract X,Y coordinates from the vertices data coords.append((v.co.x, v.co.y)) - line = LineString(coords) # convert coordinates to shapely LineString datastructure + # convert coordinates to shapely LineString datastructure + line = LineString(coords) simple.remove_multiple("-converted") utils.shapelyToCurve('-converted_curve', line, 0.0) shapes = utils.curveToShapely(o1) @@ -595,27 +843,90 @@ class CamCurveDrawer(bpy.types.Operator): bl_label = "Drawer" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - depth: bpy.props.FloatProperty(name="Drawer Depth", default=0.2, - min=0, max=1.0, precision=4, unit="LENGTH") - width: bpy.props.FloatProperty(name="Width of Drawer", default=0.125, - min=0, max=3.0, precision=4, unit="LENGTH") - height: bpy.props.FloatProperty(name="Height of drawer", default=0.07, - min=0, max=3.0, precision=4, unit="LENGTH") - finger_size: bpy.props.FloatProperty(name="Maximum Finger Size", default=0.015, min=0.005, max=3.0, precision=4, - unit="LENGTH") - finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.000045, min=0, max=0.003, precision=4, - unit="LENGTH") - finger_inset: bpy.props.FloatProperty(name="Finger inset", default=0.0, min=0.0, max=0.01, precision=4, - unit="LENGTH") - drawer_plate_thickness: bpy.props.FloatProperty(name="Drawer plate thickness", default=0.00477, min=0.001, max=3.0, - precision=4, unit="LENGTH") - drawer_hole_diameter: bpy.props.FloatProperty(name="Drawer hole diameter", default=0.02, min=0.00001, max=0.5, - precision=4, unit="LENGTH") - drawer_hole_offset: bpy.props.FloatProperty(name="Drawer hole offset", default=0.0, min=-0.5, max=0.5, precision=4, - unit="LENGTH") - overcut: bpy.props.BoolProperty(name="Add overcut", default=False) - overcut_diameter: bpy.props.FloatProperty(name="Overcut toool Diameter", default=0.003175, min=-0.001, max=0.5, - precision=4, unit="LENGTH") + depth: FloatProperty( + name="Drawer Depth", + default=0.2, + min=0, + max=1.0, + precision=4, + unit="LENGTH", + ) + width: FloatProperty( + name="Width of Drawer", + default=0.125, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + height: FloatProperty( + name="Height of drawer", + default=0.07, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + finger_size: FloatProperty( + name="Maximum Finger Size", + default=0.015, + min=0.005, + max=3.0, + precision=4, + unit="LENGTH", + ) + finger_tolerance: FloatProperty( + name="Finger play room", + default=0.000045, + min=0, + max=0.003, + precision=4, + unit="LENGTH", + ) + finger_inset: FloatProperty( + name="Finger inset", + default=0.0, + min=0.0, + max=0.01, + precision=4, + unit="LENGTH", + ) + drawer_plate_thickness: FloatProperty( + name="Drawer plate thickness", + default=0.00477, + min=0.001, + max=3.0, + precision=4, + unit="LENGTH", + ) + drawer_hole_diameter: FloatProperty( + name="Drawer hole diameter", + default=0.02, + min=0.00001, + max=0.5, + precision=4, + unit="LENGTH", + ) + drawer_hole_offset: FloatProperty( + name="Drawer hole offset", + default=0.0, + min=-0.5, + max=0.5, + precision=4, + unit="LENGTH", + ) + overcut: BoolProperty( + name="Add overcut", + default=False, + ) + overcut_diameter: FloatProperty( + name="Overcut toool Diameter", + default=0.003175, + min=-0.001, + max=0.5, + precision=4, + unit="LENGTH", + ) def draw(self, context): layout = self.layout @@ -633,9 +944,11 @@ def draw(self, context): layout.prop(self, 'overcut_diameter') def execute(self, context): - height_finger_amt = int(joinery.finger_amount(self.height, self.finger_size)) + height_finger_amt = int(joinery.finger_amount( + self.height, self.finger_size)) height_finger = (self.height + 0.0004) / height_finger_amt - width_finger_amt = int(joinery.finger_amount(self.width, self.finger_size)) + width_finger_amt = int(joinery.finger_amount( + self.width, self.finger_size)) width_finger = (self.width - self.finger_size) / width_finger_amt # create base @@ -681,14 +994,16 @@ def execute(self, context): # place back and front side by side simple.make_active('drawer_front') - bpy.ops.transform.transform(mode='TRANSLATION', value=(0.0, 2 * self.height, 0.0, 0.0)) + bpy.ops.transform.transform( + mode='TRANSLATION', value=(0.0, 2 * self.height, 0.0, 0.0)) simple.make_active('drawer_back') bpy.ops.transform.transform(mode='TRANSLATION', value=( self.width + 0.01, 2 * self.height, 0.0, 0.0)) # make side - finger_pair = joinery.finger_pair("_vfb", self.depth - self.drawer_plate_thickness, 0) + finger_pair = joinery.finger_pair( + "_vfb", self.depth - self.drawer_plate_thickness, 0) simple.make_active('_side') finger_pair.select_set(True) fronth.select_set(True) @@ -703,7 +1018,8 @@ def execute(self, context): bpy.ops.object.duplicate_move(OBJECT_OT_duplicate={"linked": False, "mode": 'TRANSLATION'}, TRANSFORM_OT_translate={"value": (0, -self.drawer_plate_thickness / 2, 0.0)}) simple.active_name("_wfb0") - joinery.finger_pair("_wfb0", 0, self.depth - self.drawer_plate_thickness) + joinery.finger_pair("_wfb0", 0, self.depth - + self.drawer_plate_thickness) simple.active_name('_bot_fingers') simple.difference('_bot', '_bottom') @@ -741,81 +1057,219 @@ class CamCurvePuzzle(bpy.types.Operator): bl_label = "Puzzle joints" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - diameter: bpy.props.FloatProperty(name="tool diameter", default=0.003175, min=0.001, max=3.0, precision=4, - unit="LENGTH") - finger_tolerance: bpy.props.FloatProperty(name="Finger play room", default=0.00005, min=0, max=0.003, precision=4, - unit="LENGTH") - finger_amount: bpy.props.IntProperty(name="Finger Amount", default=1, min=0, max=100) - stem_size: bpy.props.IntProperty(name="size of the stem", default=2, min=1, max=200) - width: bpy.props.FloatProperty(name="Width", default=0.100, min=0.005, max=3.0, precision=4, - unit="LENGTH") - height: bpy.props.FloatProperty(name="height or thickness", default=0.025, min=0.005, max=3.0, precision=4, - unit="LENGTH") - - angle: bpy.props.FloatProperty(name="angle A", default=math.pi/4, min=-10, max=10, subtype="ANGLE", - unit="ROTATION") - angleb: bpy.props.FloatProperty(name="angle B", default=math.pi/4, min=-10, max=10, subtype="ANGLE", - unit="ROTATION") - - radius: bpy.props.FloatProperty(name="Arc Radius", default=0.025, min=0.005, max=5, precision=4, - unit="LENGTH") - - interlock_type: EnumProperty(name='Type of shape', - items=(('JOINT', 'Joint', 'Puzzle Joint interlock'), - ('BAR', 'Bar', 'Bar interlock'), - ('ARC', 'Arc', 'Arc interlock'), - ('MULTIANGLE', 'Multi angle', 'Multi angle joint'), - ('CURVEBAR', 'Arc Bar', 'Arc Bar interlock'), - ('CURVEBARCURVE', 'Arc Bar Arc', 'Arc Bar Arc interlock'), - ('CURVET', 'T curve', 'T curve interlock'), - ('T', 'T Bar', 'T Bar interlock'), - ('CORNER', 'Corner Bar', 'Corner Bar interlock'), - ('TILE', 'Tile', 'Tile interlock'), - ('OPENCURVE', 'Open Curve', 'Corner Bar interlock')), - description='Type of interlock', - default='CURVET') - gender: EnumProperty(name='Type gender', - items=(('MF', 'Male-Receptacle', 'Male and receptacle'), - ('F', 'Receptacle only', 'Receptacle'), - ('M', 'Male only', 'Male')), - description='Type of interlock', - default='MF') - base_gender: EnumProperty(name='Base gender', - items=(('MF', 'Male - Receptacle', 'Male - Receptacle'), - ('F', 'Receptacle', 'Receptacle'), - ('M', 'Male', 'Male')), - description='Type of interlock', - default='M') - multiangle_gender: EnumProperty(name='Multiangle gender', - items=(('MMF', 'Male Male Receptacle', 'M M F'), - ('MFF', 'Male Receptacle Receptacle', 'M F F')), - description='Type of interlock', - default='MFF') - - mitre: bpy.props.BoolProperty(name="Add Mitres", default=False) - - twist_lock: bpy.props.BoolProperty(name="Add TwistLock", default=False) - twist_thick: bpy.props.FloatProperty(name="Twist Thickness", default=0.0047, min=0.001, max=3.0, precision=4, - unit="LENGTH") - twist_percent: bpy.props.FloatProperty( - name="Twist neck", default=0.3, min=0.1, max=0.9, precision=4) - twist_keep: bpy.props.BoolProperty(name="keep Twist holes", default=False) - twist_line: bpy.props.BoolProperty(name="Add Twist to bar", default=False) - twist_line_amount: bpy.props.IntProperty(name="amount of separators", default=2, min=1, max=600) - twist_separator: bpy.props.BoolProperty(name="Add Twist separator", default=False) - twist_separator_amount: bpy.props.IntProperty( - name="amount of separators", default=2, min=2, max=600) - twist_separator_spacing: bpy.props.FloatProperty(name="Separator spacing", default=0.025, min=-0.004, max=1.0, - precision=4, unit="LENGTH") - twist_separator_edge_distance: bpy.props.FloatProperty(name="Separator edge distance", default=0.01, min=0.0005, - max=0.1, precision=4, unit="LENGTH") - tile_x_amount: bpy.props.IntProperty(name="amount of x fingers", default=2, min=1, max=600) - tile_y_amount: bpy.props.IntProperty(name="amount of y fingers", default=2, min=1, max=600) - interlock_amount: bpy.props.IntProperty( - name="Interlock amount on curve", default=2, min=0, max=200) - overcut: bpy.props.BoolProperty(name="Add overcut", default=False) - overcut_diameter: bpy.props.FloatProperty(name="Overcut toool Diameter", default=0.003175, min=-0.001, max=0.5, - precision=4, unit="LENGTH") + diameter: FloatProperty( + name="tool diameter", + default=0.003175, + min=0.001, + max=3.0, + precision=4, + unit="LENGTH", + ) + finger_tolerance: FloatProperty( + name="Finger play room", + default=0.00005, + min=0, + max=0.003, + precision=4, + unit="LENGTH", + ) + finger_amount: IntProperty( + name="Finger Amount", + default=1, + min=0, + max=100, + ) + stem_size: IntProperty( + name="size of the stem", + default=2, + min=1, + max=200, + ) + width: FloatProperty( + name="Width", + default=0.100, + min=0.005, + max=3.0, + precision=4, + unit="LENGTH", + ) + height: FloatProperty( + name="height or thickness", + default=0.025, + min=0.005, + max=3.0, + precision=4, + unit="LENGTH", + ) + + angle: FloatProperty( + name="angle A", + default=math.pi/4, + min=-10, + max=10, + subtype="ANGLE", + unit="ROTATION", + ) + angleb: FloatProperty( + name="angle B", + default=math.pi/4, + min=-10, + max=10, + subtype="ANGLE", + unit="ROTATION", + ) + + radius: FloatProperty( + name="Arc Radius", + default=0.025, + min=0.005, + max=5, + precision=4, + unit="LENGTH", + ) + + interlock_type: EnumProperty( + name='Type of shape', + items=( + ('JOINT', 'Joint', 'Puzzle Joint interlock'), + ('BAR', 'Bar', 'Bar interlock'), + ('ARC', 'Arc', 'Arc interlock'), + ('MULTIANGLE', 'Multi angle', 'Multi angle joint'), + ('CURVEBAR', 'Arc Bar', 'Arc Bar interlock'), + ('CURVEBARCURVE', 'Arc Bar Arc', 'Arc Bar Arc interlock'), + ('CURVET', 'T curve', 'T curve interlock'), + ('T', 'T Bar', 'T Bar interlock'), + ('CORNER', 'Corner Bar', 'Corner Bar interlock'), + ('TILE', 'Tile', 'Tile interlock'), + ('OPENCURVE', 'Open Curve', 'Corner Bar interlock') + ), + description='Type of interlock', + default='CURVET', + ) + gender: EnumProperty( + name='Type gender', + items=( + ('MF', 'Male-Receptacle', 'Male and receptacle'), + ('F', 'Receptacle only', 'Receptacle'), + ('M', 'Male only', 'Male') + ), + description='Type of interlock', + default='MF', + ) + base_gender: EnumProperty( + name='Base gender', + items=( + ('MF', 'Male - Receptacle', 'Male - Receptacle'), + ('F', 'Receptacle', 'Receptacle'), + ('M', 'Male', 'Male') + ), + description='Type of interlock', + default='M', + ) + multiangle_gender: EnumProperty( + name='Multiangle gender', + items=( + ('MMF', 'Male Male Receptacle', 'M M F'), + ('MFF', 'Male Receptacle Receptacle', 'M F F') + ), + description='Type of interlock', + default='MFF', + ) + + mitre: BoolProperty( + name="Add Mitres", + default=False, + ) + + twist_lock: BoolProperty( + name="Add TwistLock", + default=False, + ) + twist_thick: FloatProperty( + name="Twist Thickness", + default=0.0047, + min=0.001, + max=3.0, + precision=4, + unit="LENGTH", + ) + twist_percent: FloatProperty( + name="Twist neck", + default=0.3, + min=0.1, + max=0.9, + precision=4, + ) + twist_keep: BoolProperty( + name="keep Twist holes", + default=False, + ) + twist_line: BoolProperty( + name="Add Twist to bar", + default=False, + ) + twist_line_amount: IntProperty( + name="amount of separators", + default=2, + min=1, + max=600, + ) + twist_separator: BoolProperty( + name="Add Twist separator", + default=False, + ) + twist_separator_amount: IntProperty( + name="amount of separators", + default=2, + min=2, + max=600, + ) + twist_separator_spacing: FloatProperty( + name="Separator spacing", + default=0.025, + min=-0.004, + max=1.0, + precision=4, + unit="LENGTH", + ) + twist_separator_edge_distance: FloatProperty( + name="Separator edge distance", + default=0.01, + min=0.0005, + max=0.1, + precision=4, + unit="LENGTH", + ) + tile_x_amount: IntProperty( + name="amount of x fingers", + default=2, + min=1, + max=600, + ) + tile_y_amount: IntProperty( + name="amount of y fingers", + default=2, + min=1, + max=600, + ) + interlock_amount: IntProperty( + name="Interlock amount on curve", + default=2, + min=0, + max=200, + ) + overcut: BoolProperty( + name="Add overcut", + default=False, + ) + overcut_diameter: FloatProperty( + name="Overcut toool Diameter", + default=0.003175, + min=-0.001, + max=0.5, + precision=4, + unit="LENGTH", + ) def draw(self, context): layout = self.layout @@ -852,7 +1306,7 @@ def draw(self, context): if self.interlock_type == 'BAR': layout.prop(self, 'mitre') - if self.interlock_type in ["ARC", "CURVEBARCURVE", "CURVEBAR", "MULTIANGLE", 'CURVET'] \ + if self.interlock_type in ["ARC", "CURVEBARCURVE", "CURVEBAR", "MULTIANGLE", 'CURVET'] \ or (self.interlock_type == 'BAR' and self.mitre): if self.interlock_type == 'MULTIANGLE': layout.prop(self, 'multiangle_gender') @@ -878,7 +1332,8 @@ def draw(self, context): def execute(self, context): curve_detected = False - print(len(context.selected_objects), "selected object", context.selected_objects) + print(len(context.selected_objects), + "selected object", context.selected_objects) if len(context.selected_objects) > 0 and context.active_object.type == 'CURVE': curve_detected = True # bpy.context.object.data.resolution_u = 60 @@ -892,11 +1347,13 @@ def execute(self, context): for v in obj.data.vertices: # extract X,Y coordinates from the vertices data coords.append((v.co.x, v.co.y)) simple.remove_multiple('_tmp') - line = LineString(coords) # convert coordinates to shapely LineString datastructure + # convert coordinates to shapely LineString datastructure + line = LineString(coords) simple.remove_multiple("_") if self.interlock_type == 'FINGER': - puzzle_joinery.finger(self.diameter, self.finger_tolerance, stem=self.stem_size) + puzzle_joinery.finger( + self.diameter, self.finger_tolerance, stem=self.stem_size) simple.rename('_puzzle', 'receptacle') puzzle_joinery.finger(self.diameter, 0, stem=self.stem_size) simple.rename('_puzzle', 'finger') @@ -995,34 +1452,97 @@ class CamCurveGear(bpy.types.Operator): bl_label = "Gears" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - tooth_spacing: bpy.props.FloatProperty(name="distance per tooth", default=0.010, min=0.001, max=1.0, precision=4, - unit="LENGTH") - tooth_amount: bpy.props.IntProperty(name="Amount of teeth", default=7, min=4) - - spoke_amount: bpy.props.IntProperty(name="Amount of spokes", default=4, min=0) - - hole_diameter: bpy.props.FloatProperty(name="Hole diameter", default=0.003175, min=0, max=3.0, precision=4, - unit="LENGTH") - rim_size: bpy.props.FloatProperty(name="Rim size", default=0.003175, min=0, max=3.0, precision=4, - unit="LENGTH") - hub_diameter: bpy.props.FloatProperty(name="Hub diameter", default=0.005, min=0, max=3.0, precision=4, - unit="LENGTH") - pressure_angle: bpy.props.FloatProperty(name="Pressure Angle", default=math.radians(20), min=0.001, max=math.pi/2, - precision=4, - subtype="ANGLE", - unit="ROTATION") - clearance: bpy.props.FloatProperty(name="Clearance", default=0.00, min=0, max=0.1, precision=4, - unit="LENGTH") - backlash: bpy.props.FloatProperty(name="Backlash", default=0.0, min=0.0, max=0.1, precision=4, - unit="LENGTH") - rack_height: bpy.props.FloatProperty(name="Rack Height", default=0.012, min=0.001, max=1, precision=4, - unit="LENGTH") - rack_tooth_per_hole: bpy.props.IntProperty(name="teeth per mounting hole", default=7, min=2) - gear_type: EnumProperty(name='Type of gear', - items=(('PINION', 'Pinion', 'circular gear'), - ('RACK', 'Rack', 'Straight Rack')), - description='Type of gear', - default='PINION') + tooth_spacing: FloatProperty( + name="distance per tooth", + default=0.010, + min=0.001, + max=1.0, + precision=4, + unit="LENGTH", + ) + tooth_amount: IntProperty( + name="Amount of teeth", + default=7, + min=4, + ) + + spoke_amount: IntProperty( + name="Amount of spokes", + default=4, + min=0, + ) + + hole_diameter: FloatProperty( + name="Hole diameter", + default=0.003175, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + rim_size: FloatProperty( + name="Rim size", + default=0.003175, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + hub_diameter: FloatProperty( + name="Hub diameter", + default=0.005, + min=0, + max=3.0, + precision=4, + unit="LENGTH", + ) + pressure_angle: FloatProperty( + name="Pressure Angle", + default=math.radians(20), + min=0.001, + max=math.pi/2, + precision=4, + subtype="ANGLE", + unit="ROTATION", + ) + clearance: FloatProperty( + name="Clearance", + default=0.00, + min=0, + max=0.1, + precision=4, + unit="LENGTH", + ) + backlash: FloatProperty( + name="Backlash", + default=0.0, + min=0.0, + max=0.1, + precision=4, + unit="LENGTH", + ) + rack_height: FloatProperty( + name="Rack Height", + default=0.012, + min=0.001, + max=1, + precision=4, + unit="LENGTH", + ) + rack_tooth_per_hole: IntProperty( + name="teeth per mounting hole", + default=7, + min=2, + ) + gear_type: EnumProperty( + name='Type of gear', + items=( + ('PINION', 'Pinion', 'circular gear'), + ('RACK', 'Rack', 'Straight Rack') + ), + description='Type of gear', + default='PINION', + ) def draw(self, context): layout = self.layout diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index 2c3935cb8..c30b20c16 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -35,36 +35,111 @@ class CamSineCurve(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO', 'PRESET'} # zstring: StringProperty(name="Z equation", description="Equation for z=F(u,v)", default="0.05*sin(2*pi*4*t)" ) - axis: bpy.props.EnumProperty(name="displacement axis", items=( - ('XY', 'Y to displace X axis', 'Y constant; X sine displacement'), - ('YX', 'X to displace Y axis', 'X constant; Y sine displacement'), - ('ZX', 'X to displace Z axis', 'X constant; Y sine displacement'), - ('ZY', 'Y to displace Z axis', 'X constant; Y sine displacement')), default='ZX') - wave: bpy.props.EnumProperty(name="Wave", items=( - ('sine', 'Sine Wave', 'Sine Wave'), - ('triangle', 'Triangle Wave', 'triangle wave'), - ('cycloid', 'Cycloid', 'Sine wave rectification'), - ('invcycloid', 'Inverse Cycloid', 'Sine wave rectification')), default='sine') - amplitude: bpy.props.FloatProperty( - name="Amplitude", default=.01, min=0, max=10, precision=4, unit="LENGTH") - period: bpy.props.FloatProperty(name="Period", default=.5, - min=0.001, max=100, precision=4, unit="LENGTH") - beatperiod: bpy.props.FloatProperty(name="Beat Period offset", default=0.0, min=0.0, max=100, precision=4, - unit="LENGTH") - shift: bpy.props.FloatProperty(name="phase shift", default=0, - min=-360, max=360, precision=4, unit="ROTATION") - offset: bpy.props.FloatProperty(name="offset", default=0, min=- - 1.0, max=1, precision=4, unit="LENGTH") - iteration: bpy.props.IntProperty(name="iteration", default=100, min=50, max=2000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, - min=-3.0, max=3, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, - min=-3.0, max=3, precision=4, unit="LENGTH") - wave_distance: bpy.props.FloatProperty(name="distance between multiple waves", default=0.0, min=0.0, max=100, - precision=4, unit="LENGTH") - wave_angle_offset: bpy.props.FloatProperty(name="angle offset for multiple waves", default=math.pi/2, - min=-200*math.pi, max=200*math.pi, precision=4, unit="ROTATION") - wave_amount: bpy.props.IntProperty(name="amount of multiple waves", default=1, min=1, max=2000) + axis: EnumProperty( + name="displacement axis", + items=( + ('XY', 'Y to displace X axis', 'Y constant; X sine displacement'), + ('YX', 'X to displace Y axis', 'X constant; Y sine displacement'), + ('ZX', 'X to displace Z axis', 'X constant; Y sine displacement'), + ('ZY', 'Y to displace Z axis', 'X constant; Y sine displacement') + ), + default='ZX', + ) + wave: EnumProperty( + name="Wave", + items=( + ('sine', 'Sine Wave', 'Sine Wave'), + ('triangle', 'Triangle Wave', 'triangle wave'), + ('cycloid', 'Cycloid', 'Sine wave rectification'), + ('invcycloid', 'Inverse Cycloid', 'Sine wave rectification') + ), + default='sine', + ) + amplitude: FloatProperty( + name="Amplitude", + default=.01, + min=0, + max=10, + precision=4, + unit="LENGTH", + ) + period: FloatProperty( + name="Period", + default=.5, + min=0.001, + max=100, + precision=4, + unit="LENGTH", + ) + beatperiod: FloatProperty( + name="Beat Period offset", + default=0.0, + min=0.0, + max=100, + precision=4, + unit="LENGTH", + ) + shift: FloatProperty( + name="phase shift", + default=0, + min=-360, + max=360, + precision=4, + unit="ROTATION", + ) + offset: FloatProperty( + name="offset", + default=0, + min=- + 1.0, + max=1, + precision=4, + unit="LENGTH", + ) + iteration: IntProperty( + name="iteration", + default=100, + min=50, + max=2000, + ) + maxt: FloatProperty( + name="Wave ends at x", + default=0.5, + min=-3.0, + max=3, + precision=4, + unit="LENGTH", + ) + mint: FloatProperty( + name="Wave starts at x", + default=0, + min=-3.0, + max=3, + precision=4, + unit="LENGTH", + ) + wave_distance: FloatProperty( + name="distance between multiple waves", + default=0.0, + min=0.0, + max=100, + precision=4, + unit="LENGTH", + ) + wave_angle_offset: FloatProperty( + name="angle offset for multiple waves", + default=math.pi/2, + min=-200*math.pi, + max=200*math.pi, + precision=4, + unit="ROTATION", + ) + wave_amount: IntProperty( + name="amount of multiple waves", + default=1, + min=1, + max=2000, + ) def execute(self, context): @@ -79,7 +154,8 @@ def execute(self, context): zstring = str(round(self.offset, 6)) + \ "+(" + str(triangle(80, self.period, self.amplitude))+")" if self.beatperiod != 0: - zstring += '+' + str(triangle(80, self.period+self.beatperiod, self.amplitude)) + zstring += '+' + \ + str(triangle(80, self.period+self.beatperiod, self.amplitude)) elif self.wave == 'cycloid': zstring = "abs("+ssine(self.amplitude, self.period, dc_offset=self.offset, phase_shift=self.shift)+")" @@ -116,39 +192,109 @@ class CamLissajousCurve(bpy.types.Operator): bl_label = "Create Lissajous figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - amplitude_A: bpy.props.FloatProperty( - name="Amplitude A", default=.1, min=0, max=100, precision=4, unit="LENGTH") - waveA: bpy.props.EnumProperty(name="Wave X", items=( - ('sine', 'Sine Wave', 'Sine Wave'), - ('triangle', 'Triangle Wave', 'triangle wave')), default='sine') - - amplitude_B: bpy.props.FloatProperty( - name="Amplitude B", default=.1, min=0, max=100, precision=4, unit="LENGTH") - waveB: bpy.props.EnumProperty(name="Wave Y", items=( - ('sine', 'Sine Wave', 'Sine Wave'), - ('triangle', 'Triangle Wave', 'triangle wave')), default='sine') - period_A: bpy.props.FloatProperty(name="Period A", default=1.1, - min=0.001, max=100, precision=4, unit="LENGTH") - period_B: bpy.props.FloatProperty(name="Period B", default=1.0, - min=0.001, max=100, precision=4, unit="LENGTH") - period_Z: bpy.props.FloatProperty(name="Period Z", default=1.0, - min=0.001, max=100, precision=4, unit="LENGTH") - amplitude_Z: bpy.props.FloatProperty( - name="Amplitude Z", default=0.0, min=0, max=100, precision=4, unit="LENGTH") - shift: bpy.props.FloatProperty(name="phase shift", default=0, - min=-360, max=360, precision=4, unit="ROTATION") - - iteration: bpy.props.IntProperty(name="iteration", default=500, min=50, max=10000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=11, - min=-3.0, max=1000000, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, - min=-10.0, max=3, precision=4, unit="LENGTH") + amplitude_A: FloatProperty( + name="Amplitude A", + default=.1, + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + waveA: EnumProperty( + name="Wave X", + items=( + ('sine', 'Sine Wave', 'Sine Wave'), + ('triangle', 'Triangle Wave', 'triangle wave') + ), + default='sine', + ) + + amplitude_B: FloatProperty( + name="Amplitude B", + default=.1, + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + waveB: EnumProperty( + name="Wave Y", + items=( + ('sine', 'Sine Wave', 'Sine Wave'), + ('triangle', 'Triangle Wave', 'triangle wave') + ), + default='sine', + ) + period_A: FloatProperty( + name="Period A", + default=1.1, + min=0.001, + max=100, + precision=4, + unit="LENGTH", + ) + period_B: FloatProperty( + name="Period B", + default=1.0, + min=0.001, + max=100, + precision=4, + unit="LENGTH", + ) + period_Z: FloatProperty( + name="Period Z", + default=1.0, + min=0.001, + max=100, + precision=4, + unit="LENGTH", + ) + amplitude_Z: FloatProperty( + name="Amplitude Z", + default=0.0, + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + shift: FloatProperty( + name="phase shift", + default=0, + min=-360, + max=360, + precision=4, + unit="ROTATION", + ) + + iteration: IntProperty( + name="iteration", + default=500, + min=50, + max=10000, + ) + maxt: FloatProperty( + name="Wave ends at x", + default=11, + min=-3.0, + max=1000000, + precision=4, + unit="LENGTH", + ) + mint: FloatProperty( + name="Wave starts at x", + default=0, + min=-10.0, + max=3, + precision=4, + unit="LENGTH", + ) def execute(self, context): # x=Asin(at+delta ),y=Bsin(bt) if self.waveA == 'sine': - xstring = ssine(self.amplitude_A, self.period_A, phase_shift=self.shift) + xstring = ssine(self.amplitude_A, self.period_A, + phase_shift=self.shift) elif self.waveA == 'triangle': xstring = str(triangle(100, self.period_A, self.amplitude_A)) @@ -183,16 +329,44 @@ class CamHypotrochoidCurve(bpy.types.Operator): bl_label = "Create Spirograph type figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - typecurve: bpy.props.EnumProperty(name="type of curve", items=( - ('hypo', 'Hypotrochoid', 'inside ring'), ('epi', 'Epicycloid', 'outside inner ring'))) - R: bpy.props.FloatProperty(name="Big circle radius", default=0.25, - min=0.001, max=100, precision=4, unit="LENGTH") - r: bpy.props.FloatProperty(name="Small circle radius", default=0.18, min=0.0001, max=100, precision=4, - unit="LENGTH") - d: bpy.props.FloatProperty(name="distance from center of interior circle", default=0.050, min=0, max=100, - precision=4, unit="LENGTH") - dip: bpy.props.FloatProperty(name="variable depth from center", - default=0.00, min=-100, max=100, precision=4) + typecurve: EnumProperty( + name="type of curve", + items=( + ('hypo', 'Hypotrochoid', 'inside ring'), + ('epi', 'Epicycloid', 'outside inner ring') + ), + ) + R: FloatProperty( + name="Big circle radius", + default=0.25, + min=0.001, + max=100, + precision=4, + unit="LENGTH", + ) + r: FloatProperty( + name="Small circle radius", + default=0.18, + min=0.0001, + max=100, + precision=4, + unit="LENGTH", + ) + d: FloatProperty( + name="distance from center of interior circle", + default=0.050, + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + dip: FloatProperty( + name="variable depth from center", + default=0.00, + min=-100, + max=100, + precision=4, + ) def execute(self, context): r = round(self.r, 6) @@ -202,14 +376,19 @@ def execute(self, context): Rpr = round(R + r, 6) # R +r Rpror = round(Rpr / r, 6) # (R+r)/r Rmror = round(Rmr / r, 6) # (R-r)/r - maxangle = 2 * math.pi * ((np.lcm(round(self.R * 1000), round(self.r * 1000)) / (R * 1000))) + maxangle = 2 * math.pi * \ + ((np.lcm(round(self.R * 1000), round(self.r * 1000)) / (R * 1000))) if self.typecurve == "hypo": - xstring = str(Rmr) + "*cos(t)+" + str(d) + "*cos(" + str(Rmror) + "*t)" - ystring = str(Rmr) + "*sin(t)-" + str(d) + "*sin(" + str(Rmror) + "*t)" + xstring = str(Rmr) + "*cos(t)+" + str(d) + \ + "*cos(" + str(Rmror) + "*t)" + ystring = str(Rmr) + "*sin(t)-" + str(d) + \ + "*sin(" + str(Rmror) + "*t)" else: - xstring = str(Rpr) + "*cos(t)-" + str(d) + "*cos(" + str(Rpror) + "*t)" - ystring = str(Rpr) + "*sin(t)-" + str(d) + "*sin(" + str(Rpror) + "*t)" + xstring = str(Rpr) + "*cos(t)-" + str(d) + \ + "*cos(" + str(Rpror) + "*t)" + ystring = str(Rpr) + "*sin(t)-" + str(d) + \ + "*sin(" + str(Rpror) + "*t)" zstring = '(' + str(round(self.dip, 6)) + \ '*(sqrt(((' + xstring + ')**2)+((' + ystring + ')**2))))' @@ -244,16 +423,44 @@ class CamCustomCurve(bpy.types.Operator): bl_label = "Create custom curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - xstring: StringProperty(name="X equation", description="Equation x=F(t)", default="t") - ystring: StringProperty(name="Y equation", description="Equation y=F(t)", default="0") - zstring: StringProperty(name="Z equation", description="Equation z=F(t)", - default="0.05*sin(2*pi*4*t)") - - iteration: bpy.props.IntProperty(name="iteration", default=100, min=50, max=2000) - maxt: bpy.props.FloatProperty(name="Wave ends at x", default=0.5, - min=-3.0, max=10, precision=4, unit="LENGTH") - mint: bpy.props.FloatProperty(name="Wave starts at x", default=0, - min=-3.0, max=3, precision=4, unit="LENGTH") + xstring: StringProperty( + name="X equation", + description="Equation x=F(t)", + default="t", + ) + ystring: StringProperty( + name="Y equation", + description="Equation y=F(t)", + default="0", + ) + zstring: StringProperty( + name="Z equation", + description="Equation z=F(t)", + default="0.05*sin(2*pi*4*t)", + ) + + iteration: IntProperty( + name="iteration", + default=100, + min=50, + max=2000, + ) + maxt: FloatProperty( + name="Wave ends at x", + default=0.5, + min=-3.0, + max=10, + precision=4, + unit="LENGTH", + ) + mint: FloatProperty( + name="Wave starts at x", + default=0, + min=-3.0, + max=3, + precision=4, + unit="LENGTH", + ) def execute(self, context): print("x= " + self.xstring) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 582b421dd..0f97ecbfd 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -43,11 +43,16 @@ class CamCurveBoolean(bpy.types.Operator): bl_label = "Curve Boolean" bl_options = {'REGISTER', 'UNDO'} - boolean_type: bpy.props.EnumProperty(name='type', - items=(('UNION', 'Union', ''), ('DIFFERENCE', 'Difference', ''), - ('INTERSECT', 'Intersect', '')), - description='boolean type', - default='UNION') + boolean_type: EnumProperty( + name='type', + items=( + ('UNION', 'Union', ''), + ('DIFFERENCE', 'Difference', ''), + ('INTERSECT', 'Intersect', '') + ), + description='boolean type', + default='UNION' + ) @classmethod def poll(cls, context): @@ -84,20 +89,62 @@ class CamCurveIntarsion(bpy.types.Operator): bl_label = "Intarsion" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - diameter: bpy.props.FloatProperty(name="cutter diameter", default=.001, min=0, max=0.025, precision=4, - unit="LENGTH") - tolerance: bpy.props.FloatProperty(name="cutout Tolerance", default=.0001, min=0, max=0.005, precision=4, - unit="LENGTH") - backlight: bpy.props.FloatProperty(name="Backlight seat", default=0.000, min=0, max=0.010, precision=4, - unit="LENGTH") - perimeter_cut: bpy.props.FloatProperty(name="Perimeter cut offset", default=0.000, min=0, max=0.100, precision=4, - unit="LENGTH") - base_thickness: bpy.props.FloatProperty(name="Base material thickness", default=0.000, min=0, max=0.100, - precision=4, unit="LENGTH") - intarsion_thickness: bpy.props.FloatProperty(name="Intarsion material thickness", default=0.000, min=0, max=0.100, - precision=4, unit="LENGTH") - backlight_depth_from_top: bpy.props.FloatProperty(name="Backlight well depth", default=0.000, min=0, max=0.100, - precision=4, unit="LENGTH") + diameter: FloatProperty( + name="cutter diameter", + default=.001, + min=0, + max=0.025, + precision=4, + unit="LENGTH", + ) + tolerance: FloatProperty( + name="cutout Tolerance", + default=.0001, + min=0, + max=0.005, + precision=4, + unit="LENGTH", + ) + backlight: FloatProperty( + name="Backlight seat", + default=0.000, + min=0, + max=0.010, + precision=4, + unit="LENGTH", + ) + perimeter_cut: FloatProperty( + name="Perimeter cut offset", + default=0.000, + min=0, + max=0.100, + precision=4, + unit="LENGTH", + ) + base_thickness: FloatProperty( + name="Base material thickness", + default=0.000, + min=0, + max=0.100, + precision=4, + unit="LENGTH", + ) + intarsion_thickness: FloatProperty( + name="Intarsion material thickness", + default=0.000, + min=0, + max=0.100, + precision=4, + unit="LENGTH", + ) + backlight_depth_from_top: FloatProperty( + name="Backlight well depth", + default=0.000, + min=0, + max=0.100, + precision=4, + unit="LENGTH", + ) @classmethod def poll(cls, context): @@ -125,7 +172,8 @@ def execute(self, context): o1.select_set(True) o2.select_set(True) o3.select_set(False) - bpy.ops.object.delete(use_global=False) # delete o1 and o2 temporary working curves + # delete o1 and o2 temporary working curves + bpy.ops.object.delete(use_global=False) o3.name = "intarsion_pocket" # this is the pocket for intarsion bpy.context.object.location[2] = -self.intarsion_thickness @@ -146,7 +194,8 @@ def execute(self, context): o4.select_set(False) if self.backlight > 0.0: # Make a smaller curve for backlighting purposes - utils.silhoueteOffset(context, (-self.tolerance / 2) - self.backlight) + utils.silhoueteOffset( + context, (-self.tolerance / 2) - self.backlight) bpy.context.active_object.name = "intarsion_backlight" bpy.context.object.location[2] = - \ self.backlight_depth_from_top - self.intarsion_thickness @@ -162,12 +211,31 @@ class CamCurveOvercuts(bpy.types.Operator): bl_label = "Add Overcuts" bl_options = {'REGISTER', 'UNDO'} - diameter: bpy.props.FloatProperty( - name="diameter", default=.003175, min=0, max=100, precision=4, unit="LENGTH") - threshold: bpy.props.FloatProperty(name="threshold", default=math.pi / 2 * .99, min=-3.14, max=3.14, precision=4, - subtype="ANGLE", unit="ROTATION") - do_outer: bpy.props.BoolProperty(name="Outer polygons", default=True) - invert: bpy.props.BoolProperty(name="Invert", default=False) + diameter: FloatProperty( + name="diameter", + default=.003175, + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + threshold: FloatProperty( + name="threshold", + default=math.pi / 2 * .99, + min=-3.14, + max=3.14, + precision=4, + subtype="ANGLE", + unit="ROTATION", + ) + do_outer: BoolProperty( + name="Outer polygons", + default=True, + ) + invert: BoolProperty( + name="Invert", + default=False, + ) @classmethod def poll(cls, context): @@ -198,9 +266,11 @@ def execute(self, context): if i2 == len(c.coords): i2 = 0 - v1 = mathutils.Vector(co) - mathutils.Vector(c.coords[i1]) + v1 = mathutils.Vector( + co) - mathutils.Vector(c.coords[i1]) v1 = v1.xy # Vector((v1.x,v1.y,0)) - v2 = mathutils.Vector(c.coords[i2]) - mathutils.Vector(co) + v2 = mathutils.Vector( + c.coords[i2]) - mathutils.Vector(co) v2 = v2.xy # v2 = Vector((v2.x,v2.y,0)) if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) @@ -217,12 +287,14 @@ def execute(self, context): p = p - v * diameter / 2 if abs(a) < math.pi / 2: shape = utils.Circle(diameter / 2, 64) - shape = shapely.affinity.translate(shape, p.x, p.y) + shape = shapely.affinity.translate( + shape, p.x, p.y) else: l = math.tan(a / 2) * diameter / 2 p1 = p - sign * v * l l = shapely.geometry.LineString((p, p1)) - shape = l.buffer(diameter / 2, resolution=64) + shape = l.buffer( + diameter / 2, resolution=64) if sign > 0: negative_overcuts.append(shape) @@ -247,25 +319,52 @@ class CamCurveOvercutsB(bpy.types.Operator): bl_label = "Add Overcuts-B" bl_options = {'REGISTER', 'UNDO'} - diameter: bpy.props.FloatProperty(name="Tool diameter", default=.003175, - description='Tool bit diameter used in cut operation', min=0, max=100, - precision=4, unit="LENGTH") - style: bpy.props.EnumProperty( + diameter: FloatProperty( + name="Tool diameter", + default=.003175, + description='Tool bit diameter used in cut operation', + min=0, + max=100, + precision=4, + unit="LENGTH", + ) + style: EnumProperty( name="style", - items=(('OPEDGE', 'opposite edge', 'place corner overcuts on opposite edges'), - ('DOGBONE', 'Dog-bone / Corner Point', 'place overcuts at center of corners'), - ('TBONE', 'T-bone', 'place corner overcuts on the same edge')), + items=( + ('OPEDGE', 'opposite edge', + 'place corner overcuts on opposite edges'), + ('DOGBONE', 'Dog-bone / Corner Point', + 'place overcuts at center of corners'), + ('TBONE', 'T-bone', 'place corner overcuts on the same edge') + ), default='DOGBONE', - description='style of overcut to use') - threshold: bpy.props.FloatProperty(name="Max Inside Angle", default=math.pi / 2, min=-3.14, max=3.14, - description='The maximum angle to be considered as an inside corner', - precision=4, subtype="ANGLE", unit="ROTATION") - do_outer: bpy.props.BoolProperty(name="Include outer curve", - description='Include the outer curve if there are curves inside', default=True) - do_invert: bpy.props.BoolProperty(name="Invert", description='invert overcut operation on all curves', - default=True) - otherEdge: bpy.props.BoolProperty(name="other edge", - description='change to the other edge for the overcut to be on', default=False) + description='style of overcut to use', + ) + threshold: FloatProperty( + name="Max Inside Angle", + default=math.pi / 2, + min=-3.14, + max=3.14, + description='The maximum angle to be considered as an inside corner', + precision=4, + subtype="ANGLE", + unit="ROTATION", + ) + do_outer: BoolProperty( + name="Include outer curve", + description='Include the outer curve if there are curves inside', + default=True, + ) + do_invert: BoolProperty( + name="Invert", + description='invert overcut operation on all curves', + default=True, + ) + otherEdge: BoolProperty( + name="other edge", + description='change to the other edge for the overcut to be on', + default=False, + ) @classmethod def poll(cls, context): @@ -345,7 +444,8 @@ def getCornerDelta(curidx, nextidx): return delta for s in shapes.geoms: - s = shapely.geometry.polygon.orient(s, 1) # ensure the shape is counterclockwise + # ensure the shape is counterclockwise + s = shapely.geometry.polygon.orient(s, 1) if s.boundary.geom_type == 'LineString': from shapely import MultiLineString @@ -368,8 +468,10 @@ def getCornerDelta(curidx, nextidx): if i2 == len(c.coords): i2 = 0 - v1 = mathutils.Vector(co).xy - mathutils.Vector(c.coords[i1]).xy - v2 = mathutils.Vector(c.coords[i2]).xy - mathutils.Vector(co).xy + v1 = mathutils.Vector( + co).xy - mathutils.Vector(c.coords[i1]).xy + v2 = mathutils.Vector( + c.coords[i2]).xy - mathutils.Vector(co).xy if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) @@ -400,7 +502,8 @@ def getCornerDelta(curidx, nextidx): if isTBone: cornerCnt += 1 # insideCorner tuplet: (pos, v1, v2, angle, corner index) - insideCorners.append((pos, v1, v2, a, cornerCnt - 1)) + insideCorners.append( + (pos, v1, v2, a, cornerCnt - 1)) # processing of corners for T-Bone are done after all points are processed continue @@ -416,7 +519,8 @@ def getCornerDelta(curidx, nextidx): # check if t-bone processing required # if no inside corners then nothing to do if isTBone and len(insideCorners) > 0: - print("corner count", cornerCnt, "inside corner count", len(insideCorners)) + print("corner count", cornerCnt, + "inside corner count", len(insideCorners)) # process all of the inside corners for i, corner in enumerate(insideCorners): pos, v1, v2, a, idx = corner @@ -427,7 +531,8 @@ def getCornerDelta(curidx, nextidx): print('first:', i, idx, prevCorner[IDX]) if getCornerDelta(prevCorner[IDX], idx) == 1: # make sure there is an outside corner - print(getCornerDelta(getCorner(i, -2)[IDX], idx)) + print(getCornerDelta( + getCorner(i, -2)[IDX], idx)) if getCornerDelta(getCorner(i, -2)[IDX], idx) > 2: setOtherEdge(v1, v2, a) print('first won') @@ -437,7 +542,8 @@ def getCornerDelta(curidx, nextidx): print('second:', i, idx, nextCorner[IDX]) if getCornerDelta(idx, nextCorner[IDX]) == 1: # make sure there is an outside corner - print(getCornerDelta(idx, getCorner(i, 2)[IDX])) + print(getCornerDelta( + idx, getCorner(i, 2)[IDX])) if getCornerDelta(idx, getCorner(i, 2)[IDX]) > 2: print('second won') setOtherEdge(-v2, -v1, a) @@ -446,7 +552,8 @@ def getCornerDelta(curidx, nextidx): print('third') if getCornerDelta(prevCorner[IDX], idx) == 3: # check if they share the same edge - a1 = v1.angle_signed(prevCorner[V2]) * 180.0 / math.pi + a1 = v1.angle_signed( + prevCorner[V2]) * 180.0 / math.pi print('third won', a1) if a1 < -135 or a1 > 135: setOtherEdge(-v2, -v1, a) @@ -455,7 +562,8 @@ def getCornerDelta(curidx, nextidx): print('fourth') if getCornerDelta(idx, nextCorner[IDX]) == 3: # check if they share the same edge - a1 = v2.angle_signed(nextCorner[V1]) * 180.0 / math.pi + a1 = v2.angle_signed( + nextCorner[V1]) * 180.0 / math.pi print('fourth won', a1) if a1 < -135 or a1 > 135: setOtherEdge(v1, v2, a) @@ -513,13 +621,25 @@ class CamMeshGetPockets(bpy.types.Operator): bl_label = "Get pocket surfaces" bl_options = {'REGISTER', 'UNDO'} - threshold: bpy.props.FloatProperty(name="horizontal threshold", - description="How horizontal the surface must be for a pocket: " - "1.0 perfectly flat, 0.0 is any orientation", - default=.99, min=0, max=1.0, precision=4) - zlimit: bpy.props.FloatProperty(name="z limit", - description="maximum z height considered for pocket operation, default is 0.0", - default=0.0, min=-1000.0, max=1000.0, precision=4, unit='LENGTH') + threshold: FloatProperty( + name="horizontal threshold", + description="How horizontal the surface must be for a pocket: " + "1.0 perfectly flat, 0.0 is any orientation", + default=.99, + min=0, + max=1.0, + precision=4, + ) + zlimit: FloatProperty( + name="z limit", + description="maximum z height considered for pocket operation, " + "default is 0.0", + default=0.0, + min=-1000.0, + max=1000.0, + precision=4, + unit='LENGTH', + ) @classmethod def poll(cls, context): @@ -535,7 +655,8 @@ def execute(self, context): mw = ob.matrix_world mesh = ob.data bpy.ops.object.editmode_toggle() - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='FACE') bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.editmode_toggle() i = 0 @@ -572,11 +693,13 @@ def execute(self, context): bpy.ops.object.editmode_toggle() - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='EDGE') bpy.ops.mesh.region_to_loop() bpy.ops.mesh.separate(type='SELECTED') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='FACE') bpy.ops.object.editmode_toggle() ao.select_set(state=False) bpy.context.view_layer.objects.active = bpy.context.selected_objects[0] @@ -609,13 +732,34 @@ class CamOffsetSilhouete(bpy.types.Operator): bl_label = "Silhouete offset" bl_options = {'REGISTER', 'UNDO', 'PRESET'} - offset: bpy.props.FloatProperty(name="offset", default=.003, - min=-100, max=100, precision=4, unit="LENGTH") - mitrelimit: bpy.props.FloatProperty( - name="Mitre Limit", default=.003, min=0.0, max=20, precision=4, unit="LENGTH") - style: bpy.props.EnumProperty(name="type of curve", items=( - ('1', 'Round', ''), ('2', 'Mitre', ''), ('3', 'Bevel', ''))) - opencurve: bpy.props.BoolProperty(name="Dialate open curve", default=False) + offset: FloatProperty( + name="offset", + default=.003, + min=-100, + max=100, + precision=4, + unit="LENGTH", + ) + mitrelimit: FloatProperty( + name="Mitre Limit", + default=.003, + min=0.0, + max=20, + precision=4, + unit="LENGTH", + ) + style: EnumProperty( + name="type of curve", + items=( + ('1', 'Round', ''), + ('2', 'Mitre', ''), + ('3', 'Bevel', '') + ), + ) + opencurve: BoolProperty( + name="Dialate open curve", + default=False, + ) @classmethod def poll(cls, context): @@ -623,7 +767,8 @@ def poll(cls, context): context.active_object.type == 'CURVE' or context.active_object.type == 'FONT' or context.active_object.type == 'MESH') - def execute(self, context): # this is almost same as getobjectoutline, just without the need of operation data + # this is almost same as getobjectoutline, just without the need of operation data + def execute(self, context): bpy.ops.object.curve_remove_doubles() ob = context.active_object if self.opencurve and ob.type == 'CURVE': @@ -642,14 +787,16 @@ def execute(self, context): # this is almost same as getobjectoutline, just wit simple.remove_multiple('temp_mesh') # delete temporary mesh simple.remove_multiple('dilation') # delete old dilation objects - line = LineString(coords) # convert coordinates to shapely LineString datastructure + # convert coordinates to shapely LineString datastructure + line = LineString(coords) print("line length=", round(line.length * 1000), 'mm') dilated = line.buffer(self.offset, cap_style=1, resolution=16, mitre_limit=self.mitrelimit) # use shapely to expand polygon_utils_cam.shapelyToCurve("dilation", dilated, 0) else: - utils.silhoueteOffset(context, self.offset, int(self.style), self.mitrelimit) + utils.silhoueteOffset(context, self.offset, + int(self.style), self.mitrelimit) return {'FINISHED'} @@ -668,13 +815,16 @@ def poll(cls, context): context.active_object.type == 'FONT' or context.active_object.type == 'MESH') - def execute(self, context): # this is almost same as getobjectoutline, just without the need of operation data + # this is almost same as getobjectoutline, just without the need of operation data + def execute(self, context): ob = bpy.context.active_object - self.silh = utils.getObjectSilhouete('OBJECTS', objects=bpy.context.selected_objects) + self.silh = utils.getObjectSilhouete( + 'OBJECTS', objects=bpy.context.selected_objects) bpy.context.scene.cursor.location = (0, 0, 0) # smp=sgeometry.asMultiPolygon(self.silh) for smp in self.silh: - polygon_utils_cam.shapelyToCurve(ob.name + '_silhouette', smp, 0) # + polygon_utils_cam.shapelyToCurve( + ob.name + '_silhouette', smp, 0) # # bpy.ops.object.convert(target='CURVE') bpy.context.scene.cursor.location = ob.location bpy.ops.object.origin_set(type='ORIGIN_CURSOR') diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 53e54151a..4f4c80422 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -87,7 +87,8 @@ def timer_update(context): update_zbufferimage_tag = False update_offsetimage_tag = False else: - readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True) + readthread = threading.Thread( + target=threadread, args=([tcom]), daemon=True) readthread.start() p[0] = readthread o = s.cam_operations[tcom.opname] # changes @@ -118,7 +119,8 @@ def execute(self, context): bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE) tcom = threadCom(o, proc) - readthread = threading.Thread(target=threadread, args=([tcom]), daemon=True) + readthread = threading.Thread( + target=threadread, args=([tcom]), daemon=True) readthread.start() # self.__class__.cam_processes=[] if not hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'): @@ -258,7 +260,8 @@ def execute(self, context): def draw(self, context): layout = self.layout - layout.prop_search(self, "operation", bpy.context.scene, "cam_operations") + layout.prop_search(self, "operation", + bpy.context.scene, "cam_operations") class CamPackObjects(bpy.types.Operator): @@ -325,11 +328,13 @@ async def execute_async(self, context): meshes = [] try: for i in range(0, len(chainops)): - s.cam_active_operation = s.cam_operations.find(chainops[i].name) + s.cam_active_operation = s.cam_operations.find( + chainops[i].name) self.report({'INFO'}, f"Calculating path: {chainops[i].name}") result, success = await _calc_path(self, context) if not success and 'FINISHED' in result: - self.report({'ERROR'}, f"Couldn't calculate path: {chainops[i].name}") + self.report( + {'ERROR'}, f"Couldn't calculate path: {chainops[i].name}") except Exception as e: print("FAIL", e) traceback.print_tb(e.__traceback__) @@ -397,8 +402,11 @@ class CAMSimulate(bpy.types.Operator, AsyncOperatorMixin): bl_label = "CAM simulation" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} - operation: StringProperty(name="Operation", - description="Specify the operation to calculate", default='Operation') + operation: StringProperty( + name="Operation", + description="Specify the operation to calculate", + default='Operation', + ) async def execute_async(self, context): s = bpy.context.scene @@ -418,7 +426,8 @@ async def execute_async(self, context): def draw(self, context): layout = self.layout - layout.prop_search(self, "operation", bpy.context.scene, "cam_operations") + layout.prop_search(self, "operation", + bpy.context.scene, "cam_operations") class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin): @@ -434,8 +443,11 @@ def poll(cls, context): chain = s.cam_chains[s.cam_active_chain] return cam.isChainValid(chain, context)[0] - operation: StringProperty(name="Operation", - description="Specify the operation to calculate", default='Operation') + operation: StringProperty( + name="Operation", + description="Specify the operation to calculate", + default='Operation', + ) async def execute_async(self, context): s = bpy.context.scene @@ -459,7 +471,8 @@ async def execute_async(self, context): def draw(self, context): layout = self.layout - layout.prop_search(self, "operation", bpy.context.scene, "cam_operations") + layout.prop_search(self, "operation", + bpy.context.scene, "cam_operations") class CamChainAdd(bpy.types.Operator): @@ -643,7 +656,8 @@ def execute(self, context): ob = bpy.context.active_object if ob is None: - self.report({'ERROR_INVALID_INPUT'}, "Please add an object to base the operation on.") + self.report({'ERROR_INVALID_INPUT'}, + "Please add an object to base the operation on.") return {'CANCELLED'} minx, miny, minz, maxx, maxy, maxz = utils.getBoundsWorldspace([ob]) @@ -704,7 +718,8 @@ def execute(self, context): numdigits += 1 isdigit = o.name[-numdigits].isdigit() numdigits -= 1 - o.name = o.name[:-numdigits] + str(int(o.name[-numdigits:]) + 1).zfill(numdigits) + o.name = o.name[:-numdigits] + \ + str(int(o.name[-numdigits:]) + 1).zfill(numdigits) o.filename = o.name else: o.name = o.name + '_copy' @@ -754,9 +769,15 @@ class CamOperationMove(bpy.types.Operator): bl_label = "Move CAM operation in list" bl_options = {'REGISTER', 'UNDO'} - direction: EnumProperty(name='direction', - items=(('UP', 'Up', ''), ('DOWN', 'Down', '')), - description='direction', default='DOWN') + direction: EnumProperty( + name='direction', + items=( + ('UP', 'Up', ''), + ('DOWN', 'Down', '') + ), + description='direction', + default='DOWN', + ) @classmethod def poll(cls, context): @@ -800,7 +821,8 @@ def execute(self, context): oriob.empty_draw_size = 0.02 # 2 cm simple.addToGroup(oriob, gname) - oriob.name = 'ori_' + o.name + '.' + str(len(bpy.data.collections[gname].objects)).zfill(3) + oriob.name = 'ori_' + o.name + '.' + \ + str(len(bpy.data.collections[gname].objects)).zfill(3) return {'FINISHED'} diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index b59f6a731..5e0730366 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -1,4 +1,6 @@ import bpy +from bpy.props import StringProperty +from bpy.props import FloatProperty from cam.simple import strInUnits from cam.ui_panels.buttons_panel import CAMButtonsPanel @@ -12,21 +14,25 @@ class CAM_INFO_Properties(bpy.types.PropertyGroup): - warnings: bpy.props.StringProperty( + warnings: StringProperty( name='warnings', description='warnings', default='', - update=cam.utils.update_operation) + update=cam.utils.update_operation, + ) - chipload: bpy.props.FloatProperty( + chipload: FloatProperty( name="chipload", description="Calculated chipload", default=0.0, unit='LENGTH', - precision=cam.constants.CHIPLOAD_PRECISION) + precision=cam.constants.CHIPLOAD_PRECISION, + ) - duration: bpy.props.FloatProperty( + duration: FloatProperty( name="Estimated time", default=0.01, min=0.0000, max=cam.constants.MAX_OPERATION_TIME, - precision=cam.constants.PRECISION, unit="TIME") + precision=cam.constants.PRECISION, + unit="TIME", + ) class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -48,7 +54,8 @@ class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel): def draw_blendercam_version(self): if not self.has_correct_level(): return - self.layout.label(text=f'Blendercam version: {".".join([str(x) for x in cam_version])}') + self.layout.label( + text=f'Blendercam version: {".".join([str(x) for x in cam_version])}') if len(bpy.context.preferences.addons['cam'].preferences.new_version_available) > 0: self.layout.label(text=f"New version available:") self.layout.label( diff --git a/scripts/addons/cam/ui_panels/interface.py b/scripts/addons/cam/ui_panels/interface.py index 56bccaf7a..d4e1f9e94 100644 --- a/scripts/addons/cam/ui_panels/interface.py +++ b/scripts/addons/cam/ui_panels/interface.py @@ -1,5 +1,6 @@ - import bpy +from bpy.props import EnumProperty + import math from cam.ui_panels.buttons_panel import CAMButtonsPanel import cam.utils @@ -13,7 +14,7 @@ def update_interface(self, context): class CAM_INTERFACE_Properties(bpy.types.PropertyGroup): - level: bpy.props.EnumProperty( + level: EnumProperty( name="Interface", description="Choose visible options", items=[('0', "Basic", "Only show essential options"), @@ -21,7 +22,7 @@ class CAM_INTERFACE_Properties(bpy.types.PropertyGroup): ('2', "Complete", "Show all options"), ('3', "Experimental", "Show experimental options")], default='0', - update=update_interface + update=update_interface, ) diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 9400afafe..3bb9f0555 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -1,5 +1,9 @@ - import bpy +from bpy.props import BoolProperty +from bpy.props import EnumProperty +from bpy.props import FloatProperty +from bpy.props import FloatVectorProperty + from cam.ui_panels.buttons_panel import CAMButtonsPanel import cam.utils import cam.constants @@ -7,53 +11,61 @@ class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): - estimate_from_model: bpy.props.BoolProperty( + estimate_from_model: BoolProperty( name="Estimate cut area from model", description="Estimate cut area based on model geometry", default=True, - update=cam.utils.update_material + update=cam.utils.update_material, ) - radius_around_model: bpy.props.FloatProperty( + radius_around_model: FloatProperty( name='Radius around model', - description="Increase cut area around the model on X and Y by this amount", + description="Increase cut area around the model on X and " + "Y by this amount", default=0.0, unit='LENGTH', precision=cam.constants.PRECISION, - update=cam.utils.update_material + update=cam.utils.update_material, ) - center_x: bpy.props.BoolProperty( + center_x: BoolProperty( name="Center on X axis", description="Position model centered on X", - default=False, update=cam.utils.update_material + default=False, update=cam.utils.update_material, ) - center_y: bpy.props.BoolProperty( + center_y: BoolProperty( name="Center on Y axis", description="Position model centered on Y", - default=False, update=cam.utils.update_material + default=False, update=cam.utils.update_material, ) - z_position: bpy.props.EnumProperty( + z_position: EnumProperty( name="Z placement", items=( ('ABOVE', 'Above', 'Place object vertically above the XY plane'), ('BELOW', 'Below', 'Place object vertically below the XY plane'), - ('CENTERED', 'Centered', 'Place object vertically centered on the XY plane')), - description="Position below Zero", default='BELOW', - update=cam.utils.update_material + ('CENTERED', 'Centered', + 'Place object vertically centered on the XY plane') + ), + description="Position below Zero", + default='BELOW', + update=cam.utils.update_material, ) # material_origin - origin: bpy.props.FloatVectorProperty( + origin: FloatVectorProperty( name='Material origin', default=(0, 0, 0), unit='LENGTH', precision=cam.constants.PRECISION, subtype="XYZ", - update=cam.utils.update_material + update=cam.utils.update_material, ) # material_size - size: bpy.props.FloatVectorProperty( - name='Material size', default=(0.200, 0.200, 0.100), min=0, unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", - update=cam.utils.update_material + size: FloatVectorProperty( + name='Material size', + default=(0.200, 0.200, 0.100), + min=0, + unit='LENGTH', + precision=cam.constants.PRECISION, + subtype="XYZ", + update=cam.utils.update_material, ) @@ -78,7 +90,8 @@ def execute(self, context): def draw(self, context): if not self.interface_level <= int(self.context.scene.interface.level): return - self.layout.prop_search(self, "operation", bpy.context.scene, "cam_operations") + self.layout.prop_search( + self, "operation", bpy.context.scene, "cam_operations") class CAM_MATERIAL_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -106,7 +119,8 @@ def draw_estimate_from_object(self): if self.op.material.estimate_from_model: row_radius = self.layout.row() row_radius.label(text="Additional radius") - row_radius.prop(self.op.material, 'radius_around_model', text='') + row_radius.prop(self.op.material, + 'radius_around_model', text='') else: self.layout.prop(self.op.material, 'origin') self.layout.prop(self.op.material, 'size') @@ -120,7 +134,8 @@ def draw_axis_alignment(self): row_axis.prop(self.op.material, 'center_x') row_axis.prop(self.op.material, 'center_y') self.layout.prop(self.op.material, 'z_position') - self.layout.operator("object.material_cam_position", text="Position object") + self.layout.operator( + "object.material_cam_position", text="Position object") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index fbb96f73d..df32b6f04 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -1,5 +1,8 @@ - import bpy +from bpy.props import BoolProperty +from bpy.props import EnumProperty +from bpy.props import FloatProperty + import math from cam.ui_panels.buttons_panel import CAMButtonsPanel import cam.utils @@ -8,99 +11,196 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): # movement parallel_step_back - type: bpy.props.EnumProperty(name='Movement type', items=( - ('CONVENTIONAL', 'Conventional / Up milling', - 'cutter rotates against the direction of the feed'), - ('CLIMB', 'Climb / Down milling', - 'cutter rotates with the direction of the feed'), - ('MEANDER', 'Meander / Zig Zag', - 'cutting is done both with and against the rotation of the spindle')), - description='movement type', default='CLIMB', update=cam.utils.update_operation) - - insideout: bpy.props.EnumProperty(name='Direction', - items=(('INSIDEOUT', 'Inside out', 'a'), - ('OUTSIDEIN', 'Outside in', 'a')), - description='approach to the piece', default='INSIDEOUT', - update=cam.utils.update_operation) - - spindle_rotation: bpy.props.EnumProperty(name='Spindle rotation', - items=(('CW', 'Clock wise', 'a'), - ('CCW', 'Counter clock wise', 'a')), - description='Spindle rotation direction', default='CW', - update=cam.utils.update_operation) - - free_height: bpy.props.FloatProperty(name="Free movement height", - default=0.01, min=0.0000, max=32, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) - - useG64: bpy.props.BoolProperty(name="G64 trajectory", - description='Use only if your machine supports G64 code. LinuxCNC and Mach3 do', - default=False, update=cam.utils.update_operation) - - G64: bpy.props.FloatProperty(name="Path Control Mode with Optional Tolerance", - default=0.0001, min=0.0000, max=0.005, - precision=cam.constants.PRECISION, unit="LENGTH", update=cam.utils.update_operation) - - parallel_step_back: bpy.props.BoolProperty(name="Parallel step back", - description='For roughing and finishing in one pass: mills material in climb mode, then steps back and goes between 2 last chunks back', - default=False, update=cam.utils.update_operation) - - helix_enter: bpy.props.BoolProperty(name="Helix enter - EXPERIMENTAL", - description="Enter material in helix", - default=False, update=cam.utils.update_operation) - - ramp_in_angle: bpy.props.FloatProperty(name="Ramp in angle", default=math.pi / 6, - min=0, max=math.pi * 0.4999, - precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) - - helix_diameter: bpy.props.FloatProperty(name='Helix diameter - % of cutter diameter', - default=90, min=10, max=100, - precision=1, subtype='PERCENTAGE', update=cam.utils.update_operation) - - ramp: bpy.props.BoolProperty(name="Ramp in - EXPERIMENTAL", - description="Ramps down the whole contour, so the cutline looks like helix", - default=False, update=cam.utils.update_operation) - - ramp_out: bpy.props.BoolProperty(name="Ramp out - EXPERIMENTAL", - description="Ramp out to not leave mark on surface", default=False, - update=cam.utils.update_operation) - - ramp_out_angle: bpy.props.FloatProperty(name="Ramp out angle", - default=math.pi / 6, min=0, max=math.pi * 0.4999, - precision=1, subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) - - retract_tangential: bpy.props.BoolProperty(name="Retract tangential - EXPERIMENTAL", - description="Retract from material in circular motion", default=False, - update=cam.utils.update_operation) - - retract_radius: bpy.props.FloatProperty(name='Retract arc radius', - default=0.001, min=0.000001, max=100, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) - - retract_height: bpy.props.FloatProperty(name='Retract arc height', - default=0.001, min=0.00000, max=100, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) - - stay_low: bpy.props.BoolProperty(name="Stay low if possible", - default=True, update=cam.utils.update_operation) - - merge_dist: bpy.props.FloatProperty(name="Merge distance - EXPERIMENTAL", - default=0.0, min=0.0000, max=0.1, - precision=cam.constants.PRECISION, unit="LENGTH", - update=cam.utils.update_operation) - - protect_vertical: bpy.props.BoolProperty(name="Protect vertical", - description="The path goes only vertically next to steep areas", - default=True, - update=cam.utils.update_operation) - - protect_vertical_limit: bpy.props.FloatProperty(name="Verticality limit", - description="What angle is allready considered vertical", - default=math.pi / 45, min=0, max=math.pi * 0.5, precision=0, - subtype="ANGLE", unit="ROTATION", update=cam.utils.update_operation) + type: EnumProperty( + name='Movement type', + items=( + ('CONVENTIONAL', 'Conventional / Up milling', + 'cutter rotates against the direction of the feed'), + ('CLIMB', 'Climb / Down milling', + 'cutter rotates with the direction of the feed'), + ('MEANDER', 'Meander / Zig Zag', + 'cutting is done both with and against the ' + 'rotation of the spindle') + ), + description='movement type', + default='CLIMB', + update=cam.utils.update_operation, + ) + + insideout: EnumProperty( + name='Direction', + items=( + ('INSIDEOUT', 'Inside out', 'a'), + ('OUTSIDEIN', 'Outside in', 'a') + ), + description='approach to the piece', + default='INSIDEOUT', + update=cam.utils.update_operation, + ) + + spindle_rotation: EnumProperty( + name='Spindle rotation', + items=( + ('CW', 'Clock wise', 'a'), + ('CCW', 'Counter clock wise', 'a') + ), + description='Spindle rotation direction', + default='CW', + update=cam.utils.update_operation, + ) + + free_height: FloatProperty( + name="Free movement height", + default=0.01, + min=0.0000, + max=32, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) + + useG64: BoolProperty( + name="G64 trajectory", + description='Use only if your machine supports ' + 'G64 code. LinuxCNC and Mach3 do', + default=False, + update=cam.utils.update_operation, + ) + + G64: FloatProperty( + name="Path Control Mode with Optional Tolerance", + default=0.0001, + min=0.0000, + max=0.005, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) + + parallel_step_back: BoolProperty( + name="Parallel step back", + description='For roughing and finishing in one pass: mills ' + 'material in climb mode, then steps back and goes ' + 'between 2 last chunks back', + default=False, + update=cam.utils.update_operation, + ) + + helix_enter: BoolProperty( + name="Helix enter - EXPERIMENTAL", + description="Enter material in helix", + default=False, + update=cam.utils.update_operation, + ) + + ramp_in_angle: FloatProperty( + name="Ramp in angle", + default=math.pi / 6, + min=0, + max=math.pi * 0.4999, + precision=1, + subtype="ANGLE", + unit="ROTATION", + update=cam.utils.update_operation, + ) + + helix_diameter: FloatProperty( + name='Helix diameter - % of cutter diameter', + default=90, + min=10, + max=100, + precision=1, + subtype='PERCENTAGE', + update=cam.utils.update_operation, + ) + + ramp: BoolProperty( + name="Ramp in - EXPERIMENTAL", + description="Ramps down the whole contour, so the cutline looks " + "like helix", + default=False, + update=cam.utils.update_operation, + ) + + ramp_out: BoolProperty( + name="Ramp out - EXPERIMENTAL", + description="Ramp out to not leave mark on surface", + default=False, + update=cam.utils.update_operation, + ) + + ramp_out_angle: FloatProperty( + name="Ramp out angle", + default=math.pi / 6, + min=0, + max=math.pi * 0.4999, + precision=1, + subtype="ANGLE", + unit="ROTATION", + update=cam.utils.update_operation, + ) + + retract_tangential: BoolProperty( + name="Retract tangential - EXPERIMENTAL", + description="Retract from material in circular motion", + default=False, + update=cam.utils.update_operation, + ) + + retract_radius: FloatProperty( + name='Retract arc radius', + default=0.001, + min=0.000001, + max=100, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) + + retract_height: FloatProperty( + name='Retract arc height', + default=0.001, + min=0.00000, + max=100, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) + + stay_low: BoolProperty( + name="Stay low if possible", + default=True, + update=cam.utils.update_operation, + ) + + merge_dist: FloatProperty( + name="Merge distance - EXPERIMENTAL", + default=0.0, + min=0.0000, + max=0.1, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) + + protect_vertical: BoolProperty( + name="Protect vertical", + description="The path goes only vertically next to steep areas", + default=True, + update=cam.utils.update_operation, + ) + + protect_vertical_limit: FloatProperty( + name="Verticality limit", + description="What angle is allready considered vertical", + default=math.pi / 45, + min=0, + max=math.pi * 0.5, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=cam.utils.update_operation, + ) class CAM_MOVEMENT_Panel(CAMButtonsPanel, bpy.types.Panel): diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index 3c5d8cccd..298fbc825 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -1,4 +1,8 @@ import bpy +from bpy.props import BoolProperty +from bpy.props import FloatProperty +from bpy.props import IntProperty + from cam.ui_panels.buttons_panel import CAMButtonsPanel import cam.utils import cam.constants @@ -6,45 +10,82 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): - optimize: bpy.props.BoolProperty( - name="Reduce path points", description="Reduce path points", default=True, - update=cam.utils.update_operation) - - optimize_threshold: bpy.props.FloatProperty( - name="Reduction threshold in μm", default=.2, min=0.000000001, - max=1000, precision=20, update=cam.utils.update_operation) - - use_exact: bpy.props.BoolProperty( + optimize: BoolProperty( + name="Reduce path points", + description="Reduce path points", + default=True, + update=cam.utils.update_operation, + ) + + optimize_threshold: FloatProperty( + name="Reduction threshold in μm", + default=.2, + min=0.000000001, + max=1000, + precision=20, + update=cam.utils.update_operation, + ) + + use_exact: BoolProperty( name="Use exact mode", - description="Exact mode allows greater precision, but is slower with complex meshes", - default=True, update=cam.utils.update_exact_mode) - - imgres_limit: bpy.props.IntProperty( - name="Maximum resolution in megapixels", default=16, min=1, max=512, - description="Limits total memory usage and prevents crashes. Increase it if you know what are doing", - update=cam.utils.update_zbuffer_image) - - pixsize: bpy.props.FloatProperty( - name="sampling raster detail", default=0.0001, min=0.00001, max=0.1, - precision=cam.constants.PRECISION, unit="LENGTH", update=cam.utils.update_zbuffer_image) - - use_opencamlib: bpy.props.BoolProperty( + description="Exact mode allows greater precision, but is slower " + "with complex meshes", + default=True, + update=cam.utils.update_exact_mode, + ) + + imgres_limit: IntProperty( + name="Maximum resolution in megapixels", + default=16, + min=1, + max=512, + description="Limits total memory usage and prevents crashes. " + "Increase it if you know what are doing", + update=cam.utils.update_zbuffer_image, + ) + + pixsize: FloatProperty( + name="sampling raster detail", + default=0.0001, + min=0.00001, + max=0.1, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_zbuffer_image, + ) + + use_opencamlib: BoolProperty( name="Use OpenCAMLib", description="Use OpenCAMLib to sample paths or get waterline shape", - default=False, update=cam.utils.update_opencamlib) + default=False, + update=cam.utils.update_opencamlib, + ) - exact_subdivide_edges: bpy.props.BoolProperty( + exact_subdivide_edges: BoolProperty( name="Auto subdivide long edges", - description="This can avoid some collision issues when importing CAD models", - default=False, update=cam.utils.update_exact_mode) - - circle_detail: bpy.props.IntProperty( - name="Detail of circles used for curve offsets", default=64, min=12, max=512, - update=cam.utils.update_operation) - - simulation_detail: bpy.props.FloatProperty( - name="Simulation sampling raster detail", default=0.0002, min=0.00001, - max=0.01, precision=cam.constants.PRECISION, unit="LENGTH", update=cam.utils.update_operation) + description="This can avoid some collision issues when " + "importing CAD models", + default=False, + update=cam.utils.update_exact_mode, + ) + + circle_detail: IntProperty( + name="Detail of circles used for curve offsets", + default=64, + min=12, + max=512, + update=cam.utils.update_operation, + ) + + simulation_detail: FloatProperty( + name="Simulation sampling raster detail", + default=0.0002, + min=0.00001, + max=0.01, + precision=cam.constants.PRECISION, + unit="LENGTH", + update=cam.utils.update_operation, + ) class CAM_OPTIMISATION_Panel(CAMButtonsPanel, bpy.types.Panel): From 50682fb8c019ec881a32ba6034885c3f30bb3b9c Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 14:55:03 -0400 Subject: [PATCH 005/100] Property format, version update Changed the formatting of Properties (e.g. bpy.props.StringProperty) throughout: Before: ``` use_position_definitions: bpy.props.BoolProperty(name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False) ``` After: ``` use_position_definitions: BoolProperty( name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False, ) ``` Added trailing commas to retain formatting if another autoformatter (e.g.: black) is implemented. Removed bpy.props. prefix from Properties that still had it, and added explicit imports when required in ui_panels module. From 4fea2d25fbd3ebd50927da6a8262fccff9ed6f2d Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 21 Mar 2024 14:56:15 -0400 Subject: [PATCH 006/100] Property format, version update Changed the formatting of Properties (e.g. bpy.props.StringProperty) throughout: Before: ``` use_position_definitions: bpy.props.BoolProperty(name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False) ``` After: ``` use_position_definitions: BoolProperty( name="Use position definitions", description="Define own positions for op start, " "toolchange, ending position", default=False, ) ``` Added trailing commas to retain formatting if another autoformatter (e.g.: black) is implemented. Removed bpy.props. prefix from Properties that still had it, and added explicit imports when required in ui_panels module. From fcc92395941971bbb6edc35e62c6833f29c5b28b Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 22 Mar 2024 12:28:19 +0000 Subject: [PATCH 007/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index ea0f6f7fd..30b9a48d0 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -62,7 +62,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 6), + "version":(1,0,5), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 714c3d665..a7e41462d 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__ = (1, 0, 4) +__version__=(1,0,5) \ No newline at end of file From d9524cf655de0eab29387ab273523bee28f834a1 Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 22 Mar 2024 12:28:37 +0000 Subject: [PATCH 008/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 30b9a48d0..a69276f3e 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -62,7 +62,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,5), + "version":(1,0,6), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index a7e41462d..24e8b33ab 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,5) \ No newline at end of file +__version__=(1,0,6) \ No newline at end of file From c0febee9688222ac0af64a59afd0df3c00222ea3 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 17:52:18 -0400 Subject: [PATCH 009/100] Import Fix, funcs moved, refs updated, ocl rename Changed all imports from absolute to relative: - instead of importing the module 'cam' now imports come from '.' the relative parent folder - importing the module into itself resulted in a circular import error, relative imports avoid this Moved Functions, refs updated: - some functions (and 2 variables) only existed on the base level of the module, in the init file, and could not otherwise be accessed, they were moved into the utils file with other similar functions - these were primarily called as update functions for Properties scattered across the addon, these have all been updated to reflect the new location and import convention opencamlib_version rename: - in 2 files there was a local variable named 'opencamlib_version' that was defined by a function called 'opencamlib_version' that returned the opencamlib_version - this seemed to confuse Blender into thinking that it was being called before it was defined, and simply changing the variable name to 'ocl_version' while keeping the function name as 'opencamlib_version' fixed the issue --- scripts/addons/cam/__init__.py | 438 ++++----------- scripts/addons/cam/autoupdate.py | 2 +- scripts/addons/cam/basrelief.py | 9 +- scripts/addons/cam/bridges.py | 41 +- scripts/addons/cam/chunk.py | 80 ++- scripts/addons/cam/collision.py | 34 +- scripts/addons/cam/curvecamcreate.py | 30 +- scripts/addons/cam/curvecamequation.py | 11 +- scripts/addons/cam/curvecamtools.py | 18 +- scripts/addons/cam/gcodepath.py | 74 +-- scripts/addons/cam/image_utils.py | 110 ++-- scripts/addons/cam/involute_gear.py | 12 +- scripts/addons/cam/joinery.py | 57 +- scripts/addons/cam/ops.py | 59 +- scripts/addons/cam/pack.py | 18 +- scripts/addons/cam/parametric.py | 103 ++-- scripts/addons/cam/pattern.py | 26 +- scripts/addons/cam/puzzle_joinery.py | 63 ++- scripts/addons/cam/simple.py | 13 +- scripts/addons/cam/simulation.py | 18 +- scripts/addons/cam/slice.py | 11 +- scripts/addons/cam/strategy.py | 113 ++-- scripts/addons/cam/testing.py | 13 +- scripts/addons/cam/ui.py | 60 +- scripts/addons/cam/ui_panels/area.py | 14 +- scripts/addons/cam/ui_panels/chains.py | 30 +- scripts/addons/cam/ui_panels/cutter.py | 17 +- scripts/addons/cam/ui_panels/feedrate.py | 2 +- scripts/addons/cam/ui_panels/gcode.py | 2 +- scripts/addons/cam/ui_panels/info.py | 39 +- scripts/addons/cam/ui_panels/interface.py | 14 +- scripts/addons/cam/ui_panels/machine.py | 8 +- scripts/addons/cam/ui_panels/material.py | 53 +- scripts/addons/cam/ui_panels/movement.py | 69 +-- scripts/addons/cam/ui_panels/op_properties.py | 14 +- scripts/addons/cam/ui_panels/operations.py | 47 +- scripts/addons/cam/ui_panels/optimisation.py | 48 +- scripts/addons/cam/ui_panels/pack.py | 2 +- scripts/addons/cam/ui_panels/slice.py | 2 +- scripts/addons/cam/utils.py | 516 ++++++++++++++---- scripts/addons/cam/voronoi.py | 33 +- 41 files changed, 1396 insertions(+), 927 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index ea0f6f7fd..f4bc44b9f 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -18,11 +18,61 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK **** +from .ui import * +from .version import __version__ +from . import ( + ui, + ops, + constants, + curvecamtools, + curvecamequation, + curvecamcreate, + utils, + simple, + polygon_utils_cam, + autoupdate, + basrelief, +) # , post_processors +from .utils import ( + updateMachine, + updateRest, + updateOperation, + updateOperationValid, + operationValid, + updateZbufferImage, + updateOffsetImage, + updateStrategy, + updateCutout, + updateChipload, + updateRotation, + update_operation, + getStrategyList, + updateBridges, +) import bl_operators import blf import bpy +from bpy.app.handlers import persistent +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, + PointerProperty, + StringProperty, + CollectionProperty, +) +from bpy.types import ( + Menu, + Operator, + UIList, + AddonPreferences, +) +from bpy_extras.object_utils import object_data_add import bpy.ops import math +from mathutils import * import numpy import os import pickle @@ -34,97 +84,32 @@ from pathlib import Path -USE_PROFILER = False - try: import shapely except ImportError: # pip install required python stuff subprocess.check_call([sys.executable, "-m", "ensurepip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) + subprocess.check_call([sys.executable, "-m", "pip", + "install", "--upgrade", " pip"]) subprocess.check_call([sys.executable, "-m", "pip", "install", "shapely", "Equation", "opencamlib"]) # install numba if available for this platform, ignore failure subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) -from cam import ui, ops, curvecamtools, curvecamequation, curvecamcreate, utils, simple, \ - polygon_utils_cam, autoupdate, basrelief # , post_processors -from mathutils import * -from shapely import geometry as sgeometry -from bpy_extras.object_utils import object_data_add -from bpy.types import Menu, Operator, UIList, AddonPreferences -from bpy.props import * -from bpy.app.handlers import persistent - -from cam.version import __version__ -from cam.ui import * +from shapely import geometry as sgeometry # noqa bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 6), + "version": (1, 0, 7), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", "warning": "", - "doc_url": "https://blendercam.com/", + "doc_url": "https://blendercom/", "tracker_url": "", "category": "Scene"} -import cam.constants - -was_hidden_dict = {} - - -def updateMachine(self, context): - global _IS_LOADING_DEFAULTS - print('update machine ') - if not _IS_LOADING_DEFAULTS: - utils.addMachineAreaObject() - - -def updateMaterial(self, context): - print('update material') - utils.addMaterialAreaObject() - - -def updateOperation(self, context): - scene = context.scene - ao = scene.cam_operations[scene.cam_active_operation] - operationValid(self, context) - - if ao.hide_all_others: - for _ao in scene.cam_operations: - if _ao.path_object_name in bpy.data.objects: - other_obj = bpy.data.objects[_ao.path_object_name] - current_obj = bpy.data.objects[ao.path_object_name] - if other_obj != current_obj: - other_obj.hide = True - other_obj.select = False - else: - for path_obj_name in was_hidden_dict: - print(was_hidden_dict) - if was_hidden_dict[path_obj_name]: - # Find object and make it hidde, then reset 'hidden' flag - obj = bpy.data.objects[path_obj_name] - obj.hide = True - obj.select = False - was_hidden_dict[path_obj_name] = False - - # try highlighting the object in the 3d view and make it active - bpy.ops.object.select_all(action='DESELECT') - # highlight the cutting path if it exists - try: - ob = bpy.data.objects[ao.path_object_name] - ob.select_set(state=True, view_layer=None) - # Show object if, it's was hidden - if ob.hide: - ob.hide = False - was_hidden_dict[ao.path_object_name] = True - bpy.context.scene.objects.active = ob - except Exception as e: - print(e) - class CamAddonPreferences(AddonPreferences): # this must match the addon name, use '__package__' @@ -250,7 +235,7 @@ class machineSettings(bpy.types.PropertyGroup): name='Start position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", update=updateMachine, ) @@ -258,7 +243,7 @@ class machineSettings(bpy.types.PropertyGroup): name='MTC position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", update=updateMachine, ) @@ -266,7 +251,7 @@ class machineSettings(bpy.types.PropertyGroup): name='End position', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", update=updateMachine, ) @@ -275,7 +260,7 @@ class machineSettings(bpy.types.PropertyGroup): name='Work Area', default=(0.500, 0.500, 0.100), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", update=updateMachine, ) @@ -284,7 +269,7 @@ class machineSettings(bpy.types.PropertyGroup): default=0.0, min=0.00001, max=320000, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit='LENGTH', ) feedrate_max: FloatProperty( @@ -292,7 +277,7 @@ class machineSettings(bpy.types.PropertyGroup): default=2, min=0.00001, max=320000, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit='LENGTH', ) feedrate_default: FloatProperty( @@ -300,7 +285,7 @@ class machineSettings(bpy.types.PropertyGroup): default=1.5, min=0.00001, max=320000, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit='LENGTH', ) hourly_rate: FloatProperty( @@ -381,7 +366,7 @@ class machineSettings(bpy.types.PropertyGroup): default=33, min=0.00001, max=320000, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) # exporter_start = StringProperty(name="exporter start", default="%") @@ -443,7 +428,7 @@ class PackObjectsSettings(bpy.types.PropertyGroup): min=0.001, max=10, default=0.5, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) sheet_y: FloatProperty( @@ -452,7 +437,7 @@ class PackObjectsSettings(bpy.types.PropertyGroup): min=0.001, max=10, default=0.5, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) distance: FloatProperty( @@ -462,7 +447,7 @@ class PackObjectsSettings(bpy.types.PropertyGroup): min=0.001, max=10, default=0.01, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) tolerance: FloatProperty( @@ -471,7 +456,7 @@ class PackObjectsSettings(bpy.types.PropertyGroup): min=0.001, max=0.02, default=0.005, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) rotate: BoolProperty( @@ -500,7 +485,7 @@ class SliceObjectsSettings(bpy.types.PropertyGroup): min=0.001, max=10, default=0.005, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) slice_above0: BoolProperty( @@ -550,209 +535,6 @@ class import_settings(bpy.types.PropertyGroup): ) -def isValid(o, context): - valid = True - if o.geometry_source == 'OBJECT': - if o.object_name not in bpy.data.objects: - valid = False - if o.geometry_source == 'COLLECTION': - if o.collection_name not in bpy.data.collections: - valid = False - elif len(bpy.data.collections[o.collection_name].objects) == 0: - valid = False - - if o.geometry_source == 'IMAGE': - if o.source_image_name not in bpy.data.images: - valid = False - return valid - - -def operationValid(self, context): - scene = context.scene - o = scene.cam_operations[scene.cam_active_operation] - o.changed = True - o.valid = isValid(o, context) - invalidmsg = "Invalid source object for operation.\n" - if o.valid: - o.info.warnings = "" - else: - o.info.warnings = invalidmsg - - if o.geometry_source == 'IMAGE': - o.optimisation.use_exact = False - o.update_offsetimage_tag = True - o.update_zbufferimage_tag = True - print('validity ') - - -def isChainValid(chain, context): - s = context.scene - if len(chain.operations) == 0: - return (False, "") - for cho in chain.operations: - found_op = None - for so in s.cam_operations: - if so.name == cho.name: - found_op = so - if found_op == None: - return (False, f"Couldn't find operation {cho.name}") - if cam.isValid(found_op, context) is False: - return (False, f"Operation {found_op.name} is not valid") - return (True, "") - - -def updateOperationValid(self, context): - updateOperation(self, context) - - -# Update functions start here -def updateChipload(self, context): - """this is very simple computation of chip size, could be very much improved""" - print('update chipload ') - o = self - # Old chipload - o.info.chipload = (o.feedrate / (o.spindle_rpm * o.cutter_flutes)) - # New chipload with chip thining compensation. - # I have tried to combine these 2 formulas to compinsate for the phenomenon of chip thinning when cutting at less - # than 50% cutter engagement with cylindrical end mills. formula 1 Nominal Chipload is - # " feedrate mm/minute = spindle rpm x chipload x cutter diameter mm x cutter_flutes " - # formula 2 (.5*(cutter diameter mm devided by dist_between_paths)) divided by square root of - # ((cutter diameter mm devided by dist_between_paths)-1) x Nominal Chipload - # Nominal Chipload = what you find in end mill data sheats recomended chip load at %50 cutter engagment. - # I am sure there is a better way to do this. I dont get consistent result and - # I am not sure if there is something wrong with the units going into the formula, my math or my lack of - # underestanding of python or programming in genereal. Hopefuly some one can have a look at this and with any luck - # we will be one tiny step on the way to a slightly better chipload calculating function. - - # self.chipload = ((0.5*(o.cutter_diameter/o.dist_between_paths))/(math.sqrt((o.feedrate*1000)/(o.spindle_rpm*o.cutter_diameter*o.cutter_flutes)*(o.cutter_diameter/o.dist_between_paths)-1))) - print(o.info.chipload) - - -def updateOffsetImage(self, context): - """refresh offset image tag for rerendering""" - updateChipload(self, context) - print('update offset') - self.changed = True - self.update_offsetimage_tag = True - - -def updateZbufferImage(self, context): - """changes tags so offset and zbuffer images get updated on calculation time.""" - # print('updatezbuf') - # print(self,context) - self.changed = True - self.update_zbufferimage_tag = True - self.update_offsetimage_tag = True - utils.getOperationSources(self) - - -def updateStrategy(o, context): - """""" - o.changed = True - print('update strategy') - if o.machine_axes == '5' or ( - o.machine_axes == '4' and o.strategy4axis == 'INDEXED'): # INDEXED 4 AXIS DOESN'T EXIST NOW... - utils.addOrientationObject(o) - else: - utils.removeOrientationObject(o) - updateExact(o, context) - - -def updateCutout(o, context): - pass - - -def updateExact(o, context): - print('update exact ') - o.changed = True - o.update_zbufferimage_tag = True - o.update_offsetimage_tag = True - if o.optimisation.use_exact: - if o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS' or o.inverse: - o.optimisation.use_opencamlib = False - print('Current operation cannot use exact mode') - else: - o.optimisation.use_opencamlib = False - - -def updateOpencamlib(o, context): - print('update opencamlib ') - o.changed = True - if o.optimisation.use_opencamlib and ( - o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS'): - o.optimisation.use_exact = False - o.optimisation.use_opencamlib = False - print('Current operation cannot use opencamlib') - - -def updateBridges(o, context): - print('update bridges ') - o.changed = True - - -def updateRotation(o, context): - print('update rotation') - if o.enable_B or o.enable_A: - print(o, o.rotation_A) - ob = bpy.data.objects[o.object_name] - ob.select_set(True) - bpy.context.view_layer.objects.active = ob - if o.A_along_x: # A parallel with X - if o.enable_A: - bpy.context.active_object.rotation_euler.x = o.rotation_A - if o.enable_B: - bpy.context.active_object.rotation_euler.y = o.rotation_B - else: # A parallel with Y - if o.enable_A: - bpy.context.active_object.rotation_euler.y = o.rotation_A - if o.enable_B: - bpy.context.active_object.rotation_euler.x = o.rotation_B - - -# def updateRest(o, context): -# print('update rest ') -# # if o.use_layers: -# o.movement.parallel_step_back = False -# o.changed = True - -def updateRest(o, context): - print('update rest ') - o.changed = True - - -# if (o.strategy == 'WATERLINE'): -# o.use_layers = True - - -def getStrategyList(scene, context): - use_experimental = bpy.context.preferences.addons['cam'].preferences.experimental - items = [ - ('CUTOUT', 'Profile(Cutout)', 'Cut the silhouete with offset'), - ('POCKET', 'Pocket', 'Pocket operation'), - ('DRILL', 'Drill', 'Drill operation'), - ('PARALLEL', 'Parallel', 'Parallel lines on any angle'), - ('CROSS', 'Cross', 'Cross paths'), - ('BLOCK', 'Block', 'Block path'), - ('SPIRAL', 'Spiral', 'Spiral path'), - ('CIRCLES', 'Circles', 'Circles path'), - ('OUTLINEFILL', 'Outline Fill', - 'Detect outline and fill it with paths as pocket. Then sample these paths on the 3d surface'), - ('CARVE', 'Project curve to surface', 'Engrave the curve path to surface'), - ('WATERLINE', 'Waterline - Roughing -below zero', - 'Waterline paths - constant z below zero'), - ('CURVE', 'Curve to Path', 'Curve object gets converted directly to path'), - ('MEDIAL_AXIS', 'Medial axis', - 'Medial axis, must be used with V or ball cutter, for engraving various width shapes with a single stroke ') - ] - # if use_experimental: - # items.extend([('MEDIAL_AXIS', 'Medial axis - EXPERIMENTAL', - # 'Medial axis, must be used with V or ball cutter, for engraving various width shapes with a single stroke ')]); - # ('PENCIL', 'Pencil - EXPERIMENTAL','Pencil operation - detects negative corners in the model and mills only those.'), - # ('CRAZY', 'Crazy path - EXPERIMENTAL', 'Crazy paths - dont even think about using this!'), - # ('PROJECTED_CURVE', 'Projected curve - EXPERIMENTAL', 'project 1 curve towards other curve')]) - return items - - class camOperation(bpy.types.PropertyGroup): material: PointerProperty( @@ -944,7 +726,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.0, max=1.0, default=0.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateOffsetImage, ) @@ -983,7 +765,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.00001, max=1.0, default=0.01, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -993,7 +775,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.00001, max=1.0, default=0.01, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1056,7 +838,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.000001, max=10, default=0.003, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateOffsetImage, ) @@ -1066,7 +848,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.000001, max=10, default=0.003, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateOffsetImage, ) @@ -1076,7 +858,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.0, max=100.0, default=25.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateOffsetImage, ) @@ -1094,7 +876,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.0, max=180.0, default=60.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateOffsetImage, ) ball_radius: FloatProperty( @@ -1104,11 +886,11 @@ class camOperation(bpy.types.PropertyGroup): max=0.035, default=0.001, unit="LENGTH", - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateOffsetImage, ) # ball_cone_flute: FloatProperty(name="BallCone Flute Length", description="length of flute", min=0.0, - # max=0.1, default=0.017, unit="LENGTH", precision=cam.constants.PRECISION, update=updateOffsetImage) + # max=0.1, default=0.017, unit="LENGTH", precision=constants.PRECISION, update=updateOffsetImage) bull_corner_radius: FloatProperty( name="Bull Corner Radius", description="Radius tool bit corner", @@ -1116,7 +898,7 @@ class camOperation(bpy.types.PropertyGroup): max=0.035, default=0.005, unit="LENGTH", - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateOffsetImage, ) @@ -1170,7 +952,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.001, min=0.00001, max=32, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1179,7 +961,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.0002, min=0.00001, max=32, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1266,7 +1048,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.001, min=-.100, max=32, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1289,7 +1071,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.001, min=0.00001, max=32, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1319,7 +1101,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.01, min=0.00001, max=32, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1329,7 +1111,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.00, max=1, default=0.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) lead_out: FloatProperty( @@ -1338,7 +1120,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.00, max=1, default=0.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", ) profile_start: IntProperty( @@ -1356,7 +1138,7 @@ class camOperation(bpy.types.PropertyGroup): default=-0.01, min=-3, max=3, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1392,7 +1174,7 @@ class camOperation(bpy.types.PropertyGroup): default=0, min=-3, max=10, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) # EXPERIMENTAL @@ -1401,7 +1183,7 @@ class camOperation(bpy.types.PropertyGroup): name="First down", description="First go down on a contour, then go to the next one", default=False, - update=cam.utils.update_operation, + update=update_operation, ) ####################################################### @@ -1413,7 +1195,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.01, min=-1, max=1, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateZbufferImage, ) @@ -1422,7 +1204,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.1, min=-10, max=10, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateZbufferImage, ) @@ -1430,7 +1212,7 @@ class camOperation(bpy.types.PropertyGroup): name='Image offset', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", update=updateZbufferImage, ) @@ -1448,7 +1230,7 @@ class camOperation(bpy.types.PropertyGroup): default=0, min=0, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype='PERCENTAGE', update=updateZbufferImage, ) @@ -1457,7 +1239,7 @@ class camOperation(bpy.types.PropertyGroup): default=0, min=0, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype='PERCENTAGE', update=updateZbufferImage, ) @@ -1466,7 +1248,7 @@ class camOperation(bpy.types.PropertyGroup): default=100, min=0, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype='PERCENTAGE', update=updateZbufferImage, ) @@ -1475,7 +1257,7 @@ class camOperation(bpy.types.PropertyGroup): default=100, min=0, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype='PERCENTAGE', update=updateZbufferImage, ) @@ -1499,7 +1281,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.0, max=100.0, default=0.01, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1530,7 +1312,7 @@ class camOperation(bpy.types.PropertyGroup): min=0.00005, max=50.0, default=1.0, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateChipload, ) @@ -1585,7 +1367,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.00002, min=0.00000001, max=1, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1595,7 +1377,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.02, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateRest, ) crazy_threshold5: FloatProperty( @@ -1603,7 +1385,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.3, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateRest, ) crazy_threshold2: FloatProperty( @@ -1611,7 +1393,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.5, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateRest, ) crazy_threshold3: FloatProperty( @@ -1619,7 +1401,7 @@ class camOperation(bpy.types.PropertyGroup): default=2, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateRest, ) crazy_threshold4: FloatProperty( @@ -1627,7 +1409,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.05, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateRest, ) # Add pocket operation to medial axis @@ -1651,7 +1433,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.001, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1660,7 +1442,7 @@ class camOperation(bpy.types.PropertyGroup): default=0.0002, min=0.00000001, max=100, - precision=cam.constants.PRECISION, + precision=constants.PRECISION, unit="LENGTH", update=updateRest, ) @@ -1677,7 +1459,7 @@ class camOperation(bpy.types.PropertyGroup): name='width of bridges', default=0.002, unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateBridges, ) bridges_height: FloatProperty( @@ -1685,7 +1467,7 @@ class camOperation(bpy.types.PropertyGroup): description="Height from the bottom of the cutting operation", default=0.0005, unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, update=updateBridges, ) bridges_collection_name: StringProperty( @@ -1712,7 +1494,7 @@ class camOperation(bpy.types.PropertyGroup): # update = updateStrategy) # # bridges_per_curve = IntProperty(name="minimum bridges per curve", description="", default=4, min=1, max=512, update = updateBridges) - # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=cam.constants.PRECISION, update = updateBridges) + # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=constants.PRECISION, update = updateBridges) use_modifiers: BoolProperty( name="use mesh modifiers", @@ -1733,14 +1515,14 @@ class camOperation(bpy.types.PropertyGroup): name='Operation minimum', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", ) max: FloatVectorProperty( name='Operation maximum', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, + precision=constants.PRECISION, subtype="XYZ", ) diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 970d8bdb3..cd522e430 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -1,5 +1,5 @@ from datetime import date -from cam.version import __version__ as current_version +from .version import __version__ as current_version from urllib.request import urlopen import json import pathlib diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index f6dc8030c..86035f4ca 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -4,7 +4,14 @@ import math import re from math import * -from bpy.props import * +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + PointerProperty, + StringProperty, +) # //////////////////////////////////////////////////////////////////// diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index dae8b3ae8..fcbc272eb 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -21,11 +21,10 @@ # here is the bridges functionality of Blender CAM. The functions here are called with operators defined from ops.py. import bpy -from bpy.props import * from bpy_extras.object_utils import AddObjectHelper, object_data_add -from cam import utils -from cam import simple +from . import utils +from . import simple import mathutils import math @@ -64,7 +63,8 @@ def addAutoBridges(o): if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is None: bridgecollectionname = 'bridges_' + o.name bpy.data.collections.new(bridgecollectionname) - bpy.context.collection.children.link(bpy.data.collections[bridgecollectionname]) + bpy.context.collection.children.link( + bpy.data.collections[bridgecollectionname]) g = bpy.data.collections[bridgecollectionname] o.bridges_collection_name = bridgecollectionname for ob in o.objects: @@ -78,12 +78,14 @@ def addAutoBridges(o): minx, miny, maxx, maxy = c.bounds d1 = c.project(sgeometry.Point(maxx + 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, -math.pi / 2, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, -math.pi / 2, + o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point(minx - 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi / 2, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, math.pi / 2, + o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, maxy + 1000)) @@ -93,7 +95,8 @@ def addAutoBridges(o): bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, miny - 1000)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, math.pi, o.bridges_width, + o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) @@ -117,7 +120,8 @@ def getBridgesPoly(o): bridgespoly = sops.unary_union(shapes) # buffer the poly, so the bridges are not actually milled... - o.bridgespolyorig = bridgespoly.buffer(distance=o.cutter_diameter / 2.0) + o.bridgespolyorig = bridgespoly.buffer( + distance=o.cutter_diameter / 2.0) o.bridgespoly_boundary = o.bridgespolyorig.boundary o.bridgespoly_boundary_prep = prepared.prep(o.bridgespolyorig.boundary) o.bridgespoly = prepared.prep(o.bridgespolyorig) @@ -149,7 +153,8 @@ def useBridges(ch, o): i1 = vi i2 = vi chp1 = ch_points[i1] - chp2 = ch_points[i1] # Vector(v1)#this is for case of last point and not closed chunk.. + # Vector(v1)#this is for case of last point and not closed chunk.. + chp2 = ch_points[i1] if vi + 1 < len(ch_points): i2 = vi + 1 chp2 = ch_points[vi + 1] # Vector(ch_points[vi+1]) @@ -178,10 +183,12 @@ def useBridges(ch, o): if not startinside: newpoints.append(chp1) elif startinside: - newpoints.append((chp1[0], chp1[1], max(chp1[2], bridgeheight))) + newpoints.append( + (chp1[0], chp1[1], max(chp1[2], bridgeheight))) cpoints = [] if itpoint: - pt = mathutils.Vector((intersections.x, intersections.y, intersections.z)) + pt = mathutils.Vector( + (intersections.x, intersections.y, intersections.z)) cpoints = [pt] elif itmpoint: @@ -245,7 +252,8 @@ def useBridges(ch, o): if isedge == 1: # This is to subdivide edges which are longer than the width of the bridge edgelength = math.hypot(x - x2, y - y2) if edgelength > o.bridges_width: - verts.append(((x + x2)/2, (y + y2)/2, o.minz)) # make new vertex + # make new vertex + verts.append(((x + x2)/2, (y + y2)/2, o.minz)) isedge += 1 edge = [count - 2, count - 1] @@ -265,11 +273,14 @@ def useBridges(ch, o): # verify if vertices have been generated and generate a mesh if verts: - mesh = bpy.data.meshes.new(name=o.name + "_cut_bridges") # generate new mesh - mesh.from_pydata(verts, edges, faces) # integrate coordinates and edges + mesh = bpy.data.meshes.new( + name=o.name + "_cut_bridges") # generate new mesh + # integrate coordinates and edges + mesh.from_pydata(verts, edges, faces) object_data_add(bpy.context, mesh) # create object bpy.ops.object.convert(target='CURVE') # convert mesh to curve - simple.join_multiple(o.name + '_cut_bridges') # join all the new cut bridges curves + # join all the new cut bridges curves + simple.join_multiple(o.name + '_cut_bridges') simple.remove_doubles() # remove overlapping vertices diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py index 5427b31cc..1102fe172 100644 --- a/scripts/addons/cam/chunk.py +++ b/scripts/addons/cam/chunk.py @@ -23,10 +23,10 @@ import shapely from shapely.geometry import polygon as spolygon from shapely import geometry as sgeometry -from cam import polygon_utils_cam -from cam.simple import * -from cam.exception import CamException -from cam.numba_wrapper import jit, prange +from . import polygon_utils_cam +from .simple import * +from .exception import CamException +from .numba_wrapper import jit, prange import math import numpy as np @@ -74,7 +74,8 @@ def __init__(self, inpoints=None, startpoints=None, endpoints=None, rotations=No self.depth = None def to_chunk(self): - chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) + chunk = camPathChunk(self.points, self.startpoints, + self.endpoints, self.rotations) if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): chunk.closed = True if self.depth is not None: @@ -101,7 +102,8 @@ def __init__(self, inpoints, startpoints=None, endpoints=None, rotations=None): self.poly = None # get polygon just in time self.simppoly = None if startpoints: - self.startpoints = startpoints # from where the sweep test begins, but also retract point for given path + # from where the sweep test begins, but also retract point for given path + self.startpoints = startpoints else: self.startpoints = [] if endpoints: @@ -162,7 +164,8 @@ def shift(self, x, y, z): def setZ(self, z, if_bigger=False): if if_bigger: - self.points[:, 2] = z if z > self.points[:, 2] else self.points[:, 2] + self.points[:, 2] = z if z > self.points[:, + 2] else self.points[:, 2] else: self.points[:, 2] = z @@ -183,7 +186,8 @@ def clampmaxZ(self, z): def dist(self, pos, o): if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 + dist_sq = (pos[0]-self.points[:, 0])**2 + \ + (pos[1]-self.points[:, 1])**2 return sqrt(np.min(dist_sq)) else: if o.movement.type == 'MEANDER': @@ -230,9 +234,11 @@ def xyDistanceTo(self, other, cutoff=0): def adaptdist(self, pos, o): # reorders chunk so that it starts at the closest point to pos. if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 + dist_sq = (pos[0]-self.points[:, 0])**2 + \ + (pos[1]-self.points[:, 1])**2 point_idx = np.argmin(dist_sq) - new_points = np.concatenate((self.points[point_idx:], self.points[:point_idx+1])) + new_points = np.concatenate( + (self.points[point_idx:], self.points[:point_idx+1])) self.points = new_points else: if o.movement.type == 'MEANDER': @@ -291,7 +297,8 @@ def reverse(self): def pop(self, index): print("WARNING: Popping from chunk is slow", self, index) - self.points = np.concatenate((self.points[0:index], self.points[index+1:]), axis=0) + self.points = np.concatenate( + (self.points[0:index], self.points[index+1:]), axis=0) if len(self.startpoints) > 0: self.startpoints.pop(index) self.endpoints.pop(index) @@ -480,7 +487,8 @@ def rampZigZag(self, zstart, zend, o): else: zigzagtraveled = 0.0 haspoints = False - ramppoints = [(self.points[0][0], self.points[0][1], self.points[0][2])] + ramppoints = [ + (self.points[0][0], self.points[0][1], self.points[0][2])] i = 1 while not haspoints: # print(i,zigzaglength,zigzagtraveled) @@ -526,10 +534,12 @@ def rampZigZag(self, zstart, zend, o): if o.movement.ramp_out: zstart = o.maxz zend = self.points[-1][2] - if zend < zstart: # again, sometimes a chunk could theoretically end above the starting level. + # again, sometimes a chunk could theoretically end above the starting level. + if zend < zstart: stepdown = zstart - zend - estlength = (zstart - zend) / tan(o.movement.ramp_out_angle) + estlength = (zstart - zend) / \ + tan(o.movement.ramp_out_angle) self.getLength() if self.length > 0: ramplength = estlength @@ -541,7 +551,8 @@ def rampZigZag(self, zstart, zend, o): ramplength = turns * self.length * 2.0 zigzaglength = self.length ramppoints = self.points.tolist() - ramppoints.reverse() # revert points here, we go the other way. + # revert points here, we go the other way. + ramppoints.reverse() else: zigzagtraveled = 0.0 @@ -556,7 +567,8 @@ def rampZigZag(self, zstart, zend, o): d = dist2d(p1, p2) zigzagtraveled += d if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): - ratio = 1 - (zigzagtraveled - zigzaglength) / d + ratio = 1 - (zigzagtraveled - + zigzaglength) / d if (i + 1 == len( self.points)): # this condition is for a rare case of # combined layers+bridges+ramps... @@ -583,7 +595,8 @@ def rampZigZag(self, zstart, zend, o): traveled += d ratio = 1 - (traveled / ramplength) znew = zstart - stepdown * ratio - chunk_points.append((p2[0], p2[1], max(p2[2], znew))) + chunk_points.append( + (p2[0], p2[1], max(p2[2], znew))) # max value here is so that it doesn't go below surface in the case of 3d paths self.points = np.array(chunk_points) @@ -593,7 +606,8 @@ def changePathStart(self, o): newstart = o.profile_start chunkamt = len(self.points) newstart = newstart % chunkamt - self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) + self.points = np.concatenate( + (self.points[newstart:], self.points[:newstart])) def breakPathForLeadinLeadout(self, o): iradius = o.lead_in @@ -796,8 +810,10 @@ def optimizeChunk(chunk, operation): # list comprehension so we don't have to do tons of appends chunk.startpoints = [chunk.startpoints[i] for i, b in enumerate(keep_points) if b == True] - chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] - chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] + chunk.endpoints = [chunk.endpoints[i] + for i, b in enumerate(keep_points) if b == True] + chunk.rotations = [chunk.rotations[i] + for i, b in enumerate(keep_points) if b == True] return chunk @@ -884,7 +900,8 @@ def parentChild(parents, children, o): child.parents.append(parent) -def chunksToShapely(chunks): # this does more cleve chunks to Poly with hierarchies... ;) +# this does more cleve chunks to Poly with hierarchies... ;) +def chunksToShapely(chunks): # print ('analyzing paths') for ch in chunks: # first convert chunk to poly if len(ch.points) > 2: @@ -909,7 +926,8 @@ def chunksToShapely(chunks): # this does more cleve chunks to Poly with hierarc for parent in ch.parents: if len(parent.parents) + 1 == len(ch.parents): - ch.nparents = [parent] # nparents serves as temporary storage for parents, + # nparents serves as temporary storage for parents, + ch.nparents = [parent] # not to get mixed with the first parenting during the check found = True break @@ -1002,7 +1020,8 @@ def chunksToShapely(chunks): # this does more cleve chunks to Poly with hierarc # print('starting and ending points too close, removing ending point') ch.parents[0].points = np.array(newPoints) - ch.parents[0].poly = sgeometry.Polygon(ch.parents[0].points) + ch.parents[0].poly = sgeometry.Polygon( + ch.parents[0].points) ch.parents[0].poly = ch.parents[0].poly.difference( ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) @@ -1047,7 +1066,8 @@ def meshFromCurveToChunk(object): # print('itis') chunk.closed = True - chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) + chunk.points.append( + (mesh.vertices[lastvi].co + object.location).to_tuple()) # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z lastvi = vi + 1 chunk = chunk.to_chunk() @@ -1129,9 +1149,12 @@ def meshFromCurve(o, use_modifiers=False): bpy.data.meshes.remove(oldmesh) try: - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply( + location=False, rotation=True, scale=False) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) except: pass @@ -1235,7 +1258,8 @@ def chunksRefineThreshold(chunks, distance, limitdistance): newchunk.append((p.x, p.y, p.z)) i += 1 - vref = v * distance * i # because of the condition, so it doesn't run again. + # because of the condition, so it doesn't run again. + vref = v * distance * i while i > 0: vref = v * distance * i if vref.length < d: diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 5fc5a5cd8..6d0aa2330 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -22,8 +22,8 @@ import bpy import time -from cam import simple -from cam.simple import * +from . import simple +from .simple import * BULLET_SCALE = 10000 @@ -50,7 +50,8 @@ def getCutterBullet(o): rotation=(0, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -64,7 +65,8 @@ def getCutterBullet(o): location=CUTTER_OFFSET, rotation=(0, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -83,7 +85,8 @@ def getCutterBullet(o): rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -100,7 +103,8 @@ def getCutterBullet(o): location=CUTTER_OFFSET, rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -125,7 +129,8 @@ def getCutterBullet(o): bpy.ops.object.editmode_toggle() bpy.ops.object.convert(target='MESH') bpy.ops.transform.rotate(value=-math.pi / 2, orient_axis='X') - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=True) ob = bpy.context.active_object ob.name = "BallConeTool" ob_scr = ob.modifiers.new(type='SCREW', name='scr') @@ -137,7 +142,8 @@ def getCutterBullet(o): bpy.data.objects['BallConeTool'].select_set(True) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter.location = CUTTER_OFFSET @@ -151,7 +157,8 @@ def getCutterBullet(o): cutter = bpy.context.active_object scale = o.cutter_diameter / cutob.dimensions.x cutter.scale *= BULLET_SCALE * scale - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') # print(cutter.dimensions,scale) @@ -189,7 +196,8 @@ def subdivideLongEdges(ob, threshold): # bpy.ops.mesh.tris_convert_to_quads() bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='EDGE') bpy.ops.object.editmode_toggle() for i in subdivides: m.edges[i].select = True @@ -262,7 +270,8 @@ def prepareBulletCollision(o): use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, texture_space=False, release_confirm=False) collisionob.location = collisionob.location * BULLET_SCALE - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=True) bpy.context.view_layer.objects.active = collisionob if active_collection in collisionob.users_collection: active_collection.objects.unlink(collisionob) @@ -327,7 +336,8 @@ def getSampleBulletNAxis(cutter, startpoint, endpoint, rotation, cutter_compensa cutterVec.rotate(Euler(rotation)) start = (startpoint * BULLET_SCALE + cutterVec).to_tuple() end = (endpoint * BULLET_SCALE + cutterVec).to_tuple() - pos = bpy.context.scene.rigidbody_world.convex_sweep_test(cutter, start, end) + pos = bpy.context.scene.rigidbody_world.convex_sweep_test( + cutter, start, end) if pos[3] == 1: pos = Vector(pos[0]) diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index d7d8586ff..1c2f82645 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -21,13 +21,35 @@ import bpy -from bpy.props import * +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, +) from bpy.types import Operator from bpy_extras.io_utils import ImportHelper -from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, parametric, joinery, \ - curvecamtools, puzzle_joinery, involute_gear +from . import ( + utils, + pack, + polygon_utils_cam, + simple, + gcodepath, + bridges, + parametric, + joinery, + curvecamtools, + puzzle_joinery, + involute_gear +) import shapely -from shapely.geometry import Point, LineString, Polygon, MultiLineString, MultiPoint +from shapely.geometry import ( + Point, + LineString, + Polygon, + MultiLineString, + MultiPoint +) import mathutils import math from Equation import Expression diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index c30b20c16..1819ffbb9 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -20,9 +20,14 @@ # ***** END GPL LICENCE BLOCK ***** import bpy -from bpy.props import * - -from cam import utils, parametric +from bpy.props import ( + EnumProperty, + FloatProperty, + IntProperty, + StringProperty, +) + +from . import utils, parametric import math from Equation import Expression import numpy as np diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 0f97ecbfd..f18fef6e3 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -23,11 +23,25 @@ import bpy -from bpy.props import * +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, +) from bpy.types import Operator from bpy_extras.io_utils import ImportHelper -from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, parametric, gcodeimportparser, joinery +from . import ( + utils, + pack, + polygon_utils_cam, + simple, + gcodepath, + bridges, + parametric, + gcodeimportparser, + joinery +) import shapely from shapely.geometry import Point, LineString, Polygon import mathutils diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index 678bb37ac..c04fa93f4 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -27,38 +27,37 @@ import math from math import * from mathutils import * -from bpy.props import * import numpy -from cam import chunk -from cam.chunk import * -from cam import USE_PROFILER +from . import chunk +from .chunk import * +from .utils import USE_PROFILER -from cam import collision -from cam.collision import * +from . import collision +from .collision import * -from cam import simple -from cam.simple import * +from . import simple +from .simple import * -from cam.async_op import progress_async +from .async_op import progress_async -from cam import bridges -from cam.bridges import * +from . import bridges +from .bridges import * -from cam import utils -from cam import strategy +from . import utils +from . import strategy -from cam import pattern -from cam.pattern import * +from . import pattern +from .pattern import * -from cam import polygon_utils_cam -from cam.polygon_utils_cam import * +from . import polygon_utils_cam +from .polygon_utils_cam import * -from cam import image_utils -from cam.image_utils import * -from cam.opencamlib.opencamlib import * -from cam.nc import iso +from . import image_utils +from .image_utils import * +from .opencamlib.opencamlib import * +from .nc import iso def pointonline(a, b, c, tolerence): @@ -66,7 +65,8 @@ def pointonline(a, b, c, tolerence): c = c - a dot_pr = b.dot(c) # b dot c norms = numpy.linalg.norm(b) * numpy.linalg.norm(c) # find norms - angle = (numpy.rad2deg(numpy.arccos(dot_pr / norms))) # find angle between the two vectors + # find angle between the two vectors + angle = (numpy.rad2deg(numpy.arccos(dot_pr / norms))) if angle > tolerence: return False else: @@ -197,7 +197,8 @@ def startNewFile(): return c c = startNewFile() - last_cutter = None # [o.cutter_id,o.cutter_dameter,o.cutter_type,o.cutter_flutes] + # [o.cutter_id,o.cutter_dameter,o.cutter_type,o.cutter_flutes] + last_cutter = None processedops = 0 last = Vector((0, 0, 0)) @@ -237,7 +238,8 @@ def startNewFile(): c.flush_nc() - last_cutter = [o.cutter_id, o.cutter_diameter, o.cutter_type, o.cutter_flutes] + last_cutter = [o.cutter_id, o.cutter_diameter, + o.cutter_type, o.cutter_flutes] if o.cutter_type not in ['LASER', 'PLASMA']: if o.enable_hold: c.write('(Hold Down)\n') @@ -304,7 +306,8 @@ def startNewFile(): fadjust = True if m.use_position_definitions: # dhull - last = Vector((m.starting_position.x, m.starting_position.y, m.starting_position.z)) + last = Vector( + (m.starting_position.x, m.starting_position.y, m.starting_position.z)) lastrot = Euler((0, 0, 0)) duration = 0.0 @@ -398,7 +401,8 @@ def startNewFile(): if not cut: if o.cutter_type == 'LASER': c.write("(*************dwell->laser on)\n") - c.write("G04 P" + str(round(o.Laser_delay, 2)) + "\n") + c.write( + "G04 P" + str(round(o.Laser_delay, 2)) + "\n") c.write(o.Laser_on + '\n') elif o.cutter_type == 'PLASMA': c.write("(*************dwell->PLASMA on)\n") @@ -437,7 +441,8 @@ def startNewFile(): c.rapid(x=vx, y=vy, z=vz) # this is to evaluate operation time and adds a feedrate for fast moves if vz is not None: - f = plungefeedrate * fadjustval * 0.35 # compensate for multiple fast move accelerations + # compensate for multiple fast move accelerations + f = plungefeedrate * fadjustval * 0.35 if vx is not None or vy is not None: f = freefeedrate * 0.8 # compensate for free feedrate acceleration else: @@ -462,7 +467,8 @@ def startNewFile(): processedops += 1 if split and processedops > m.split_limit: - c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=free_height * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * + unitcorr, z=free_height * unitcorr) # @v=(ch.points[-1][0],ch.points[-1][1],free_height) c.program_end() findex += 1 @@ -481,8 +487,10 @@ def startNewFile(): c.flush_nc() c.feedrate(unitcorr * o.feedrate) - c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=free_height * unitcorr) - c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=last.z * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * + unitcorr, z=free_height * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * + unitcorr, z=last.z * unitcorr) processedops = 0 if o.remove_redundant_points and o.strategy != "DRILL": @@ -639,14 +647,16 @@ async def getPath3axis(context, operation): pathSamples = [] ob = bpy.data.objects[o.curve_object] pathSamples.extend(curveToChunks(ob)) - pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling + # sort before sampling + pathSamples = await utils.sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) elif o.strategy == 'PENCIL': await prepareArea(o) utils.getAmbient(o) pathSamples = getOffsetImageCavities(o, o.offset_image) pathSamples = limitChunks(pathSamples, o) - pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling + # sort before sampling + pathSamples = await utils.sortChunks(pathSamples, o) elif o.strategy == 'CRAZY': await prepareArea(o) # pathSamples = crazyStrokeImage(o) diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 85018cdab..48580d40a 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -29,14 +29,14 @@ import mathutils from mathutils import * -from cam import simple -from cam.simple import * -from cam import chunk -from cam.chunk import * -from cam import simulation -from cam.async_op import progress_async +from . import simple +from .simple import * +from . import chunk +from .chunk import * +from . import simulation +from .async_op import progress_async -from cam.numba_wrapper import jit, prange +from .numba_wrapper import jit, prange def getCircle(r, z): @@ -118,7 +118,8 @@ def imagetonumpy(i): width = i.size[0] height = i.size[1] - na = numpy.full(shape=(width*height*4,), fill_value=-10, dtype=numpy.double) + na = numpy.full(shape=(width*height*4,), + fill_value=-10, dtype=numpy.double) p = i.pixels[:] # these 2 lines are about 15% faster than na[:]=i.pixels[:].... whyyyyyyyy!!?!?!?!?! @@ -136,7 +137,8 @@ def imagetonumpy(i): def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea): for y in prange(y1, y2): for x in range(0, width-cwidth): - comparearea[x, y] = numpy.nanmax(sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan) + comparearea[x, y] = numpy.nanmax( + sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan) async def offsetArea(o, samples): @@ -152,7 +154,8 @@ async def offsetArea(o, samples): width = len(sourceArray) height = len(sourceArray[0]) cwidth = len(cutterArray) - o.offset_image = numpy.full(shape=(width, height), fill_value=-10.0, dtype=numpy.double) + o.offset_image = numpy.full( + shape=(width, height), fill_value=-10.0, dtype=numpy.double) t = time.time() @@ -160,7 +163,8 @@ async def offsetArea(o, samples): if o.inverse: sourceArray = -sourceArray + minz - comparearea = o.offset_image[m: width - cwidth + m, m:height - cwidth + m] + comparearea = o.offset_image[m: width - + cwidth + m, m:height - cwidth + m] # i=0 cutterArrayNan = np.where(cutterArray > -10, cutterArray, np.full(cutterArray.shape, np.nan)) @@ -170,7 +174,8 @@ async def offsetArea(o, samples): _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea) await progress_async('offset depth image', int((y2 * 100) / comparearea.shape[1])) - o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea + o.offset_image[m: width - cwidth + m, + m:height - cwidth + m] = comparearea print('\nOffset image time ' + str(time.time() - t)) @@ -188,8 +193,10 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly """detects areas in the offset image which are 'cavities' - the curvature changes.""" # i=numpy.logical_xor(lastislice , islice) simple.progress('detect corners in the offset image') - vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] - horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] + vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - \ + o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] + horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - \ + o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] # if bpy.app.debug_value==2: ar = numpy.logical_or(vertical, horizontal) @@ -215,14 +222,16 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly return chunks -def imageEdgeSearch_online(o, ar, zimage): # search edges for pencil strategy, another try. +# search edges for pencil strategy, another try. +def imageEdgeSearch_online(o, ar, zimage): minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z r = ceil((o.cutter_diameter/12)/o.optimisation.pixsize) # was commented coef = 0.75 maxarx = ar.shape[0] maxary = ar.shape[1] - directions = ((-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)) + directions = ((-1, -1), (0, -1), (1, -1), (1, 0), + (1, 1), (0, 1), (-1, 1), (-1, 0)) indices = ar.nonzero() # first get white pixels startpix = ar.sum() @@ -290,7 +299,8 @@ def imageEdgeSearch_online(o, ar, zimage): # search edges for pencil strategy, if len(indices[0] > 0): xs = indices[0][0] ys = indices[1][0] - nchunk = camPathChunkBuilder([(xs, ys, zimage[xs, ys])]) # startposition + nchunk = camPathChunkBuilder( + [(xs, ys, zimage[xs, ys])]) # startposition ar[xs, ys] = False else: @@ -319,7 +329,8 @@ def imageEdgeSearch_online(o, ar, zimage): # search edges for pencil strategy, test_direction = directions[dindexmod] if 0: - print(xs, ys, test_direction, last_direction, testangulardistance) + print(xs, ys, test_direction, + last_direction, testangulardistance) print(totpix) itests += 1 totaltests += 1 @@ -350,9 +361,11 @@ async def crazyPath(o): resx = ceil(sx / o.optimisation.simulation_detail) + 2 * o.borderwidth resy = ceil(sy / o.optimisation.simulation_detail) + 2 * o.borderwidth - o.millimage = numpy.full(shape=(resx, resy), fill_value=0., dtype=numpy.float) + o.millimage = numpy.full( + shape=(resx, resy), fill_value=0., dtype=numpy.float) # getting inverted cutter - o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) + o.cutterArray = - \ + simulation.getCutterArray(o, o.optimisation.simulation_detail) def buildStroke(start, end, cutterArray): @@ -361,7 +374,8 @@ def buildStroke(start, end, cutterArray): size_y = abs(end[1] - start[1]) + cutterArray.size[0] r = cutterArray.size[0] / 2 - strokeArray = numpy.full(shape=(size_x, size_y), fill_value=-10.0, dtype=numpy.float) + strokeArray = numpy.full(shape=(size_x, size_y), + fill_value=-10.0, dtype=numpy.float) samplesx = numpy.round(numpy.linspace(start[0], end[0], strokelength)) samplesy = numpy.round(numpy.linspace(start[1], end[1], strokelength)) samplesz = numpy.round(numpy.linspace(start[2], end[2], strokelength)) @@ -419,7 +433,8 @@ def crazyStrokeImage(o): print(indices[0][0], indices[1][0]) # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. lastvect = Vector((r, 0, 0)) - testvect = lastvect.normalized() * r / 2.0 # multiply *2 not to get values <1 pixel + # multiply *2 not to get values <1 pixel + testvect = lastvect.normalized() * r / 2.0 rot = Euler((0, 0, 1)) i = 0 perc = 0 @@ -431,7 +446,8 @@ def crazyStrokeImage(o): print(xs, ys, indices[0][0], indices[1][0], r) ar[xs - r:xs - r + d, ys - r:ys - r + d] = ar[xs - r:xs - r + d, ys - r:ys - r + d] * cutterArrayNegative - anglerange = [-pi, pi] # range for angle of toolpath vector versus material vector + # range for angle of toolpath vector versus material vector + anglerange = [-pi, pi] testangleinit = 0 angleincrement = 0.05 if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW') or ( @@ -471,7 +487,8 @@ def crazyStrokeImage(o): cy = cindices[1].sum() / eatpix v = Vector((cx - r, cy - r)) angle = testvect.to_2d().angle_signed(v) - if anglerange[0] < angle < anglerange[1]: # this could be righthanded milling? lets see :) + # this could be righthanded milling? lets see :) + if anglerange[0] < angle < anglerange[1]: if toomuchpix > eatpix > satisfypix: success = True if success: @@ -499,7 +516,8 @@ def crazyStrokeImage(o): testangle = -testangle testleftright = False else: - testangle = abs(testangle) + angleincrement # increment angle + testangle = abs(testangle) + \ + angleincrement # increment angle testleftright = True else: # climb/conv. testangle += angleincrement @@ -616,7 +634,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): print(indices[0][0], indices[1][0]) # vector is 3d, blender somehow doesn't rotate 2d vectors with angles. lastvect = Vector((r, 0, 0)) - testvect = lastvect.normalized() * r / 4.0 # multiply *2 not to get values <1 pixel + # multiply *2 not to get values <1 pixel + testvect = lastvect.normalized() * r / 4.0 rot = Euler((0, 0, 1)) i = 0 itests = 0 @@ -627,7 +646,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): margin = 0 # print(xs,ys,indices[0][0],indices[1][0],r) - ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, ys - r:ys + r] * cutterArrayNegative + ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - + r:xs + r, ys - r:ys + r] * cutterArrayNegative anglerange = [-pi, pi] # range for angle of toolpath vector versus material vector - # probably direction negative to the force applied on cutter by material. @@ -678,7 +698,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): # lets see :) # print(xs,ys,angle) foundsolutions.append([testvect.copy(), eatpix]) - if len(foundsolutions) >= 10: # or totpix < startpix*0.025: + # or totpix < startpix*0.025: + if len(foundsolutions) >= 10: success = True itests += 1 totaltests += 1 @@ -694,7 +715,8 @@ def crazyStrokeImageBinary(o, ar, avoidar): closest = abs(s[1] - optimalpix) # print('closest',closest) - testvect = bestsolution[0] # v1#+(v2-v1)*ratio#rewriting with interpolated vect. + # v1#+(v2-v1)*ratio#rewriting with interpolated vect. + testvect = bestsolution[0] xs = int(nchunk.points[-1][0] + testvect.x) ys = int(nchunk.points[-1][1] + testvect.y) nchunk.points.append([xs, ys]) @@ -787,13 +809,15 @@ def crazyStrokeImageBinary(o, ar, avoidar): if avoidar[xs, ys] == 0: # print(toomuchpix,ar[xs-r:xs-r+d,ys-r:ys-r+d].sum()*pi/4,satisfypix) - testarsum = ar[xs - r:xs - r + d, ys - r:ys - r + d].sum() * pi / 4 + testarsum = ar[xs - r:xs - r + d, + ys - r:ys - r + d].sum() * pi / 4 if toomuchpix > testarsum > 0 or ( totpix < startpix * 0.025): # 0 now instead of satisfypix found = True # print(xs,ys,indices[0][index],indices[1][index]) - nchunk = camPathChunk([(xs, ys)]) # startposition + nchunk = camPathChunk( + [(xs, ys)]) # startposition ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, ys - r:ys + r] * cutterArrayNegative # lastvect=Vector((r,0,0))#vector is 3d, @@ -943,7 +967,8 @@ def imageToChunks(o, image, with_border=False): ch.append(v) done = True verts.remove(v) - if v[0] == ch[0][0] and v[1] == ch[0][1]: # or len(verts)<=1: + # or len(verts)<=1: + if v[0] == ch[0][0] and v[1] == ch[0][1]: closed = True if closed: @@ -1150,11 +1175,13 @@ def renderSampleImage(o): node_out.format.color_mode = 'RGB' node_out.format.color_depth = '32' node_out.file_slots.new(os.path.basename(iname)) - n.links.new(node_in.outputs[node_in.outputs.find('Mist')], node_out.inputs[-1]) + n.links.new(node_in.outputs[node_in.outputs.find( + 'Mist')], node_out.inputs[-1]) ################### # resize operation image - o.offset_image = numpy.full(shape=(resx, resy), fill_value=-10, dtype=numpy.double) + o.offset_image = numpy.full( + shape=(resx, resy), fill_value=-10, dtype=numpy.double) # various settings for faster render r.resolution_percentage = 100 @@ -1191,7 +1218,8 @@ def renderSampleImage(o): os.replace(iname+"%04d.exr" % (s.frame_current), iname) finally: if backup_settings is not None: - _restore_render_settings(SETTINGS_TO_BACKUP, backup_settings) + _restore_render_settings( + SETTINGS_TO_BACKUP, backup_settings) else: print("Failed to backup scene settings") @@ -1220,13 +1248,15 @@ def renderSampleImage(o): #o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth) o.optimisation.pixsize = o.source_image_size_x / i.size[0] - simple.progress('pixel size in the image source', o.optimisation.pixsize) + simple.progress('pixel size in the image source', + o.optimisation.pixsize) rawimage = imagetonumpy(i) maxa = numpy.max(rawimage) mina = numpy.min(rawimage) neg = o.source_image_scale_z < 0 - if o.strategy == 'WATERLINE': # waterline strategy needs image border to have ok ambient. + # waterline strategy needs image border to have ok ambient. + if o.strategy == 'WATERLINE': a = numpy.full(shape=( 2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]), fill_value=1-neg, dtype=numpy.float) else: # other operations like parallel need to reach the border @@ -1238,11 +1268,13 @@ def renderSampleImage(o): if o.source_image_scale_z < 0: # negative images place themselves under the 0 plane by inverting through scale multiplication - a = (a - mina) # first, put the image down, se we know the image minimum is on 0 + # first, put the image down, se we know the image minimum is on 0 + a = (a - mina) a *= o.source_image_scale_z else: # place positive images under 0 plane, this is logical - a = (a - mina) # first, put the image down, se we know the image minimum is on 0 + # first, put the image down, se we know the image minimum is on 0 + a = (a - mina) a *= o.source_image_scale_z a -= (maxa - mina) * o.source_image_scale_z diff --git a/scripts/addons/cam/involute_gear.py b/scripts/addons/cam/involute_gear.py index 5f857455d..c823d6cc2 100644 --- a/scripts/addons/cam/involute_gear.py +++ b/scripts/addons/cam/involute_gear.py @@ -68,9 +68,17 @@ from bpy.props import * from bpy.types import Operator -from cam import utils, polygon_utils_cam, simple +from . import ( + utils, + polygon_utils_cam, + simple, +) import shapely -from shapely.geometry import Point, LineString, Polygon +from shapely.geometry import ( + Point, + LineString, + Polygon +) import mathutils import math diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index fe8c705b3..92747ffbd 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -23,11 +23,19 @@ import bpy -from bpy.props import * from bpy.types import Operator from bpy_extras.io_utils import ImportHelper -from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, parametric, puzzle_joinery +from . import ( + utils, + pack, + polygon_utils_cam, + simple, + gcodepath, + bridges, + parametric, + puzzle_joinery +) import shapely from shapely.geometry import Point, LineString, Polygon import mathutils @@ -94,7 +102,8 @@ def twist_line(length, thickness, finger_play, percentage, amount, distance, cen spacing = distance / amount while amount > 0: position = spacing * amount - interlock_twist(length, thickness, finger_play, percentage=percentage, cx=position) + interlock_twist(length, thickness, finger_play, + percentage=percentage, cx=position) print('twistline', amount, distance, position) amount -= 1 @@ -143,13 +152,16 @@ def horizontal_finger(length, thickness, finger_play, amount, center=True): mortise(length, thickness, finger_play, 0, thickness / 2) simple.active_name("_width_finger") else: - mortise(length, thickness, finger_play, i * 2 * length, thickness / 2) + mortise(length, thickness, finger_play, + i * 2 * length, thickness / 2) simple.active_name("_width_finger") - mortise(length, thickness, finger_play, -i * 2 * length, thickness / 2) + mortise(length, thickness, finger_play, - + i * 2 * length, thickness / 2) simple.active_name("_width_finger") else: for i in range(amount): - mortise(length, thickness, finger_play, length / 2 + 2 * i * length, 0) + mortise(length, thickness, finger_play, + length / 2 + 2 * i * length, 0) simple.active_name("_width_finger") simple.join_multiple("_width_finger") @@ -229,7 +241,8 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): # creates pockets pocket using mortise function for kerf bending dist = 3 * finger_width / 2 while dist < length: - mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) + mortise(height - 2 * finger_thick, + pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") dist += finger_width * 2 @@ -240,7 +253,8 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): def make_variable_flex_pocket(height, finger_thick, pocket_width, locations): # creates pockets pocket using mortise function for kerf bending for dist in locations: - mortise(height + 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) + mortise(height + 2 * finger_thick, + pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") simple.join_multiple("_flex_pocket") @@ -262,7 +276,8 @@ def create_flex_side(length, height, finger_thick, top_bottom=False): else: simple.make_active("base") fingers = bpy.context.active_object - bpy.ops.transform.translate(value=(0.0, height / 2 - finger_thick / 2 + 0.0003, 0.0)) + bpy.ops.transform.translate( + value=(0.0, height / 2 - finger_thick / 2 + 0.0003, 0.0)) bpy.ops.curve.simple(align='WORLD', location=(length / 2 + 0.00025, 0, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', Simple_width=length, Simple_length=height, shape='3D', @@ -317,12 +332,14 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, if not_start: while distance <= pd: mortise_angle = angle(oldp, p) - mortise_angle_difference = abs(mortise_angle - old_mortise_angle) + mortise_angle_difference = abs( + mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( math.pi / 4)) # factor for tolerance for the finger if base: - mortise(finger_size, finger_thick, finger_tolerance * mad, distance, 0, 0) + mortise(finger_size, finger_thick, + finger_tolerance * mad, distance, 0, 0) simple.active_name("_base") else: mortise_point = loop.interpolate(distance) @@ -431,10 +448,12 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi if not_start: while distance <= pd: mortise_angle = angle(oldp, p) - mortise_angle_difference = abs(mortise_angle - old_mortise_angle) + mortise_angle_difference = abs( + mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( math.pi / 4)) # factor for tolerance for the finger - distance += mad * finger_tolerance # move finger by the factor mad greater with larger angle difference + # move finger by the factor mad greater with larger angle difference + distance += mad * finger_tolerance mortise_point = loop.interpolate(distance) if mad > 2 and double_adaptive: hpos.append(distance) # saves the mortise center @@ -465,12 +484,15 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi # adaptive finger length start while finger_sz > min_finger and next_angle_difference > adaptive: # while finger_sz > min_finger and next_angle_difference > adaptive: - finger_sz *= 0.95 # reduce the size of finger by a percentage... the closer to 1.0, the slower + # reduce the size of finger by a percentage... the closer to 1.0, the slower + finger_sz *= 0.95 distance = old_distance + 3 * oldfinger_sz / 2 + finger_sz / 2 - mortise_point = loop.interpolate(distance) # get the next mortise point + mortise_point = loop.interpolate( + distance) # get the next mortise point next_mortise_angle = angle((old_mortise_point.x, old_mortise_point.y), (mortise_point.x, mortise_point.y)) # calculate next angle - next_angle_difference = abs(next_mortise_angle - mortise_angle) + next_angle_difference = abs( + next_mortise_angle - mortise_angle) oldfinger_sz = finger_sz old_mortise_angle = mortise_angle @@ -488,7 +510,8 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi def single_interlock(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle, type, amount=1, twist_percentage=0.5): if type == "GROOVE": - interlock_groove(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle) + interlock_groove(finger_depth, finger_thick, + finger_tolerance, x, y, groove_angle) elif type == "TWIST": interlock_twist(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle, percentage=twist_percentage) diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 4f4c80422..b3c896345 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -19,26 +19,49 @@ # # ***** END GPL LICENCE BLOCK ***** -# blender operators definitions are in this file. They mostly call the functions from utils.py +# blender operators definitions are in this file. They mostly call the functions from py import bpy -from bpy.props import * +from bpy.props import ( + EnumProperty, + StringProperty, + +) from bpy_extras.io_utils import ImportHelper import subprocess import os import threading -from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, simulation -from cam.async_op import AsyncOperatorMixin, AsyncCancelledException +from . import ( + utils, + pack, + polygon_utils_cam, + simple, + gcodepath, + bridges, + simulation, +) +from .utils import ( + was_hidden_dict, + reload_pathss, + isValid, + isChainValid, + silhoueteOffset, + getBoundsWorldspace, + addMachineAreaObject, +) +from .async_op import ( + AsyncOperatorMixin, + AsyncCancelledException, +) import shapely import mathutils import math import textwrap import traceback -import cam -from cam.exception import * +from .exception import * class threadCom: # object passed to threads to read background process stdout info @@ -83,7 +106,7 @@ def timer_update(context): o = s.cam_operations[tcom.opname] o.computing = False - utils.reload_paths(o) + reload_paths(o) update_zbufferimage_tag = False update_offsetimage_tag = False else: @@ -110,7 +133,7 @@ def execute(self, context): bpath = bpy.app.binary_path fpath = bpy.data.filepath - for p in bpy.utils.script_paths(): + for p in bpy.script_paths(): scriptpath = p + os.sep + 'addons' + os.sep + 'cam' + os.sep + 'backgroundop.py' print(scriptpath) if os.path.isfile(scriptpath): @@ -231,7 +254,7 @@ def poll(cls, context): s = context.scene o = s.cam_operations[s.cam_active_operation] if o is not None: - if cam.isValid(o, context): + if isValid(o, context): return True return False @@ -318,7 +341,7 @@ class PathsChain(bpy.types.Operator, AsyncOperatorMixin): def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain, context)[0] + return isChainValid(chain, context)[0] async def execute_async(self, context): s = context.scene @@ -357,7 +380,7 @@ class PathExportChain(bpy.types.Operator): def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain, context)[0] + return isChainValid(chain, context)[0] def execute(self, context): s = bpy.context.scene @@ -441,7 +464,7 @@ class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin): def poll(cls, context): s = context.scene chain = s.cam_chains[s.cam_active_chain] - return cam.isChainValid(chain, context)[0] + return isChainValid(chain, context)[0] operation: StringProperty( name="Operation", @@ -623,7 +646,7 @@ def Add_Pocket(self, maxdepth, sname, new_cutter_diameter): ob = bpy.data.objects[sname] ob.select_set(True) bpy.context.view_layer.objects.active = ob - utils.silhoueteOffset(ob, -new_cutter_diameter/2, 1, 0.3) + silhoueteOffset(ob, -new_cutter_diameter/2, 1, 0.3) bpy.context.active_object.name = 'medial_pocket' if not mpocket_exists: # create a pocket operation if it does not exist already @@ -660,7 +683,7 @@ def execute(self, context): "Please add an object to base the operation on.") return {'CANCELLED'} - minx, miny, minz, maxx, maxy, maxz = utils.getBoundsWorldspace([ob]) + minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace([ob]) s.cam_operations.add() o = s.cam_operations[-1] o.object_name = ob.name @@ -672,7 +695,7 @@ def execute(self, context): o.filename = o.name if s.objects.get('CAM_machine') is None: - utils.addMachineAreaObject() + addMachineAreaObject() return {'FINISHED'} @@ -751,9 +774,9 @@ def execute(self, context): pass ao = scene.cam_operations[scene.cam_active_operation] - print(cam.was_hidden_dict) - if ao.name in cam.was_hidden_dict: - del cam.was_hidden_dict[ao.name] + print(was_hidden_dict) + if ao.name in was_hidden_dict: + del was_hidden_dict[ao.name] scene.cam_operations.remove(scene.cam_active_operation) if scene.cam_active_operation > 0: diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index 8f32735c4..b15aa01ae 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -20,7 +20,11 @@ # ***** END GPL LICENCE BLOCK ***** import bpy -from cam import utils, simple, polygon_utils_cam +from . import ( + utils, + simple, + polygon_utils_cam +) import shapely from shapely import geometry as sgeometry from shapely import affinity, prepared @@ -67,7 +71,8 @@ def packCurves(): rotate = packsettings.rotate rotate_angle = packsettings.rotate_angle - polyfield = [] # in this, position, rotation, and actual poly will be stored. + # in this, position, rotation, and actual poly will be stored. + polyfield = [] for ob in bpy.context.selected_objects: simple.activate(ob) bpy.ops.object.make_single_user(type='SELECTED_OBJECTS') @@ -123,7 +128,8 @@ def packCurves(): p = porig if rotate: - ptrans = affinity.rotate(p, rot, origin=rotcenter, use_radians=True) + ptrans = affinity.rotate( + p, rot, origin=rotcenter, use_radians=True) ptrans = affinity.translate(ptrans, x, y) else: ptrans = affinity.translate(p, x, y) @@ -162,7 +168,8 @@ def packCurves(): # print(iter) # reset polygon to best position here: - ptrans = affinity.rotate(porig, best[2], rotcenter, use_radians=True) + ptrans = affinity.rotate( + porig, best[2], rotcenter, use_radians=True) ptrans = affinity.translate(ptrans, best[0], best[1]) print(best[0], best[1], itera) @@ -190,5 +197,6 @@ def packCurves(): i += 1 t = time.time() - t - polygon_utils_cam.shapelyToCurve('test', sgeometry.MultiPolygon(placedpolys), 0) + polygon_utils_cam.shapelyToCurve( + 'test', sgeometry.MultiPolygon(placedpolys), 0) print(t) diff --git a/scripts/addons/cam/parametric.py b/scripts/addons/cam/parametric.py index 2f734c99c..25d660f2b 100644 --- a/scripts/addons/cam/parametric.py +++ b/scripts/addons/cam/parametric.py @@ -1,49 +1,42 @@ -# -*- coding: utf-8 -*- - -""" -MIT License - -Copyright (c) 2019 Devon (Gorialis) R - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -""" -Create a Blender curve from a 3D parametric function. -This allows for a 3D plot to be made of the function, which can be converted into a mesh. - -I have documented the inner workings here, but if you're not interested and just want to -suit this to your own function, scroll down to the bottom and edit the `f(t)` function and -the iteration count to your liking. - -This code has been checked to work on Blender 2.92. - -""" - - - +# MIT License +# +# Copyright (c) 2019 Devon (Gorialis) R +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# +# +# +# Create a Blender curve from a 3D parametric function. +# This allows for a 3D plot to be made of the function, which can be converted into a mesh. +# +# I have documented the inner workings here, but if you're not interested and just want to +# suit this to your own function, scroll down to the bottom and edit the `f(t)` function and +# the iteration count to your liking. +# +# This code has been checked to work on Blender 2.92. import math from math import sin, cos, pi import bmesh import bpy from mathutils import Vector + + def derive_bezier_handles(a, b, c, d, tb, tc): """ Derives bezier handles by using the start and end of the curve with 2 intermediate @@ -80,8 +73,10 @@ def derive_bezier_handles(a, b, c, d, tb, tc): final_c = c - (math.pow(1 - tc, 3) * a) - (math.pow(tc, 3) * d) # Multiply the inversed matrix with the position vector to get the handle points - bezier_b = matrix_determinant * ((matrix_d * final_b) + (-matrix_b * final_c)) - bezier_c = matrix_determinant * ((-matrix_c * final_b) + (matrix_a * final_c)) + bezier_b = matrix_determinant * \ + ((matrix_d * final_b) + (-matrix_b * final_c)) + bezier_c = matrix_determinant * \ + ((-matrix_c * final_b) + (matrix_a * final_c)) # Return the handle points return bezier_b, bezier_c @@ -142,7 +137,8 @@ def create_parametric_curve( if use_cubic: points = [ - function(((i - 3) / (3 * iterations)) * (max - min) + min, *args, **kwargs) + function(((i - 3) / (3 * iterations)) * + (max - min) + min, *args, **kwargs) for i in range((3 * (iterations + 2)) + 1) ] @@ -153,9 +149,12 @@ def create_parametric_curve( c = points[(3 * i) + 2] d = points[(3 * i) + 3] - bezier_bx, bezier_cx = derive_bezier_handles(a[0], b[0], c[0], d[0], 1 / 3, 2 / 3) - bezier_by, bezier_cy = derive_bezier_handles(a[1], b[1], c[1], d[1], 1 / 3, 2 / 3) - bezier_bz, bezier_cz = derive_bezier_handles(a[2], b[2], c[2], d[2], 1 / 3, 2 / 3) + bezier_bx, bezier_cx = derive_bezier_handles( + a[0], b[0], c[0], d[0], 1 / 3, 2 / 3) + bezier_by, bezier_cy = derive_bezier_handles( + a[1], b[1], c[1], d[1], 1 / 3, 2 / 3) + bezier_bz, bezier_cz = derive_bezier_handles( + a[2], b[2], c[2], d[2], 1 / 3, 2 / 3) points[(3 * i) + 1] = (bezier_bx, bezier_by, bezier_bz) points[(3 * i) + 2] = (bezier_cx, bezier_cy, bezier_cz) @@ -165,13 +164,16 @@ def create_parametric_curve( spline.bezier_points[i].co = points[3 * (i + 1)] spline.bezier_points[i].handle_left_type = 'FREE' - spline.bezier_points[i].handle_left = Vector(points[(3 * (i + 1)) - 1]) + spline.bezier_points[i].handle_left = Vector( + points[(3 * (i + 1)) - 1]) spline.bezier_points[i].handle_right_type = 'FREE' - spline.bezier_points[i].handle_right = Vector(points[(3 * (i + 1)) + 1]) + spline.bezier_points[i].handle_right = Vector( + points[(3 * (i + 1)) + 1]) else: - points = [function(i / iterations, *args, **kwargs) for i in range(iterations + 1)] + points = [function(i / iterations, *args, **kwargs) + for i in range(iterations + 1)] # Set point coordinates, disable handles for i in range(iterations + 1): @@ -243,7 +245,8 @@ def make_edge_loops(*objects): if bpy.app.version >= (2, 80): ctx['selected_editable_objects'] = mesh_objects else: - ctx['selected_editable_bases'] = [scene.object_bases[o.name] for o in mesh_objects] + ctx['selected_editable_bases'] = [scene.object_bases[o.name] + for o in mesh_objects] # Join them together bpy.ops.object.join(ctx) diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index 0533489c6..d2d51c20c 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -23,11 +23,15 @@ import mathutils from mathutils import * -from cam import simple, chunk, utils -from cam.simple import * -from cam.chunk import * -from cam import polygon_utils_cam -from cam.polygon_utils_cam import * +from . import ( + simple, + chunk, + utils, + polygon_utils_cam +) +from .simple import * +from .chunk import * +from .polygon_utils_cam import * import shapely from shapely import geometry as sgeometry import numpy @@ -58,7 +62,8 @@ def getPathPatternParallel(o, angle): chunk = camPathChunkBuilder([]) v = Vector((a * pathd, int(-dim / pathstep) * pathstep, 0)) v.rotate(e) - v += vm # shifting for the rotation, so pattern rotates around middle... + # shifting for the rotation, so pattern rotates around middle... + v += vm for b in range(int(-dim / pathstep), int(dim / pathstep)): v += dirvect @@ -149,7 +154,8 @@ def getPathPattern(operation): elif o.strategy == 'CROSS': pathchunks.extend(getPathPatternParallel(o, o.parallel_angle)) - pathchunks.extend(getPathPatternParallel(o, o.parallel_angle - math.pi / 2.0)) + pathchunks.extend(getPathPatternParallel( + o, o.parallel_angle - math.pi / 2.0)) elif o.strategy == 'BLOCK': @@ -324,7 +330,8 @@ def getPathPattern(operation): pathchunks = [] chunks = [] for p in polys: - p = p.buffer(-o.dist_between_paths / 10, o.optimisation.circle_detail) + p = p.buffer(-o.dist_between_paths / 10, + o.optimisation.circle_detail) # first, move a bit inside, because otherwise the border samples go crazy very often changin between # hit/non hit and making too many jumps in the path. chunks.extend(shapelyToChunks(p, 0)) @@ -339,7 +346,8 @@ def getPathPattern(operation): for porig in polys: p = porig while not p.is_empty: - p = p.buffer(-o.dist_between_paths, o.optimisation.circle_detail) + p = p.buffer(-o.dist_between_paths, + o.optimisation.circle_detail) if not p.is_empty: nchunks = shapelyToChunks(p, zlevel) diff --git a/scripts/addons/cam/puzzle_joinery.py b/scripts/addons/cam/puzzle_joinery.py index 19b26faf5..4e6989169 100644 --- a/scripts/addons/cam/puzzle_joinery.py +++ b/scripts/addons/cam/puzzle_joinery.py @@ -23,12 +23,24 @@ from typing import Any import bpy -from bpy.props import * from bpy.types import Operator -from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, parametric, joinery +from . import ( + utils, + pack, + polygon_utils_cam, + simple, + gcodepath, + bridges, + parametric, + joinery, +) import shapely -from shapely.geometry import Point, LineString, Polygon +from shapely.geometry import ( + Point, + LineString, + Polygon, +) import mathutils import math @@ -121,7 +133,8 @@ def fingers(diameter, inside, amount=1, stem=1): def twistf(name, length, diameter, tolerance, twist, tneck, tthick, twist_keep=False): # add twist lock to receptacle if twist: - joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) + joinery.interlock_twist(length, tthick, tolerance, + cx=0, cy=0, rotation=0, percentage=tneck) simple.rotate(math.pi / 2) simple.move(y=-tthick / 2 + 2 * diameter + 2 * tolerance) simple.active_name('xtemptwist') @@ -138,7 +151,8 @@ def twistm(name, length, diameter, tolerance, twist, tneck, tthick, angle, twist # add twist lock to male connector global DT if twist: - joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) + joinery.interlock_twist(length, tthick, tolerance, + cx=0, cy=0, rotation=0, percentage=tneck) simple.rotate(math.pi / 2) simple.move(y=-tthick / 2 + 2 * diameter * DT) simple.rotate(angle) @@ -186,7 +200,8 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= twistm('tmprect', thick, diameter, tolerance, twist, tneck, tthick, -math.pi / 2, x=width / 2, twist_keep=twist_keep) - twistf('receptacle', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, + twist, tneck, tthick, twist_keep=twist_keep) simple.rename('receptacle', '_tmpreceptacle') if which == 'FF' or which == 'F' or which == 'MF': simple.rotate(-math.pi / 2) @@ -203,7 +218,8 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= simple.remove_multiple("fingers") # Remove temporary base and holes if twist_line: - joinery.twist_line(thick, tthick, tolerance, tneck, twist_line_amount, width) + joinery.twist_line(thick, tthick, tolerance, + tneck, twist_line_amount, width) if twist_keep: simple.duplicate() simple.active_name('tmptwist') @@ -241,8 +257,10 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False amount = round(thick / ((4 + 2 * (stem - 1)) * diameter * DT)) - 1 fingers(diameter, tolerance, amount, stem=stem) - twistf('receptacle', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) - twistf('testing', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, + twist, tneck, tthick, twist_keep=twist_keep) + twistf('testing', thick, diameter, tolerance, + twist, tneck, tthick, twist_keep=twist_keep) print("generating arc") # generate arc bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Segment', @@ -261,7 +279,8 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False if which == 'MF' or which == 'M': simple.union('_tmp') simple.active_name("base") - twistm('base', thick, diameter, tolerance, twist, tneck, tthick, math.pi, x=radius) + twistm('base', thick, diameter, tolerance, + twist, tneck, tthick, math.pi, x=radius) simple.rename('base', '_tmparc') simple.rename('receptacle', '_tmpreceptacle') @@ -280,7 +299,8 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False if which == 'M': simple.rotate(-angle) simple.mirrory() - bpy.ops.object.transform_apply(location=True, rotation=True, scale=False) + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=False) simple.rotate(-math.pi / 2) simple.move(y=radius) simple.rename('PUZZLE_arc', 'PUZZLE_arc_male') @@ -315,7 +335,8 @@ def arcbararc(length, radius, thick, angle, angleb, diameter, tolerance, amount= # tthick = thicknest of the twist material # which = which joint to generate, Male Female MaleFemale M, F, MF - length -= (radius * 2 + thick) # adjust length to include 2x radius + thick + # adjust length to include 2x radius + thick + length -= (radius * 2 + thick) # generate base rectangle bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', @@ -344,7 +365,8 @@ def arcbararc(length, radius, thick, angle, angleb, diameter, tolerance, amount= simple.active_name('tmprect') if twist_line: - joinery.twist_line(thick, tthick, tolerance, tneck, twist_line_amount, length) + joinery.twist_line(thick, tthick, tolerance, tneck, + twist_line_amount, length) if twist_keep: simple.duplicate() simple.active_name('tmptwist') @@ -372,7 +394,8 @@ def arcbar(length, radius, thick, angle, diameter, tolerance, amount=0, stem=1, which = 'MM' elif which == 'F': which = 'FF' - length -= (radius * 2 + thick) # adjust length to include 2x radius + thick + # adjust length to include 2x radius + thick + length -= (radius * 2 + thick) # generate base rectangle # Generate male section and join to the base @@ -429,7 +452,8 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis r_exterior = radius + thick / 2 r_interior = radius - thick / 2 - height = math.sqrt(r_exterior * r_exterior - radius * radius) + r_interior / 4 + height = math.sqrt(r_exterior * r_exterior - + radius * radius) + r_interior / 4 bpy.ops.curve.simple(align='WORLD', location=(0, height, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', @@ -680,7 +704,8 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, simple.union('tmprect') dilated = line.buffer(thick/2) # expand shapely object to thickness utils.shapelyToCurve('tmp_curve', dilated, 0.0) - simple.difference('tmp', 'tmp_curve') # truncate curve at both ends with the rectangles + # truncate curve at both ends with the rectangles + simple.difference('tmp', 'tmp_curve') fingers(diameter, tolerance, amount, stem=stem) simple.make_active('fingers') @@ -692,7 +717,8 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, twistm('tmp_curve', thick, diameter, tolerance, twist, t_neck, t_thick, end_angle, x=p_end[0], y=p_end[1], twist_keep=twist_keep) - twistf('receptacle', thick, diameter, tolerance, twist, t_neck, t_thick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, + twist, t_neck, t_thick, twist_keep=twist_keep) simple.rename('receptacle', 'tmp') simple.rotate(start_angle+math.pi) simple.move(x=p_start[0], y=p_start[1]) @@ -754,4 +780,5 @@ def tile(diameter, tolerance, tile_x_amount, tile_y_amount, stem=1): simple.rotate(math.pi/2) simple.move(x=width/2) simple.difference('_', '_base') - simple.active_name('tile_ ' + str(tile_x_amount) + '_' + str(tile_y_amount)) + simple.active_name('tile_ ' + str(tile_x_amount) + + '_' + str(tile_y_amount)) diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index fe9d4d8b5..a4656cd84 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -31,7 +31,12 @@ import mathutils from mathutils import * from math import * -from shapely.geometry import Point, LineString, Polygon, MultiLineString +from shapely.geometry import ( + Point, + LineString, + Polygon, + multilinestring +) def tuple_add(t, t1): # add two tuples as Vectors @@ -320,7 +325,8 @@ def remove_doubles(): def add_overcut(diametre, overcut=True): if overcut: name = bpy.context.active_object.name - bpy.ops.object.curve_overcuts(diameter=diametre, threshold=math.pi/2.05) + bpy.ops.object.curve_overcuts( + diameter=diametre, threshold=math.pi/2.05) overcut_name = bpy.context.active_object.name make_active(name) bpy.ops.object.delete() @@ -377,4 +383,5 @@ def active_to_coords(): # returns shapely polygon from active object def active_to_shapely_poly(): - return Polygon(active_to_coords()) # convert coordinates to shapely Polygon datastructure + # convert coordinates to shapely Polygon datastructure + return Polygon(active_to_coords()) diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 13b10be32..55fe23077 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -25,13 +25,12 @@ import mathutils import math import time -from bpy.props import * -from cam import utils +from . import utils import numpy as np -from cam import simple -from cam import image_utils -from cam.async_op import progress_async +from . import simple +from . import image_utils +from .async_op import progress_async def createSimulationObject(name, operations, i): @@ -353,7 +352,8 @@ def getCutterArray(operation, pixsize): if v.length <= cutter_r: z = -(v.length - ball_r) * s - Ball_R + D_ofset if v.length <= ball_r: - z = math.sin(math.acos(v.length / Ball_R)) * Ball_R - Ball_R + z = math.sin(math.acos(v.length / Ball_R) + ) * Ball_R - Ball_R car.itemset((a, b), z) elif type == 'CUSTOM': cutob = bpy.data.objects[operation.cutter_object_name] @@ -389,13 +389,15 @@ def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False): and optionally returning the volume that has been milled. This is now used for feedrate tweaking.""" m = int(cutterArray.shape[0] / 2) size = cutterArray.shape[0] - if xs > m and xs < si.shape[0] - m and ys > m and ys < si.shape[1] - m: # whole cutter in image there + # whole cutter in image there + if xs > m and xs < si.shape[0] - m and ys > m and ys < si.shape[1] - m: if getvolume: volarray = si[xs - m:xs - m + size, ys - m:ys - m + size].copy() si[xs - m:xs - m + size, ys - m:ys - m + size] = np.minimum(si[xs - m:xs - m + size, ys - m:ys - m + size], cutterArray + z) if getvolume: - volarray = si[xs - m:xs - m + size, ys - m:ys - m + size] - volarray + volarray = si[xs - m:xs - m + size, + ys - m:ys - m + size] - volarray vsum = abs(volarray.sum()) # print(vsum) return vsum diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index 887da44b8..f0364be44 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -24,7 +24,7 @@ import bpy -from cam import utils +from . import utils def slicing2d(ob, height): # April 2020 Alain Pelletier @@ -93,7 +93,8 @@ def sliceObject(ob): # April 2020 Alain Pelletier if above0 and minz < 0: start_height = 0 - layeramt = 1 + int((maxz - start_height) // thickness) # calculate amount of layers needed + # calculate amount of layers needed + layeramt = 1 + int((maxz - start_height) // thickness) for layer in range(layeramt): height = round(layer * thickness, 6) # height of current layer @@ -108,13 +109,15 @@ def sliceObject(ob): # April 2020 Alain Pelletier bpy.ops.object.duplicate() # make a copy of object to be sliced bpy.context.view_layer.objects.active.name = slicename # change the name of object - obslice = bpy.context.view_layer.objects.active # attribute active object to obslice + # attribute active object to obslice + obslice = bpy.context.view_layer.objects.active scollection.objects.link(obslice) # link obslice to scollecton if slice3d: # slice 3d at desired height and stop at desired height slicing3d(obslice, height, height + thickness) else: - slicesuccess = slicing2d(obslice, height) # slice object at desired height + # slice object at desired height + slicesuccess = slicing2d(obslice, height) if indexes and slicesuccess: # text objects diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 5e36950ee..9d728bf2e 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -22,25 +22,28 @@ # here is the strategy functionality of Blender CAM. The functions here are called with operators defined in ops.py. import bpy -from bpy.props import * import time import math from math import * from bpy_extras import object_utils -from cam import chunk -from cam.chunk import * -from cam import collision -from cam.collision import * -from cam import simple -from cam.simple import * -from cam import pattern -from cam.pattern import * -from cam import utils, bridges, ops -from cam.utils import * -from cam import polygon_utils_cam -from cam.polygon_utils_cam import * -from cam import image_utils -from cam.image_utils import * +from . import ( + chunk, + collision, + simple, + pattern, + utils, + bridges, + ops, + polygon_utils_cam, + image_utils +) +from .chunk import * +from .collision import * +from .simple import * +from .pattern import * +from .utils import * +from .polygon_utils_cam import * +from .image_utils import * from shapely.geometry import polygon as spolygon from shapely import geometry as sgeometry @@ -213,8 +216,10 @@ async def curve(o): raise CamException("All objects must be curves for this operation.") for ob in o.objects: - pathSamples.extend(curveToChunks(ob)) # make the chunks from curve here - pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling + # make the chunks from curve here + pathSamples.extend(curveToChunks(ob)) + # sort before sampling + pathSamples = await utils.sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) # simplify # layers here @@ -225,7 +230,8 @@ async def curve(o): chunks = [] for layer in layers: for ch in pathSamples: - extendorder.append([ch.copy(), layer]) # include layer information to chunk list + # include layer information to chunk list + extendorder.append([ch.copy(), layer]) for chl in extendorder: # Set offset Z for all chunks according to the layer information, chunk = chl[0] @@ -233,7 +239,8 @@ async def curve(o): print('layer: ' + str(layer[1])) chunk.offsetZ(o.maxz * 2 - o.minz + layer[1]) chunk.clampZ(o.minz) # safety to not cut lower than minz - chunk.clampmaxZ(o.movement.free_height) # safety, not higher than free movement height + # safety, not higher than free movement height + chunk.clampmaxZ(o.movement.free_height) for chl in extendorder: # strip layer information from extendorder and transfer them to chunks chunks.append(chl[0]) @@ -243,7 +250,8 @@ async def curve(o): else: # no layers, old curve for ch in pathSamples: ch.clampZ(o.minz) # safety to not cut lower than minz - ch.clampmaxZ(o.movement.free_height) # safety, not higher than free movement height + # safety, not higher than free movement height + ch.clampmaxZ(o.movement.free_height) chunksToMesh(pathSamples, o) @@ -258,7 +266,8 @@ async def proj_curve(s, o): from cam import chunk if targetCurve.type != 'CURVE': - raise CamException('Projection target and source have to be curve objects!') + raise CamException( + 'Projection target and source have to be curve objects!') if 1: extend_up = 0.1 @@ -320,7 +329,8 @@ async def pocket(o): print("cutter offset", c_offset) p = utils.getObjectOutline(c_offset, o, False) - approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 + approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / + o.dist_between_paths) / 2 print("approximative:" + str(approxn)) print(o) @@ -378,15 +388,18 @@ async def pocket(o): # helix_enter first try here TODO: check if helix radius is not out of operation area. if o.movement.helix_enter: - helix_radius = c_offset * o.movement.helix_diameter * 0.01 # 90 percent of cutter radius + helix_radius = c_offset * o.movement.helix_diameter * \ + 0.01 # 90 percent of cutter radius helix_circumference = helix_radius * pi * 2 revheight = helix_circumference * tan(o.movement.ramp_in_angle) for chi, ch in enumerate(lchunks): if not chunksFromCurve[chi].children: - p = ch.get_point(0) # TODO:intercept closest next point when it should stay low + # TODO:intercept closest next point when it should stay low + p = ch.get_point(0) # first thing to do is to check if helix enter can really enter. - checkc = Circle(helix_radius + c_offset, o.optimisation.circle_detail) + checkc = Circle(helix_radius + c_offset, + o.optimisation.circle_detail) checkc = affinity.translate(checkc, p[0], p[1]) covers = False for poly in o.silhouete: @@ -397,7 +410,8 @@ async def pocket(o): if covers: revolutions = (l[0] - p[2]) / revheight # print(revolutions) - h = Helix(helix_radius, o.optimisation.circle_detail, l[0], p, revolutions) + h = Helix( + helix_radius, o.optimisation.circle_detail, l[0], p, revolutions) # invert helix if not the typical direction if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): @@ -443,7 +457,8 @@ async def pocket(o): h = Helix(o.movement.retract_radius, o.optimisation.circle_detail, p[2] + o.movement.retract_height, p, revolutions) - e = Euler((0, 0, rotangle + pi)) # angle to rotate whole retract move + # angle to rotate whole retract move + e = Euler((0, 0, rotangle + pi)) rothelix = [] c = [] # polygon for outlining and checking collisions. for p in h: # rotate helix to go from tangent of vector @@ -516,9 +531,12 @@ async def drill(o): if ob.type == 'CURVE': ob.data.dimensions = '3D' try: - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply( + location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply( + location=False, rotation=True, scale=False) + bpy.ops.object.transform_apply( + location=False, rotation=False, scale=True) except: pass @@ -530,7 +548,8 @@ async def drill(o): maxx, minx, maxy, miny, maxz, minz = -10000, 10000, -10000, 10000, -10000, 10000 for p in c.points: if o.drill_type == 'ALL_POINTS': - chunks.append(camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) + chunks.append(camPathChunk( + [(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) @@ -539,7 +558,8 @@ async def drill(o): maxz = max(p.co.z, maxz) for p in c.bezier_points: if o.drill_type == 'ALL_POINTS': - chunks.append(camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) + chunks.append(camPathChunk( + [(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) @@ -553,11 +573,13 @@ async def drill(o): center = (cx, cy) aspect = (maxx - minx) / (maxy - miny) if (1.3 > aspect > 0.7 and o.drill_type == 'MIDDLE_SYMETRIC') or o.drill_type == 'MIDDLE_ALL': - chunks.append(camPathChunk([(center[0] + l.x, center[1] + l.y, cz + l.z)])) + chunks.append(camPathChunk( + [(center[0] + l.x, center[1] + l.y, cz + l.z)])) elif ob.type == 'MESH': for v in ob.data.vertices: - chunks.append(camPathChunk([(v.co.x + l.x, v.co.y + l.y, v.co.z + l.z)])) + chunks.append(camPathChunk( + [(v.co.x + l.x, v.co.y + l.y, v.co.z + l.z)])) delob(ob) # delete temporary object with applied transforms layers = getLayers(o, o.maxz, checkminz(o)) @@ -592,7 +614,7 @@ async def medial_axis(o): simple.remove_multiple("medialMesh") - from cam.voronoi import Site, computeVoronoiDiagram + from .voronoi import Site, computeVoronoiDiagram chunks = [] @@ -617,7 +639,8 @@ async def medial_axis(o): elif o.cutter_type == 'BALLNOSE': maxdepth = - new_cutter_diameter / 2 - o.skin else: - raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.") + raise CamException( + "Only Ballnose and V-carve cutters are supported for meial axis.") # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] @@ -638,7 +661,8 @@ async def medial_axis(o): # just a multipolygon mpoly = polys else: - raise CamException("Failed getting object silhouette. Is input curve closed?") + raise CamException( + "Failed getting object silhouette. Is input curve closed?") mpoly_boundary = mpoly.boundary ipol = 0 @@ -700,7 +724,8 @@ async def medial_axis(o): vertr.append((False, newIdx)) if o.cutter_type == 'VCARVE': # start the z depth calc from the "start depth" of the operation. - z = o.maxz - mpoly.boundary.distance(sgeometry.Point(p)) * slope + z = o.maxz - \ + mpoly.boundary.distance(sgeometry.Point(p)) * slope if z < maxdepth: z = maxdepth elif o.cutter_type == 'BALL' or o.cutter_type == 'BALLNOSE': @@ -796,11 +821,13 @@ def getLayers(operation, startdepth, enddepth): if operation.use_layers: layers = [] n = math.ceil((startdepth - enddepth) / operation.stepdown) - print("start " + str(startdepth) + " end " + str(enddepth) + " n " + str(n)) + print("start " + str(startdepth) + " end " + + str(enddepth) + " n " + str(n)) layerstart = operation.maxz for x in range(0, n): - layerend = round(max(startdepth - ((x + 1) * operation.stepdown), enddepth), 6) + layerend = round( + max(startdepth - ((x + 1) * operation.stepdown), enddepth), 6) if int(layerstart * 10 ** 8) != int(layerend * 10 ** 8): # it was possible that with precise same end of operation, # last layer was done 2x on exactly same level... @@ -823,7 +850,8 @@ def chunksToMesh(chunks, o): if o.machine_axes == '3': if m.use_position_definitions: - origin = (m.starting_position.x, m.starting_position.y, m.starting_position.z) # dhull + origin = (m.starting_position.x, m.starting_position.y, + m.starting_position.z) # dhull else: origin = (0, 0, free_height) @@ -891,7 +919,8 @@ def chunksToMesh(chunks, o): or (o.machine_axes == '4' and vect.length < o.dist_between_paths * 2.5): # case of neighbouring paths lift = False - if abs(vect.x) < e and abs(vect.y) < e: # case of stepdown by cutting. + # case of stepdown by cutting. + if abs(vect.x) < e and abs(vect.y) < e: lift = False if lift: diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 0ad5bd99b..7842d1b22 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -22,8 +22,8 @@ import sys import bpy -from cam import simple, utils -from cam.simple import * +from . import simple, utils +from .simple import * def addTestCurve(loc): @@ -42,12 +42,14 @@ def addTestCurve(loc): def addTestMesh(loc): - bpy.ops.mesh.primitive_monkey_add(radius=.01, align='WORLD', enter_editmode=False, location=loc) + bpy.ops.mesh.primitive_monkey_add( + radius=.01, align='WORLD', enter_editmode=False, location=loc) bpy.ops.transform.rotate(value=-1.5708, axis=(1, 0, 0), constraint_axis=(True, False, False), orient_type='GLOBAL') bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.editmode_toggle() - bpy.ops.mesh.primitive_plane_add(radius=1, align='WORLD', enter_editmode=False, location=loc) + bpy.ops.mesh.primitive_plane_add( + radius=1, align='WORLD', enter_editmode=False, location=loc) bpy.ops.transform.resize(value=(0.01, 0.01, 0.01), constraint_axis=(False, False, False), orient_type='GLOBAL') bpy.ops.transform.translate(value=(-0.01, 0, 0), constraint_axis=(True, False, False), @@ -161,7 +163,8 @@ def testOperation(i): if v1.co != v2.co: different_co_count += 1 if different_co_count > 0: - report += 'vertex position is different on %i vertices \n\n' % (different_co_count) + report += 'vertex position is different on %i vertices \n\n' % ( + different_co_count) test_ok = False if test_ok: report += 'test ok\n\n' diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index ba094b310..8034cbd57 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -21,35 +21,39 @@ import sys import bpy -from bpy.types import UIList, Operator from bpy_extras.io_utils import ImportHelper -from bpy.props import (StringProperty, - BoolProperty, - PointerProperty, - FloatProperty, - ) - -from bpy.types import (Panel, Menu, Operator, PropertyGroup, ) - -from cam import gcodeimportparser, simple -from cam.simple import * - -from cam.ui_panels.buttons_panel import CAMButtonsPanel -from cam.ui_panels.interface import * -from cam.ui_panels.info import * -from cam.ui_panels.operations import * -from cam.ui_panels.cutter import * -from cam.ui_panels.machine import * -from cam.ui_panels.material import * -from cam.ui_panels.chains import * -from cam.ui_panels.op_properties import * -from cam.ui_panels.movement import * -from cam.ui_panels.feedrate import * -from cam.ui_panels.optimisation import * -from cam.ui_panels.area import * -from cam.ui_panels.gcode import * -from cam.ui_panels.pack import * -from cam.ui_panels.slice import * +from bpy.props import ( + StringProperty, +) +from bpy.types import ( + Panel, + Menu, + Operator, + UIList +) + +from . import ( + gcodeimportparser, + simple +) +from .simple import * + +from .ui_panels.buttons_panel import CAMButtonsPanel +from .ui_panels.interface import * +from .ui_panels.info import * +from .ui_panels.operations import * +from .ui_panels.cutter import * +from .ui_panels.machine import * +from .ui_panels.material import * +from .ui_panels.chains import * +from .ui_panels.op_properties import * +from .ui_panels.movement import * +from .ui_panels.feedrate import * +from .ui_panels.optimisation import * +from .ui_panels.area import * +from .ui_panels.gcode import * +from .ui_panels.pack import * +from .ui_panels.slice import * class CAM_UL_orientations(UIList): diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index 4f5b7db22..4a095f382 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -52,7 +52,8 @@ def draw_minz(self): return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: if self.op.strategy == 'CURVE': - self.layout.label(text="cannot use depth from object using CURVES") + self.layout.label( + text="cannot use depth from object using CURVES") row = self.layout.row(align=True) row.label(text='Set max depth from') @@ -66,8 +67,10 @@ def draw_minz(self): if self.op.source_image_name != '': i = bpy.data.images[self.op.source_image_name] if i is not None: - sy = int((self.op.source_image_size_x / i.size[0]) * i.size[1] * 1000000) / 1000 - self.layout.label(text='image size on y axis: ' + strInUnits(sy, 8)) + sy = int((self.op.source_image_size_x / + i.size[0]) * i.size[1] * 1000000) / 1000 + self.layout.label( + text='image size on y axis: ' + strInUnits(sy, 8)) self.layout.separator() self.layout.prop(self.op, 'source_image_offset') col = self.layout.column(align=True) @@ -93,7 +96,8 @@ def draw_limit_curve(self): if self.op.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: self.layout.prop(self.op, 'use_limit_curve') if self.op.use_limit_curve: - self.layout.prop_search(self.op, "limit_curve", bpy.data, "objects") + self.layout.prop_search( + self.op, "limit_curve", bpy.data, "objects") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index fdd61e414..260821d5b 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -1,9 +1,9 @@ import bpy from bpy.types import UIList -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel -import cam +from ..utils import isChainValid class CAM_UL_operations(UIList): @@ -47,7 +47,8 @@ def draw(self, context): row = layout.row() scene = bpy.context.scene - row.template_list("CAM_UL_chains", '', scene, "cam_chains", scene, 'cam_active_chain') + row.template_list("CAM_UL_chains", '', scene, + "cam_chains", scene, 'cam_active_chain') col = row.column(align=True) col.operator("scene.cam_chain_add", icon='ADD', text="") col.operator("scene.cam_chain_remove", icon='REMOVE', text="") @@ -60,21 +61,28 @@ def draw(self, context): row.template_list("CAM_UL_operations", '', chain, "operations", chain, 'active_operation') col = row.column(align=True) - col.operator("scene.cam_chain_operation_add", icon='ADD', text="") - col.operator("scene.cam_chain_operation_remove", icon='REMOVE', text="") + col.operator("scene.cam_chain_operation_add", + icon='ADD', text="") + col.operator("scene.cam_chain_operation_remove", + icon='REMOVE', text="") if len(chain.operations) > 0: - col.operator("scene.cam_chain_operation_up", icon='TRIA_UP', text="") - col.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="") + col.operator("scene.cam_chain_operation_up", + icon='TRIA_UP', text="") + col.operator("scene.cam_chain_operation_down", + icon='TRIA_DOWN', text="") if not chain.computing: layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode") - layout.operator("object.cam_export_paths_chain", text="Export chain gcode") - layout.operator("object.cam_simulate_chain", text="Simulate this chain") + layout.operator("object.cam_export_paths_chain", + text="Export chain gcode") + layout.operator("object.cam_simulate_chain", + text="Simulate this chain") - valid, reason = cam.isChainValid(chain, context) + valid, reason = isChainValid(chain, context) if not valid: - layout.label(icon="ERROR", text=f"Can't compute chain - reason:\n") + layout.label( + icon="ERROR", text=f"Can't compute chain - reason:\n") layout.label(text=reason) else: layout.label(text='chain is currently computing') diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index a74fa7da7..1a62723ef 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_CUTTER_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -30,9 +30,11 @@ def draw_cutter_preset_menu(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_CUTTER_MT_presets", text=bpy.types.CAM_CUTTER_MT_presets.bl_label) + row.menu("CAM_CUTTER_MT_presets", + text=bpy.types.CAM_CUTTER_MT_presets.bl_label) row.operator("render.cam_preset_cutter_add", text="", icon='ADD') - row.operator("render.cam_preset_cutter_add", text="", icon='REMOVE').remove_active = True + row.operator("render.cam_preset_cutter_add", text="", + icon='REMOVE').remove_active = True def draw_cutter_id(self): if not self.has_correct_level(): @@ -97,7 +99,8 @@ def draw_custom(self): text='Warning - only convex shapes are supported. ', icon='COLOR_RED') self.layout.label(text='If your custom cutter is concave,') self.layout.label(text='switch exact mode off.') - self.layout.prop_search(self.op, "cutter_object_name", bpy.data, "objects") + self.layout.prop_search( + self.op, "cutter_object_name", bpy.data, "objects") def draw_cutter_diameter(self): if not self.has_correct_level(): @@ -124,9 +127,11 @@ def draw_engagement(self): return if self.op.cutter_type in ['BALLCONE']: - engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) + engagement = round( + 100 * self.op.dist_between_paths / self.op.ball_radius, 1) else: - engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) + engagement = round( + 100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) self.layout.label(text=f"Cutter engagement: {engagement}%") diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index 88cdf330b..a1a410d82 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -1,5 +1,5 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_FEEDRATE_Panel(CAMButtonsPanel, bpy.types.Panel): diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index 9e1b20b48..d802f63e0 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_GCODE_Panel(CAMButtonsPanel, bpy.types.Panel): diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index 5e0730366..c96efb769 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -1,12 +1,21 @@ import bpy -from bpy.props import StringProperty -from bpy.props import FloatProperty - -from cam.simple import strInUnits -from cam.ui_panels.buttons_panel import CAMButtonsPanel -import cam.utils -import cam.constants -from cam.version import __version__ as cam_version +from bpy.props import ( + StringProperty, + FloatProperty +) + +from .buttons_panel import CAMButtonsPanel +from ..utils import ( + update_operation, + opencamlib_version +) +from ..constants import ( + PRECISION, + CHIPLOAD_PRECISION, + MAX_OPERATION_TIME, +) +from ..version import __version__ as cam_version +from ..simple import strInUnits # Info panel # This panel gives general information about the current operation @@ -18,19 +27,19 @@ class CAM_INFO_Properties(bpy.types.PropertyGroup): name='warnings', description='warnings', default='', - update=cam.utils.update_operation, + update=update_operation, ) chipload: FloatProperty( name="chipload", description="Calculated chipload", default=0.0, unit='LENGTH', - precision=cam.constants.CHIPLOAD_PRECISION, + precision=CHIPLOAD_PRECISION, ) duration: FloatProperty( name="Estimated time", default=0.01, min=0.0000, - max=cam.constants.MAX_OPERATION_TIME, - precision=cam.constants.PRECISION, + max=MAX_OPERATION_TIME, + precision=PRECISION, unit="TIME", ) @@ -66,12 +75,12 @@ def draw_blendercam_version(self): def draw_opencamlib_version(self): if not self.has_correct_level(): return - opencamlib_version = cam.utils.opencamlib_version() - if opencamlib_version is None: + ocl_version = opencamlib_version() + if ocl_version is None: self.layout.label(text="Opencamlib is not installed") else: self.layout.label( - text=f"Opencamlib v{opencamlib_version} installed") + text=f"Opencamlib v{ocl_version} installed") # Display warnings related to the current operation def draw_op_warnings(self): diff --git a/scripts/addons/cam/ui_panels/interface.py b/scripts/addons/cam/ui_panels/interface.py index d4e1f9e94..6c158929e 100644 --- a/scripts/addons/cam/ui_panels/interface.py +++ b/scripts/addons/cam/ui_panels/interface.py @@ -2,9 +2,7 @@ from bpy.props import EnumProperty import math -from cam.ui_panels.buttons_panel import CAMButtonsPanel -import cam.utils -import cam.constants +from .buttons_panel import CAMButtonsPanel def update_interface(self, context): @@ -17,10 +15,12 @@ class CAM_INTERFACE_Properties(bpy.types.PropertyGroup): level: EnumProperty( name="Interface", description="Choose visible options", - items=[('0', "Basic", "Only show essential options"), - ('1', "Advanced", "Show advanced options"), - ('2', "Complete", "Show all options"), - ('3', "Experimental", "Show experimental options")], + items=[ + ('0', "Basic", "Only show essential options"), + ('1', "Advanced", "Show advanced options"), + ('2', "Complete", "Show all options"), + ('3', "Experimental", "Show experimental options") + ], default='0', update=update_interface, ) diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index 6ffe2c9f3..9ed830e7c 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_MACHINE_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -30,9 +30,11 @@ def draw_presets(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_MACHINE_MT_presets", text=bpy.types.CAM_MACHINE_MT_presets.bl_label) + row.menu("CAM_MACHINE_MT_presets", + text=bpy.types.CAM_MACHINE_MT_presets.bl_label) row.operator("render.cam_preset_machine_add", text="", icon='ADD') - row.operator("render.cam_preset_machine_add", text="", icon='REMOVE').remove_active = True + row.operator("render.cam_preset_machine_add", text="", + icon='REMOVE').remove_active = True def draw_post_processor(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 3bb9f0555..7f218fc71 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -1,12 +1,17 @@ import bpy -from bpy.props import BoolProperty -from bpy.props import EnumProperty -from bpy.props import FloatProperty -from bpy.props import FloatVectorProperty +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, +) -from cam.ui_panels.buttons_panel import CAMButtonsPanel -import cam.utils -import cam.constants +from .buttons_panel import CAMButtonsPanel +from ..utils import ( + update_material, + positionObject +) +from ..constants import PRECISION class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): @@ -15,31 +20,36 @@ class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): name="Estimate cut area from model", description="Estimate cut area based on model geometry", default=True, - update=cam.utils.update_material, + update=update_material, ) radius_around_model: FloatProperty( name='Radius around model', description="Increase cut area around the model on X and " "Y by this amount", - default=0.0, unit='LENGTH', precision=cam.constants.PRECISION, - update=cam.utils.update_material, + default=0.0, + unit='LENGTH', + precision=PRECISION, + update=update_material, ) center_x: BoolProperty( name="Center on X axis", description="Position model centered on X", - default=False, update=cam.utils.update_material, + default=False, + update=update_material, ) center_y: BoolProperty( name="Center on Y axis", description="Position model centered on Y", - default=False, update=cam.utils.update_material, + default=False, + update=update_material, ) z_position: EnumProperty( - name="Z placement", items=( + name="Z placement", + items=( ('ABOVE', 'Above', 'Place object vertically above the XY plane'), ('BELOW', 'Below', 'Place object vertically below the XY plane'), ('CENTERED', 'Centered', @@ -47,14 +57,17 @@ class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): ), description="Position below Zero", default='BELOW', - update=cam.utils.update_material, + update=update_material, ) # material_origin origin: FloatVectorProperty( - name='Material origin', default=(0, 0, 0), unit='LENGTH', - precision=cam.constants.PRECISION, subtype="XYZ", - update=cam.utils.update_material, + name='Material origin', + default=(0, 0, 0), + unit='LENGTH', + precision=PRECISION, + subtype="XYZ", + update=update_material, ) # material_size @@ -63,9 +76,9 @@ class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): default=(0.200, 0.200, 0.100), min=0, unit='LENGTH', - precision=cam.constants.PRECISION, + precision=PRECISION, subtype="XYZ", - update=cam.utils.update_material, + update=update_material, ) @@ -82,7 +95,7 @@ def execute(self, context): s = bpy.context.scene operation = s.cam_operations[s.cam_active_operation] if operation.object_name in bpy.data.objects: - cam.utils.positionObject(operation) + positionObject(operation) else: print('no object assigned') return {'FINISHED'} diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index df32b6f04..b66db7228 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -1,12 +1,17 @@ import bpy -from bpy.props import BoolProperty -from bpy.props import EnumProperty -from bpy.props import FloatProperty +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, +) import math -from cam.ui_panels.buttons_panel import CAMButtonsPanel -import cam.utils -import cam.constants +from .buttons_panel import CAMButtonsPanel +from ..utils import update_operation +from ..constants import ( + PRECISION, + G64_INCOMPATIBLE_MACHINES +) class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): @@ -24,7 +29,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ), description='movement type', default='CLIMB', - update=cam.utils.update_operation, + update=update_operation, ) insideout: EnumProperty( @@ -35,7 +40,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ), description='approach to the piece', default='INSIDEOUT', - update=cam.utils.update_operation, + update=update_operation, ) spindle_rotation: EnumProperty( @@ -46,7 +51,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ), description='Spindle rotation direction', default='CW', - update=cam.utils.update_operation, + update=update_operation, ) free_height: FloatProperty( @@ -54,9 +59,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): default=0.01, min=0.0000, max=32, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) useG64: BoolProperty( @@ -64,7 +69,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): description='Use only if your machine supports ' 'G64 code. LinuxCNC and Mach3 do', default=False, - update=cam.utils.update_operation, + update=update_operation, ) G64: FloatProperty( @@ -72,9 +77,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): default=0.0001, min=0.0000, max=0.005, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) parallel_step_back: BoolProperty( @@ -83,14 +88,14 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): 'material in climb mode, then steps back and goes ' 'between 2 last chunks back', default=False, - update=cam.utils.update_operation, + update=update_operation, ) helix_enter: BoolProperty( name="Helix enter - EXPERIMENTAL", description="Enter material in helix", default=False, - update=cam.utils.update_operation, + update=update_operation, ) ramp_in_angle: FloatProperty( @@ -101,7 +106,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): precision=1, subtype="ANGLE", unit="ROTATION", - update=cam.utils.update_operation, + update=update_operation, ) helix_diameter: FloatProperty( @@ -111,7 +116,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): max=100, precision=1, subtype='PERCENTAGE', - update=cam.utils.update_operation, + update=update_operation, ) ramp: BoolProperty( @@ -119,14 +124,14 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): description="Ramps down the whole contour, so the cutline looks " "like helix", default=False, - update=cam.utils.update_operation, + update=update_operation, ) ramp_out: BoolProperty( name="Ramp out - EXPERIMENTAL", description="Ramp out to not leave mark on surface", default=False, - update=cam.utils.update_operation, + update=update_operation, ) ramp_out_angle: FloatProperty( @@ -137,14 +142,14 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): precision=1, subtype="ANGLE", unit="ROTATION", - update=cam.utils.update_operation, + update=update_operation, ) retract_tangential: BoolProperty( name="Retract tangential - EXPERIMENTAL", description="Retract from material in circular motion", default=False, - update=cam.utils.update_operation, + update=update_operation, ) retract_radius: FloatProperty( @@ -152,9 +157,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): default=0.001, min=0.000001, max=100, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) retract_height: FloatProperty( @@ -162,15 +167,15 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): default=0.001, min=0.00000, max=100, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) stay_low: BoolProperty( name="Stay low if possible", default=True, - update=cam.utils.update_operation, + update=update_operation, ) merge_dist: FloatProperty( @@ -178,16 +183,16 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): default=0.0, min=0.0000, max=0.1, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) protect_vertical: BoolProperty( name="Protect vertical", description="The path goes only vertically next to steep areas", default=True, - update=cam.utils.update_operation, + update=update_operation, ) protect_vertical_limit: FloatProperty( @@ -199,7 +204,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): precision=0, subtype="ANGLE", unit="ROTATION", - update=cam.utils.update_operation, + update=update_operation, ) @@ -245,7 +250,7 @@ def draw_free_height(self): def draw_use_g64(self): if not self.has_correct_level(): return - if self.context.scene.cam_machine.post_processor not in cam.constants.G64_INCOMPATIBLE_MACHINES: + if self.context.scene.cam_machine.post_processor not in G64_INCOMPATIBLE_MACHINES: self.layout.prop(self.op.movement, 'useG64') if self.op.movement.useG64: self.layout.prop(self.op.movement, 'G64') diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index 05f343bb0..8f0d786ac 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, bpy.types.Panel): @@ -38,9 +38,11 @@ def draw_cutter_engagement(self): return if self.op.cutter_type in ['BALLCONE']: - engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) + engagement = round( + 100 * self.op.dist_between_paths / self.op.ball_radius, 1) else: - engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) + engagement = round( + 100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) if engagement > 50: self.layout.label(text="Warning: High cutter engagement") @@ -200,9 +202,11 @@ def draw_bridges_options(self): if self.op.use_bridges: self.layout.prop(self.op, 'bridges_width') self.layout.prop(self.op, 'bridges_height') - self.layout.prop_search(self.op, "bridges_collection_name", bpy.data, "collections") + self.layout.prop_search( + self.op, "bridges_collection_name", bpy.data, "collections") self.layout.prop(self.op, 'use_bridge_modifiers') - self.layout.operator("scene.cam_bridges_add", text="Autogenerate bridges") + self.layout.operator("scene.cam_bridges_add", + text="Autogenerate bridges") def draw_skin(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index 3ac0a0ede..1baed37dc 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel # Operations panel # This panel displays the list of operations created by the user @@ -41,8 +41,10 @@ def draw_operations_list(self): col.operator("scene.cam_operation_copy", icon='COPYDOWN', text="") col.operator("scene.cam_operation_remove", icon='REMOVE', text="") col.separator() - col.operator("scene.cam_operation_move", icon='TRIA_UP', text="").direction = 'UP' - col.operator("scene.cam_operation_move", icon='TRIA_DOWN', text="").direction = 'DOWN' + col.operator("scene.cam_operation_move", + icon='TRIA_UP', text="").direction = 'UP' + col.operator("scene.cam_operation_move", + icon='TRIA_DOWN', text="").direction = 'DOWN' # Draw the list of preset operations, and preset add and remove buttons @@ -50,9 +52,11 @@ def draw_presets(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_OPERATION_MT_presets", text=bpy.types.CAM_OPERATION_MT_presets.bl_label) + row.menu("CAM_OPERATION_MT_presets", + text=bpy.types.CAM_OPERATION_MT_presets.bl_label) row.operator("render.cam_preset_operation_add", text="", icon='ADD') - row.operator("render.cam_preset_operation_add", text="", icon='REMOVE').remove_active = True + row.operator("render.cam_preset_operation_add", text="", + icon='REMOVE').remove_active = True def draw_calculate_path(self): if not self.has_correct_level(): @@ -64,9 +68,11 @@ def draw_calculate_path(self): self.layout.prop(self.op.movement, 'free_height') if not self.op.valid: - self.layout.label(text="Select a valid object to calculate the path.") + self.layout.label( + text="Select a valid object to calculate the path.") # will be disable if not valid - self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") + self.layout.operator("object.calculate_cam_path", + text="Calculate path & export Gcode") def draw_export_gcode(self): if not self.has_correct_level(): @@ -75,13 +81,15 @@ def draw_export_gcode(self): if self.op.name is not None: name = f"cam_path_{self.op.name}" if bpy.context.scene.objects.get(name) is not None: - self.layout.operator("object.cam_export", text="Export Gcode ") + self.layout.operator( + "object.cam_export", text="Export Gcode ") def draw_simulate_op(self): if not self.has_correct_level(): return if self.op.valid: - self.layout.operator("object.cam_simulate", text="Simulate this operation") + self.layout.operator("object.cam_simulate", + text="Simulate this operation") def draw_op_name(self): if not self.has_correct_level(): @@ -104,26 +112,33 @@ def draw_operation_source(self): if self.op.strategy == 'CURVE': if self.op.geometry_source == 'OBJECT': - self.layout.prop_search(self.op, "object_name", bpy.data, "objects") + self.layout.prop_search( + self.op, "object_name", bpy.data, "objects") elif self.op.geometry_source == 'COLLECTION': - self.layout.prop_search(self.op, "collection_name", bpy.data, "collections") + self.layout.prop_search( + self.op, "collection_name", bpy.data, "collections") else: if self.op.geometry_source == 'OBJECT': - self.layout.prop_search(self.op, "object_name", bpy.data, "objects") + self.layout.prop_search( + self.op, "object_name", bpy.data, "objects") if self.op.enable_A: self.layout.prop(self.op, 'rotation_A') if self.op.enable_B: self.layout.prop(self.op, 'rotation_B') elif self.op.geometry_source == 'COLLECTION': - self.layout.prop_search(self.op, "collection_name", bpy.data, "collections") + self.layout.prop_search( + self.op, "collection_name", bpy.data, "collections") else: - self.layout.prop_search(self.op, "source_image_name", bpy.data, "images") + self.layout.prop_search( + self.op, "source_image_name", bpy.data, "images") if self.op.strategy in ['CARVE', 'PROJECTED_CURVE']: - self.layout.prop_search(self.op, "curve_object", bpy.data, "objects") + self.layout.prop_search( + self.op, "curve_object", bpy.data, "objects") if self.op.strategy == 'PROJECTED_CURVE': - self.layout.prop_search(self.op, "curve_object1", bpy.data, "objects") + self.layout.prop_search( + self.op, "curve_object1", bpy.data, "objects") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index 298fbc825..ae1ac28e9 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -1,11 +1,19 @@ import bpy -from bpy.props import BoolProperty -from bpy.props import FloatProperty -from bpy.props import IntProperty - -from cam.ui_panels.buttons_panel import CAMButtonsPanel -import cam.utils -import cam.constants +from bpy.props import ( + BoolProperty, + FloatProperty, + IntProperty, +) + +from .buttons_panel import CAMButtonsPanel +from ..utils import ( + update_operation, + update_exact_mode, + update_zbuffer_image, + update_opencamlib, + opencamlib_version, +) +from ..constants import PRECISION class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): @@ -14,7 +22,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): name="Reduce path points", description="Reduce path points", default=True, - update=cam.utils.update_operation, + update=update_operation, ) optimize_threshold: FloatProperty( @@ -23,7 +31,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): min=0.000000001, max=1000, precision=20, - update=cam.utils.update_operation, + update=update_operation, ) use_exact: BoolProperty( @@ -31,7 +39,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): description="Exact mode allows greater precision, but is slower " "with complex meshes", default=True, - update=cam.utils.update_exact_mode, + update=update_exact_mode, ) imgres_limit: IntProperty( @@ -41,7 +49,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): max=512, description="Limits total memory usage and prevents crashes. " "Increase it if you know what are doing", - update=cam.utils.update_zbuffer_image, + update=update_zbuffer_image, ) pixsize: FloatProperty( @@ -49,16 +57,16 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): default=0.0001, min=0.00001, max=0.1, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_zbuffer_image, + update=update_zbuffer_image, ) use_opencamlib: BoolProperty( name="Use OpenCAMLib", description="Use OpenCAMLib to sample paths or get waterline shape", default=False, - update=cam.utils.update_opencamlib, + update=update_opencamlib, ) exact_subdivide_edges: BoolProperty( @@ -66,7 +74,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): description="This can avoid some collision issues when " "importing CAD models", default=False, - update=cam.utils.update_exact_mode, + update=update_exact_mode, ) circle_detail: IntProperty( @@ -74,7 +82,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): default=64, min=12, max=512, - update=cam.utils.update_operation, + update=update_operation, ) simulation_detail: FloatProperty( @@ -82,9 +90,9 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): default=0.0002, min=0.00001, max=0.01, - precision=cam.constants.PRECISION, + precision=PRECISION, unit="LENGTH", - update=cam.utils.update_operation, + update=update_operation, ) @@ -135,9 +143,9 @@ def draw_use_opencamlib(self): if not (self.exact_possible and self.op.optimisation.use_exact): return - opencamlib_version = cam.utils.opencamlib_version() + ocl_version = opencamlib_version() - if opencamlib_version is None: + if ocl_version is None: self.layout.label(text="Opencamlib is not available ") self.layout.prop(self.op.optimisation, 'exact_subdivide_edges') else: diff --git a/scripts/addons/cam/ui_panels/pack.py b/scripts/addons/cam/ui_panels/pack.py index 6dc48b86f..acda772f1 100644 --- a/scripts/addons/cam/ui_panels/pack.py +++ b/scripts/addons/cam/ui_panels/pack.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_PACK_Panel(CAMButtonsPanel, bpy.types.Panel): diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index b8fab4c78..6f42868d3 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -1,6 +1,6 @@ import bpy -from cam.ui_panels.buttons_panel import CAMButtonsPanel +from .buttons_panel import CAMButtonsPanel class CAM_SLICE_Panel(CAMButtonsPanel, bpy.types.Panel): diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 3314de7ce..fd039255a 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -28,23 +28,27 @@ import math from math import * from mathutils import * -from bpy.props import * from bpy_extras import object_utils import sys import numpy import pickle -from cam.chunk import * -from cam.collision import * -from cam.simple import * -from cam.pattern import * -from cam.polygon_utils_cam import * -from cam.image_utils import * -from cam.exception import * - -from cam.async_op import progress_async -from cam.opencamlib.opencamlib import oclSample, oclSamplePoints, oclResampleChunks, oclGetWaterline +from .chunk import * +from .collision import * +from .simple import * +from .pattern import * +from .polygon_utils_cam import * +from .image_utils import * +from .exception import * + +from .async_op import progress_async +from .opencamlib.opencamlib import ( + oclSample, + oclSamplePoints, + oclResampleChunks, + oclGetWaterline +) from shapely.geometry import polygon as spolygon from shapely.geometry import MultiPolygon @@ -55,37 +59,6 @@ SHAPELY = True -# The following functions are temporary -# until all content in __init__.py is cleaned up - -def update_material(self, context): - addMaterialAreaObject() - - -def update_operation(self, context): - from . import updateRest - active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] - updateRest(active_op, bpy.context) - - -def update_exact_mode(self, context): - from . import updateExact - active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] - updateExact(active_op, bpy.context) - - -def update_opencamlib(self, context): - from . import updateOpencamlib - active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] - updateOpencamlib(active_op, bpy.context) - - -def update_zbuffer_image(self, context): - from . import updateZbufferImage - active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] - updateZbufferImage(active_op, bpy.context) - - # Import OpencamLib # Return available OpenCamLib version on success, None otherwise def opencamlib_version(): @@ -105,7 +78,8 @@ def positionObject(operation): ob.select_set(True) bpy.context.view_layer.objects.active = ob - minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace([ob], operation.use_modifiers) + minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace( + [ob], operation.use_modifiers) totx = maxx - minx toty = maxy - miny totz = maxz - minz @@ -127,7 +101,8 @@ def positionObject(operation): ob.location.z -= minz + totz / 2 if ob.type != 'CURVE': - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply( + location=True, rotation=False, scale=False) # addMaterialAreaObject() @@ -181,7 +156,8 @@ def getBoundsWorldspace(obs, use_modifiers=False): bpy.ops.outliner.orphans_purge() else: if not hasattr(ob.data, "splines"): - raise CamException("Can't do CAM operation on the selected object type") + raise CamException( + "Can't do CAM operation on the selected object type") # for coord in bb: for c in ob.data.splines: for p in c.bezier_points: @@ -286,7 +262,8 @@ def getBounds(o): # print('kolikrat sem rpijde') if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION' or o.geometry_source == 'CURVE': print("valid geometry") - minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace(o.objects, o.use_modifiers) + minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace( + o.objects, o.use_modifiers) if o.minz_from == 'OBJECT': if minz == 10000000: @@ -391,14 +368,18 @@ def samplePathLow(o, ch1, ch2, dosample): cutterdepth = o.cutter_shape.dimensions.z / 2 for p in bpath_points: - z = getSampleBullet(o.cutter_shape, p[0], p[1], cutterdepth, 1, o.minz) + z = getSampleBullet( + o.cutter_shape, p[0], p[1], cutterdepth, 1, o.minz) if z > p[2]: p[2] = z else: for p in bpath_points: - xs = (p[0] - o.min.x) / pixsize + o.borderwidth + pixsize / 2 # -m - ys = (p[1] - o.min.y) / pixsize + o.borderwidth + pixsize / 2 # -m - z = getSampleImage((xs, ys), o.offset_image, o.minz) + o.skin + xs = (p[0] - o.min.x) / pixsize + \ + o.borderwidth + pixsize / 2 # -m + ys = (p[1] - o.min.y) / pixsize + \ + o.borderwidth + pixsize / 2 # -m + z = getSampleImage( + (xs, ys), o.offset_image, o.minz) + o.skin if z > p[2]: p[2] = z return camPathChunk(bpath_points) @@ -424,7 +405,8 @@ async def sampleChunks(o, pathSamples, layers): cutter = o.cutter_shape cutterdepth = cutter.dimensions.z / 2 else: - if o.strategy != 'WATERLINE': # or prepare offset image, but not in some strategies. + # or prepare offset image, but not in some strategies. + if o.strategy != 'WATERLINE': await prepareArea(o) pixsize = o.optimisation.pixsize @@ -476,7 +458,8 @@ async def sampleChunks(o, pathSamples, layers): # for t in range(0,threads): our_points = patternchunk.get_points_np() - ambient_contains = shapely.contains(o.ambient, shapely.points(our_points[:, 0:2])) + ambient_contains = shapely.contains( + o.ambient, shapely.points(our_points[:, 0:2])) for s, in_ambient in zip(our_points, ambient_contains): if o.strategy != 'WATERLINE' and int(100 * n / totlen) != last_percent: last_percent = int(100 * n / totlen) @@ -556,7 +539,8 @@ async def sampleChunks(o, pathSamples, layers): v1 = lastsample v2 = newsample if o.movement.protect_vertical: - v1, v2 = isVerticalLimit(v1, v2, o.movement.protect_vertical_limit) + v1, v2 = isVerticalLimit( + v1, v2, o.movement.protect_vertical_limit) v1 = Vector(v1) v2 = Vector(v2) # print(v1,v2) @@ -571,12 +555,16 @@ async def sampleChunks(o, pathSamples, layers): layeractivechunks[ls].points.insert(-1, betweensample.to_tuple()) else: - layeractivechunks[ls].points.append(betweensample.to_tuple()) - layeractivechunks[ls + 1].points.append(betweensample.to_tuple()) + layeractivechunks[ls].points.append( + betweensample.to_tuple()) + layeractivechunks[ls + + 1].points.append(betweensample.to_tuple()) else: # print(v1,v2,betweensample,lastlayer,currentlayer) - layeractivechunks[ls].points.insert(-1, betweensample.to_tuple()) - layeractivechunks[ls + 1].points.insert(0, betweensample.to_tuple()) + layeractivechunks[ls].points.insert( + -1, betweensample.to_tuple()) + layeractivechunks[ls + 1].points.insert( + 0, betweensample.to_tuple()) li += 1 # this chunk is terminated, and allready in layerchunks / @@ -693,7 +681,8 @@ async def sampleChunksNAxis(o, pathSamples, layers): # for t in range(0,threads): # print(len(patternchunk.startpoints),len( patternchunk.endpoints)) spl = len(patternchunk.startpoints) - for si in range(0, spl): # ,startp in enumerate(patternchunk.startpoints): + # ,startp in enumerate(patternchunk.startpoints): + for si in range(0, spl): # #TODO: seems we are writing into the source chunk , # and that is why we need to write endpoints everywhere too? @@ -721,18 +710,22 @@ async def sampleChunksNAxis(o, pathSamples, layers): if o.cutter_type == 'VCARVE': # Bullet cone is always pointing Up Z in the object cutter.rotation_euler.x += pi cutter.update_tag() - bpy.context.scene.frame_set(1) # this has to be :( it resets the rigidbody world. + # this has to be :( it resets the rigidbody world. + bpy.context.scene.frame_set(1) # No other way to update it probably now :( - bpy.context.scene.frame_set(2) # actually 2 frame jumps are needed. + # actually 2 frame jumps are needed. + bpy.context.scene.frame_set(2) bpy.context.scene.frame_set(0) - newsample = getSampleBulletNAxis(cutter, startp, endp, rotation, cutterdepth) + newsample = getSampleBulletNAxis( + cutter, startp, endp, rotation, cutterdepth) # print('totok',startp,endp,rotation,newsample) ################################ # handling samples ############################################ - if newsample is not None: # this is weird, but will leave it this way now.. just prototyping here. + # this is weird, but will leave it this way now.. just prototyping here. + if newsample is not None: sampled = True else: # TODO: why was this here? newsample = startp @@ -773,9 +766,11 @@ async def sampleChunksNAxis(o, pathSamples, layers): for ls in r: splitdistance = layers[ls][1] - ratio = (splitdistance - lastdistance) / (distance - lastdistance) + ratio = (splitdistance - lastdistance) / \ + (distance - lastdistance) # print(ratio) - betweensample = lastsample + (newsample - lastsample) * ratio + betweensample = lastsample + \ + (newsample - lastsample) * ratio # this probably doesn't work at all!!!! check this algoritm> betweenrotation = tuple_add(lastrotation, tuple_mul(tuple_sub(rotation, lastrotation), ratio)) @@ -783,34 +778,54 @@ async def sampleChunksNAxis(o, pathSamples, layers): betweenstartpoint = laststartpoint + \ (startp - laststartpoint) * ratio # here, we need to have also possible endpoints always.. - betweenendpoint = lastendpoint + (endp - lastendpoint) * ratio + betweenendpoint = lastendpoint + \ + (endp - lastendpoint) * ratio if growing: if li > 0: - layeractivechunks[ls].points.insert(-1, betweensample) - layeractivechunks[ls].rotations.insert(-1, betweenrotation) + layeractivechunks[ls].points.insert( + -1, betweensample) + layeractivechunks[ls].rotations.insert( + -1, betweenrotation) layeractivechunks[ls].startpoints.insert( -1, betweenstartpoint) - layeractivechunks[ls].endpoints.insert(-1, betweenendpoint) + layeractivechunks[ls].endpoints.insert( + -1, betweenendpoint) else: - layeractivechunks[ls].points.append(betweensample) - layeractivechunks[ls].rotations.append(betweenrotation) - layeractivechunks[ls].startpoints.append(betweenstartpoint) - layeractivechunks[ls].endpoints.append(betweenendpoint) - layeractivechunks[ls + 1].points.append(betweensample) - layeractivechunks[ls + 1].rotations.append(betweenrotation) - layeractivechunks[ls + 1].startpoints.append(betweenstartpoint) - layeractivechunks[ls + 1].endpoints.append(betweenendpoint) + layeractivechunks[ls].points.append( + betweensample) + layeractivechunks[ls].rotations.append( + betweenrotation) + layeractivechunks[ls].startpoints.append( + betweenstartpoint) + layeractivechunks[ls].endpoints.append( + betweenendpoint) + layeractivechunks[ls + + 1].points.append(betweensample) + layeractivechunks[ls + + 1].rotations.append(betweenrotation) + layeractivechunks[ls + + 1].startpoints.append(betweenstartpoint) + layeractivechunks[ls + + 1].endpoints.append(betweenendpoint) else: - layeractivechunks[ls].points.insert(-1, betweensample) - layeractivechunks[ls].rotations.insert(-1, betweenrotation) - layeractivechunks[ls].startpoints.insert(-1, betweenstartpoint) - layeractivechunks[ls].endpoints.insert(-1, betweenendpoint) - - layeractivechunks[ls + 1].points.append(betweensample) - layeractivechunks[ls + 1].rotations.append(betweenrotation) - layeractivechunks[ls + 1].startpoints.append(betweenstartpoint) - layeractivechunks[ls + 1].endpoints.append(betweenendpoint) + layeractivechunks[ls].points.insert( + -1, betweensample) + layeractivechunks[ls].rotations.insert( + -1, betweenrotation) + layeractivechunks[ls].startpoints.insert( + -1, betweenstartpoint) + layeractivechunks[ls].endpoints.insert( + -1, betweenendpoint) + + layeractivechunks[ls + + 1].points.append(betweensample) + layeractivechunks[ls + + 1].rotations.append(betweenrotation) + layeractivechunks[ls + + 1].startpoints.append(betweenstartpoint) + layeractivechunks[ls + + 1].endpoints.append(betweenendpoint) # layeractivechunks[ls+1].points.insert(0,betweensample) li += 1 @@ -885,7 +900,8 @@ def extendChunks5axis(chunks, o): cutterstart = Vector((m.starting_position.x, m.starting_position.y, max(o.max.z, m.starting_position.z))) # start point for casting else: - cutterstart = Vector((0, 0, max(o.max.z, free_height))) # start point for casting + # start point for casting + cutterstart = Vector((0, 0, max(o.max.z, free_height))) cutterend = Vector((0, 0, o.min.z)) oriname = o.name + ' orientation' ori = s.objects[oriname] @@ -925,8 +941,10 @@ def silhoueteOffset(context, offset, style=1, mitrelimit=1.0): mp = shapely.ops.unary_union(silhs) print("offset attributes:") print(offset, style) - mp = mp.buffer(offset, cap_style=1, join_style=style, resolution=16, mitre_limit=mitrelimit) - shapelyToCurve(ob.name + '_offset_' + str(round(offset, 5)), mp, ob.location.z) + mp = mp.buffer(offset, cap_style=1, join_style=style, + resolution=16, mitre_limit=mitrelimit) + shapelyToCurve(ob.name + '_offset_' + + str(round(offset, 5)), mp, ob.location.z) return {'FINISHED'} @@ -983,7 +1001,8 @@ def polygonConvexHull(context): simple.select_multiple('_tmp') # delete temporary mesh simple.select_multiple('ConvexHull') # delete old hull - points = sgeometry.MultiPoint(coords) # convert coordinates to shapely MultiPoint datastructure + # convert coordinates to shapely MultiPoint datastructure + points = sgeometry.MultiPoint(coords) hull = points.convex_hull shapelyToCurve('ConvexHull', hull, 0.0) @@ -1078,7 +1097,8 @@ def getClosest(o, pos, chunks): ch = None for chtest in chunks: cango = True - for child in chtest.children: # here was chtest.getNext==chtest, was doing recursion error and slowing down. + # here was chtest.getNext==chtest, was doing recursion error and slowing down. + for child in chtest.children: if not child.sorted: cango = False break @@ -1155,7 +1175,8 @@ async def sortChunks(chunks, o, last_pos=None): return sortedchunks -def getVectorRight(lastv, verts): # most right vector from a set regarding angle.. +# most right vector from a set regarding angle.. +def getVectorRight(lastv, verts): defa = 100 v1 = Vector(lastv[0]) v2 = Vector(lastv[1]) @@ -1290,7 +1311,8 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): polys = [] for ob in objects: if use_modifiers: - ob = ob.evaluated_get(bpy.context.evaluated_depsgraph_get()) + ob = ob.evaluated_get( + bpy.context.evaluated_depsgraph_get()) m = ob.to_mesh() else: m = ob.data @@ -1421,7 +1443,8 @@ def addOrientationObject(o): name = o.name + ' orientation' s = bpy.context.scene if s.objects.find(name) == -1: - bpy.ops.object.empty_add(type='ARROWS', align='WORLD', location=(0, 0, 0)) + bpy.ops.object.empty_add( + type='ARROWS', align='WORLD', location=(0, 0, 0)) ob = bpy.context.active_object ob.empty_draw_size = 0.05 @@ -1489,11 +1512,13 @@ def addMachineAreaObject(): o = bpy.context.active_object o.name = 'CAM_machine' o.data.name = 'CAM_machine' - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply( + location=True, rotation=False, scale=False) # o.type = 'SOLID' bpy.ops.object.editmode_toggle() bpy.ops.mesh.delete(type='ONLY_FACE') - bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') bpy.ops.mesh.select_all(action='TOGGLE') bpy.ops.mesh.subdivide(number_cuts=32, smoothness=0, quadcorner='STRAIGHT_CUT', fractal=0, fractal_along_normal=0, seed=0) @@ -1535,7 +1560,8 @@ def addMaterialAreaObject(): o = bpy.context.active_object o.name = 'CAM_material' o.data.name = 'CAM_material' - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply( + location=True, rotation=False, scale=False) # addTranspMat(o, 'blue_transparent', (0.458695, 0.794658, 0.8), 0.1) o.display_type = 'BOUNDS' @@ -1683,7 +1709,8 @@ def rotTo2axes(e, axescombination): # if axes if axescombination == 'CA': v2d = Vector((v.x, v.y)) - a1base = Vector((0, -1)) # ?is this right?It should be vector defining 0 rotation + # ?is this right?It should be vector defining 0 rotation + a1base = Vector((0, -1)) if v2d.length > 0: cangle = a1base.angle_signed(v2d) else: @@ -1696,7 +1723,8 @@ def rotTo2axes(e, axescombination): elif axescombination == 'CB': v2d = Vector((v.x, v.y)) - a1base = Vector((1, 0)) # ?is this right?It should be vector defining 0 rotation + # ?is this right?It should be vector defining 0 rotation + a1base = Vector((1, 0)) if v2d.length > 0: cangle = a1base.angle_signed(v2d) else: @@ -1778,3 +1806,295 @@ def reload_pathss(o): if old_pathmesh is not None: bpy.data.meshes.remove(old_pathmesh) + + +# Moved from init - the following code was moved here to permit the import fix +USE_PROFILER = False + +was_hidden_dict = {} + + +def updateMachine(self, context): + global _IS_LOADING_DEFAULTS + print('update machine ') + if not _IS_LOADING_DEFAULTS: + addMachineAreaObject() + + +def updateMaterial(self, context): + print('update material') + addMaterialAreaObject() + + +def updateOperation(self, context): + scene = context.scene + ao = scene.cam_operations[scene.cam_active_operation] + operationValid(self, context) + + if ao.hide_all_others: + for _ao in scene.cam_operations: + if _ao.path_object_name in bpy.data.objects: + other_obj = bpy.data.objects[_ao.path_object_name] + current_obj = bpy.data.objects[ao.path_object_name] + if other_obj != current_obj: + other_obj.hide = True + other_obj.select = False + else: + for path_obj_name in was_hidden_dict: + print(was_hidden_dict) + if was_hidden_dict[path_obj_name]: + # Find object and make it hidde, then reset 'hidden' flag + obj = bpy.data.objects[path_obj_name] + obj.hide = True + obj.select = False + was_hidden_dict[path_obj_name] = False + + # try highlighting the object in the 3d view and make it active + bpy.ops.object.select_all(action='DESELECT') + # highlight the cutting path if it exists + try: + ob = bpy.data.objects[ao.path_object_name] + ob.select_set(state=True, view_layer=None) + # Show object if, it's was hidden + if ob.hide: + ob.hide = False + was_hidden_dict[ao.path_object_name] = True + bpy.context.scene.objects.active = ob + except Exception as e: + print(e) + +# Moved from init - part 2 + + +def isValid(o, context): + valid = True + if o.geometry_source == 'OBJECT': + if o.object_name not in bpy.data.objects: + valid = False + if o.geometry_source == 'COLLECTION': + if o.collection_name not in bpy.data.collections: + valid = False + elif len(bpy.data.collections[o.collection_name].objects) == 0: + valid = False + + if o.geometry_source == 'IMAGE': + if o.source_image_name not in bpy.data.images: + valid = False + return valid + + +def operationValid(self, context): + scene = context.scene + o = scene.cam_operations[scene.cam_active_operation] + o.changed = True + o.valid = isValid(o, context) + invalidmsg = "Invalid source object for operation.\n" + if o.valid: + o.info.warnings = "" + else: + o.info.warnings = invalidmsg + + if o.geometry_source == 'IMAGE': + o.optimisation.use_exact = False + o.update_offsetimage_tag = True + o.update_zbufferimage_tag = True + print('validity ') + + +def isChainValid(chain, context): + s = context.scene + if len(chain.operations) == 0: + return (False, "") + for cho in chain.operations: + found_op = None + for so in s.cam_operations: + if so.name == cho.name: + found_op = so + if found_op == None: + return (False, f"Couldn't find operation {cho.name}") + if isValid(found_op, context) is False: + return (False, f"Operation {found_op.name} is not valid") + return (True, "") + + +def updateOperationValid(self, context): + updateOperation(self, context) + + +# Update functions start here +def updateChipload(self, context): + """this is very simple computation of chip size, could be very much improved""" + print('update chipload ') + o = self + # Old chipload + o.info.chipload = (o.feedrate / (o.spindle_rpm * o.cutter_flutes)) + # New chipload with chip thining compensation. + # I have tried to combine these 2 formulas to compinsate for the phenomenon of chip thinning when cutting at less + # than 50% cutter engagement with cylindrical end mills. formula 1 Nominal Chipload is + # " feedrate mm/minute = spindle rpm x chipload x cutter diameter mm x cutter_flutes " + # formula 2 (.5*(cutter diameter mm devided by dist_between_paths)) divided by square root of + # ((cutter diameter mm devided by dist_between_paths)-1) x Nominal Chipload + # Nominal Chipload = what you find in end mill data sheats recomended chip load at %50 cutter engagment. + # I am sure there is a better way to do this. I dont get consistent result and + # I am not sure if there is something wrong with the units going into the formula, my math or my lack of + # underestanding of python or programming in genereal. Hopefuly some one can have a look at this and with any luck + # we will be one tiny step on the way to a slightly better chipload calculating function. + + # self.chipload = ((0.5*(o.cutter_diameter/o.dist_between_paths))/(math.sqrt((o.feedrate*1000)/(o.spindle_rpm*o.cutter_diameter*o.cutter_flutes)*(o.cutter_diameter/o.dist_between_paths)-1))) + print(o.info.chipload) + + +def updateOffsetImage(self, context): + """refresh offset image tag for rerendering""" + updateChipload(self, context) + print('update offset') + self.changed = True + self.update_offsetimage_tag = True + + +def updateZbufferImage(self, context): + """changes tags so offset and zbuffer images get updated on calculation time.""" + # print('updatezbuf') + # print(self,context) + self.changed = True + self.update_zbufferimage_tag = True + self.update_offsetimage_tag = True + getOperationSources(self) + + +def updateStrategy(o, context): + """""" + o.changed = True + print('update strategy') + if o.machine_axes == '5' or ( + o.machine_axes == '4' and o.strategy4axis == 'INDEXED'): # INDEXED 4 AXIS DOESN'T EXIST NOW... + addOrientationObject(o) + else: + removeOrientationObject(o) + updateExact(o, context) + + +def updateCutout(o, context): + pass + + +def updateExact(o, context): + print('update exact ') + o.changed = True + o.update_zbufferimage_tag = True + o.update_offsetimage_tag = True + if o.optimisation.use_exact: + if o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS' or o.inverse: + o.optimisation.use_opencamlib = False + print('Current operation cannot use exact mode') + else: + o.optimisation.use_opencamlib = False + + +def updateOpencamlib(o, context): + print('update opencamlib ') + o.changed = True + if o.optimisation.use_opencamlib and ( + o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS'): + o.optimisation.use_exact = False + o.optimisation.use_opencamlib = False + print('Current operation cannot use opencamlib') + + +def updateBridges(o, context): + print('update bridges ') + o.changed = True + + +def updateRotation(o, context): + print('update rotation') + if o.enable_B or o.enable_A: + print(o, o.rotation_A) + ob = bpy.data.objects[o.object_name] + ob.select_set(True) + bpy.context.view_layer.objects.active = ob + if o.A_along_x: # A parallel with X + if o.enable_A: + bpy.context.active_object.rotation_euler.x = o.rotation_A + if o.enable_B: + bpy.context.active_object.rotation_euler.y = o.rotation_B + else: # A parallel with Y + if o.enable_A: + bpy.context.active_object.rotation_euler.y = o.rotation_A + if o.enable_B: + bpy.context.active_object.rotation_euler.x = o.rotation_B + + +# def updateRest(o, context): +# print('update rest ') +# # if o.use_layers: +# o.movement.parallel_step_back = False +# o.changed = True + +def updateRest(o, context): + print('update rest ') + o.changed = True + + +# if (o.strategy == 'WATERLINE'): +# o.use_layers = True + + +def getStrategyList(scene, context): + use_experimental = bpy.context.preferences.addons['cam'].preferences.experimental + items = [ + ('CUTOUT', 'Profile(Cutout)', 'Cut the silhouete with offset'), + ('POCKET', 'Pocket', 'Pocket operation'), + ('DRILL', 'Drill', 'Drill operation'), + ('PARALLEL', 'Parallel', 'Parallel lines on any angle'), + ('CROSS', 'Cross', 'Cross paths'), + ('BLOCK', 'Block', 'Block path'), + ('SPIRAL', 'Spiral', 'Spiral path'), + ('CIRCLES', 'Circles', 'Circles path'), + ('OUTLINEFILL', 'Outline Fill', + 'Detect outline and fill it with paths as pocket. Then sample these paths on the 3d surface'), + ('CARVE', 'Project curve to surface', 'Engrave the curve path to surface'), + ('WATERLINE', 'Waterline - Roughing -below zero', + 'Waterline paths - constant z below zero'), + ('CURVE', 'Curve to Path', 'Curve object gets converted directly to path'), + ('MEDIAL_AXIS', 'Medial axis', + 'Medial axis, must be used with V or ball cutter, for engraving various width shapes with a single stroke ') + ] + # if use_experimental: + # items.extend([('MEDIAL_AXIS', 'Medial axis - EXPERIMENTAL', + # 'Medial axis, must be used with V or ball cutter, for engraving various width shapes with a single stroke ')]); + # ('PENCIL', 'Pencil - EXPERIMENTAL','Pencil operation - detects negative corners in the model and mills only those.'), + # ('CRAZY', 'Crazy path - EXPERIMENTAL', 'Crazy paths - dont even think about using this!'), + # ('PROJECTED_CURVE', 'Projected curve - EXPERIMENTAL', 'project 1 curve towards other curve')]) + return items + +# The following functions are temporary +# until all content in __init__.py is cleaned up + + +def update_material(self, context): + addMaterialAreaObject() + + +def update_operation(self, context): + # from . import updateRest + active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] + updateRest(active_op, bpy.context) + + +def update_exact_mode(self, context): + # from . import updateExact + active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] + updateExact(active_op, bpy.context) + + +def update_opencamlib(self, context): + # from . import updateOpencamlib + active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] + updateOpencamlib(active_op, bpy.context) + + +def update_zbuffer_image(self, context): + # from . import updateZbufferImage + active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] + updateZbufferImage(active_op, bpy.context) diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index 58cc84d41..77e6b780c 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -99,7 +99,8 @@ def getClipEdges(self): x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] pt1, pt2 = (x1, y1), (x2, y2) - inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) + inExtentP1, inExtentP2 = self.inExtent( + x1, y1), self.inExtent(x2, y2) if inExtentP1 and inExtentP2: clipEdges.append((pt1, pt2)) elif inExtentP1 and not inExtentP2: @@ -110,10 +111,12 @@ def getClipEdges(self): clipEdges.append((pt1, pt2)) else: # infinite line if edge[1] != -1: - x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + x1, y1 = self.vertices[edge[1] + ][0], self.vertices[edge[1]][1] leftDir = False else: - x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[2] + ][0], self.vertices[edge[2]][1] leftDir = True if self.inExtent(x1, y1): pt1 = (x1, y1) @@ -129,10 +132,13 @@ def getClipPolygons(self, closePoly): for edge in edges: equation = self.lines[edge[0]] # line equation if edge[1] != -1 and edge[2] != -1: # finite line - x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] - x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[1] + ][0], self.vertices[edge[1]][1] + x2, y2 = self.vertices[edge[2] + ][0], self.vertices[edge[2]][1] pt1, pt2 = (x1, y1), (x2, y2) - inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) + inExtentP1, inExtentP2 = self.inExtent( + x1, y1), self.inExtent(x2, y2) if inExtentP1 and inExtentP2: clipEdges.append((pt1, pt2)) elif inExtentP1 and not inExtentP2: @@ -143,10 +149,12 @@ def getClipPolygons(self, closePoly): clipEdges.append((pt1, pt2)) else: # infinite line if edge[1] != -1: - x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + x1, y1 = self.vertices[edge[1] + ][0], self.vertices[edge[1]][1] leftDir = False else: - x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[2] + ][0], self.vertices[edge[2]][1] leftDir = True if self.inExtent(x1, y1): pt1 = (x1, y1) @@ -320,8 +328,10 @@ def outEdge(self, edge): self.polygons[edge.reg[0].sitenum] = [] if edge.reg[1].sitenum not in self.polygons: self.polygons[edge.reg[1].sitenum] = [] - self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR)) - self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR)) + self.polygons[edge.reg[0].sitenum].append( + (edge.edgenum, sitenumL, sitenumR)) + self.polygons[edge.reg[1].sitenum].append( + (edge.edgenum, sitenumL, sitenumR)) self.edges.append((edge.edgenum, sitenumL, sitenumR)) @@ -526,7 +536,8 @@ def __init__(self): self.edgenum = 0 def dump(self): - print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c)) + print("(#%d a=%g, b=%g, c=%g)" % + (self.edgenum, self.a, self.b, self.c)) print("ep", self.ep) print("reg", self.reg) From cd4856c3d0432f4b5d99179150af1af956f1e3c0 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 17:54:04 -0400 Subject: [PATCH 010/100] Import Fix, funcs moved, refs updated, ocl rename Changed all imports from absolute to relative: - instead of importing the module 'cam' now imports come from '.' the relative parent folder - importing the module into itself resulted in a circular import error, relative imports avoid this Moved Functions, refs updated: - some functions (and 2 variables) only existed on the base level of the module, in the init file, and could not otherwise be accessed, they were moved into the utils file with other similar functions - these were primarily called as update functions for Properties scattered across the addon, these have all been updated to reflect the new location and import convention opencamlib_version rename: - in 2 files there was a local variable named 'opencamlib_version' that was defined by a function called 'opencamlib_version' that returned the opencamlib_version - this seemed to confuse Blender into thinking that it was being called before it was defined, and simply changing the variable name to 'ocl_version' while keeping the function name as 'opencamlib_version' fixed the issue --- scripts/addons/cam/opencamlib/oclSample.py | 15 ++++--- scripts/addons/cam/opencamlib/opencamlib.py | 46 +++++++++++++-------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index 2d9a1baf1..c9f6e287f 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -11,9 +11,9 @@ from io_mesh_stl import blender_utils import mathutils import math -from cam.simple import activate -from cam.exception import * -from cam.async_op import progress_async +from ..simple import activate +from ..exception import * +from ..async_op import progress_async OCL_SCALE = 1000.0 @@ -59,9 +59,11 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): cutter = None if op_cutter_type == 'END': - cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.CylCutter( + (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'BALLNOSE': - cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.BallCutter( + (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) @@ -89,7 +91,8 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): for chunk in chunks: for coord in chunk.get_points_np(): - bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) + bdc.appendPoint(ocl.CLPoint( + coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) await progress_async("OpenCAMLib sampling") bdc.run() diff --git a/scripts/addons/cam/opencamlib/opencamlib.py b/scripts/addons/cam/opencamlib/opencamlib.py index e7c507a24..7eef7a115 100644 --- a/scripts/addons/cam/opencamlib/opencamlib.py +++ b/scripts/addons/cam/opencamlib/opencamlib.py @@ -13,16 +13,17 @@ import numpy as np from subprocess import call -from cam.collision import BULLET_SCALE -from cam import simple -from cam.chunk import camPathChunk -from cam.simple import * -from cam.async_op import progress_async +from ..collision import BULLET_SCALE +from .. import simple +from .. import utils +from ..chunk import camPathChunk +from ..simple import * +from ..async_op import progress_async from shapely import geometry as sgeometry -from .oclSample import get_oclSTL -from cam import utils - -from cam.opencamlib.oclSample import ocl_sample +from .oclSample import ( + get_oclSTL, + ocl_sample +) OCL_SCALE = 1000.0 @@ -84,14 +85,17 @@ def exportModelsToSTL(operation): bpy.ops.object.duplicate(linked=False) # collision_object = bpy.context.scene.objects.active # bpy.context.scene.objects.selected = collision_object - file_name = os.path.join(tempfile.gettempdir(), "model{0}.stl".format(str(file_number))) - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + file_name = os.path.join( + tempfile.gettempdir(), "model{0}.stl".format(str(file_number))) + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=True) bpy.ops.transform.resize(value=(OCL_SCALE, OCL_SCALE, OCL_SCALE), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), texture_space=False, release_confirm=False) - bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply( + location=True, rotation=True, scale=True) bpy.ops.export_mesh.stl(check_existing=True, filepath=file_name, filter_glob="*.stl", use_selection=True, ascii=False, use_mesh_modifiers=True, axis_forward='Y', axis_up='Z', global_scale=1.0) bpy.ops.object.delete() @@ -119,7 +123,8 @@ async def oclResampleChunks(operation, chunks_to_resample, use_cached_mesh): sample_index = 0 for chunk, i_start, i_length in chunks_to_resample: - z = np.array([p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE + z = np.array( + [p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE pts = chunk.get_points_np() pt_z = pts[i_start:i_start+i_length, 2] pt_z = np.where(z > pt_z, z, pt_z) @@ -149,7 +154,8 @@ def oclGetMedialAxis(operation, chunks): oclWaterlineHeightsToOCL(operation) operationSettingsToOCL(operation) curvesToOCL(operation) - call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")]) + call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), + "addons", "cam", "opencamlib", "ocl.py")]) waterlineChunksFromOCL(operation, chunks) @@ -164,12 +170,15 @@ async def oclGetWaterline(operation, chunks): op_cutter_tip_angle = operation['cutter_tip_angle'] cutter = None - cutter_length = 150 # TODO: automatically determine necessary cutter length depending on object size + # TODO: automatically determine necessary cutter length depending on object size + cutter_length = 150 if op_cutter_type == 'END': - cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.CylCutter( + (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'BALLNOSE': - cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.BallCutter( + (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) @@ -192,7 +201,8 @@ async def oclGetWaterline(operation, chunks): for l in wl_loops: inpoints = [] for p in l: - inpoints.append((p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE)) + inpoints.append( + (p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE)) inpoints.append(inpoints[0]) chunk = camPathChunk(inpoints=inpoints) chunk.closed = True From 803a5c5d63e95588ff87e1116413a4b362862ca1 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 17:55:41 -0400 Subject: [PATCH 011/100] Import Fix, funcs moved, refs updated, ocl rename Changed all imports from absolute to relative: - instead of importing the module 'cam' now imports come from '.' the relative parent folder - importing the module into itself resulted in a circular import error, relative imports avoid this Moved Functions, refs updated: - some functions (and 2 variables) only existed on the base level of the module, in the init file, and could not otherwise be accessed, they were moved into the utils file with other similar functions - these were primarily called as update functions for Properties scattered across the addon, these have all been updated to reflect the new location and import convention opencamlib_version rename: - in 2 files there was a local variable named 'opencamlib_version' that was defined by a function called 'opencamlib_version' that returned the opencamlib_version - this seemed to confuse Blender into thinking that it was being called before it was defined, and simply changing the variable name to 'ocl_version' while keeping the function name as 'opencamlib_version' fixed the issue From e8a9333f52ed40f09f76dff46a21bb5d8466d139 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 20:40:31 -0400 Subject: [PATCH 012/100] Update version version was previously only updated in bl_info, it has now been updated to match in the version.py file as well, which should allow it to display in the main UI. --- scripts/addons/cam/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 714c3d665..6afcba66a 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__ = (1, 0, 4) +__version__ = (1, 0, 7) From 2a22fb19b1e00aacfc42111eb8bcccbac081f4de Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 20:57:33 -0400 Subject: [PATCH 013/100] Update __init__.py --- scripts/addons/cam/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index f4bc44b9f..b69378afb 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -101,7 +101,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 7), + "version":(1,0,7), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", From 4b5dddc52441ccba5629c0ec0b987976d197776a Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 22 Mar 2024 20:57:50 -0400 Subject: [PATCH 014/100] Update version.py --- scripts/addons/cam/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 6afcba66a..08b424b11 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__ = (1, 0, 7) +__version__=(1,0,7) From 3a546c27589835e5df72cc7ab08d4a9fe46114e2 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:14:38 -0400 Subject: [PATCH 015/100] Import fix update - bpy.utils, autoformat fix While rearranging and cleaning up the utils imports in the initial 'Import fix' commit I deleted `utils.` from `bpy.utils.script_paths()` in the ops.py file. This has been fixed, and I took the opportunity to reverse the autoformat issue (changed line length from 100 to 89 between commits), and restore all the code to its previous state. I made one exception: - comments that came at the end of the line that were moved to the line above All functional code has been restored to its previous format. --- scripts/addons/cam/__init__.py | 7 +- scripts/addons/cam/bridges.py | 24 ++-- scripts/addons/cam/chunk.py | 48 +++---- scripts/addons/cam/collision.py | 30 ++-- scripts/addons/cam/gcodepath.py | 18 +-- scripts/addons/cam/image_utils.py | 63 +++----- scripts/addons/cam/joinery.py | 36 ++--- scripts/addons/cam/ops.py | 4 +- scripts/addons/cam/pack.py | 9 +- scripts/addons/cam/parametric.py | 30 ++-- scripts/addons/cam/pattern.py | 9 +- scripts/addons/cam/puzzle_joinery.py | 36 ++--- scripts/addons/cam/simple.py | 3 +- scripts/addons/cam/simulation.py | 6 +- scripts/addons/cam/strategy.py | 51 +++---- scripts/addons/cam/testing.py | 9 +- scripts/addons/cam/ui_panels/area.py | 12 +- scripts/addons/cam/ui_panels/chains.py | 24 ++-- scripts/addons/cam/ui_panels/cutter.py | 15 +- scripts/addons/cam/ui_panels/machine.py | 6 +- scripts/addons/cam/ui_panels/op_properties.py | 12 +- scripts/addons/cam/ui_panels/operations.py | 45 ++---- scripts/addons/cam/utils.py | 134 ++++++------------ scripts/addons/cam/version.py | 2 +- scripts/addons/cam/voronoi.py | 24 ++-- 25 files changed, 221 insertions(+), 436 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index b69378afb..b472a5f0c 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -89,8 +89,7 @@ except ImportError: # pip install required python stuff subprocess.check_call([sys.executable, "-m", "ensurepip"]) - subprocess.check_call([sys.executable, "-m", "pip", - "install", "--upgrade", " pip"]) + subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) subprocess.check_call([sys.executable, "-m", "pip", "install", "shapely", "Equation", "opencamlib"]) # install numba if available for this platform, ignore failure @@ -101,12 +100,12 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,7), + "version": (1, 0, 7), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", "warning": "", - "doc_url": "https://blendercom/", + "doc_url": "https://blendercam.com/", "tracker_url": "", "category": "Scene"} diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index fcbc272eb..224d7ff38 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -63,8 +63,7 @@ def addAutoBridges(o): if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is None: bridgecollectionname = 'bridges_' + o.name bpy.data.collections.new(bridgecollectionname) - bpy.context.collection.children.link( - bpy.data.collections[bridgecollectionname]) + bpy.context.collection.children.link(bpy.data.collections[bridgecollectionname]) g = bpy.data.collections[bridgecollectionname] o.bridges_collection_name = bridgecollectionname for ob in o.objects: @@ -78,14 +77,12 @@ def addAutoBridges(o): minx, miny, maxx, maxy = c.bounds d1 = c.project(sgeometry.Point(maxx + 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, -math.pi / 2, - o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, -math.pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point(minx - 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi / 2, - o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, math.pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, maxy + 1000)) @@ -95,8 +92,7 @@ def addAutoBridges(o): bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, miny - 1000)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi, o.bridges_width, - o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, math.pi, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) @@ -120,8 +116,7 @@ def getBridgesPoly(o): bridgespoly = sops.unary_union(shapes) # buffer the poly, so the bridges are not actually milled... - o.bridgespolyorig = bridgespoly.buffer( - distance=o.cutter_diameter / 2.0) + o.bridgespolyorig = bridgespoly.buffer(distance=o.cutter_diameter / 2.0) o.bridgespoly_boundary = o.bridgespolyorig.boundary o.bridgespoly_boundary_prep = prepared.prep(o.bridgespolyorig.boundary) o.bridgespoly = prepared.prep(o.bridgespolyorig) @@ -183,12 +178,10 @@ def useBridges(ch, o): if not startinside: newpoints.append(chp1) elif startinside: - newpoints.append( - (chp1[0], chp1[1], max(chp1[2], bridgeheight))) + newpoints.append((chp1[0], chp1[1], max(chp1[2], bridgeheight))) cpoints = [] if itpoint: - pt = mathutils.Vector( - (intersections.x, intersections.y, intersections.z)) + pt = mathutils.Vector((intersections.x, intersections.y, intersections.z)) cpoints = [pt] elif itmpoint: @@ -273,8 +266,7 @@ def useBridges(ch, o): # verify if vertices have been generated and generate a mesh if verts: - mesh = bpy.data.meshes.new( - name=o.name + "_cut_bridges") # generate new mesh + mesh = bpy.data.meshes.new(name=o.name + "_cut_bridges") # generate new mesh # integrate coordinates and edges mesh.from_pydata(verts, edges, faces) object_data_add(bpy.context, mesh) # create object diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py index 1102fe172..0a234eebb 100644 --- a/scripts/addons/cam/chunk.py +++ b/scripts/addons/cam/chunk.py @@ -74,8 +74,7 @@ def __init__(self, inpoints=None, startpoints=None, endpoints=None, rotations=No self.depth = None def to_chunk(self): - chunk = camPathChunk(self.points, self.startpoints, - self.endpoints, self.rotations) + chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): chunk.closed = True if self.depth is not None: @@ -164,8 +163,7 @@ def shift(self, x, y, z): def setZ(self, z, if_bigger=False): if if_bigger: - self.points[:, 2] = z if z > self.points[:, - 2] else self.points[:, 2] + self.points[:, 2] = z if z > self.points[:, 2] else self.points[:, 2] else: self.points[:, 2] = z @@ -186,8 +184,7 @@ def clampmaxZ(self, z): def dist(self, pos, o): if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + \ - (pos[1]-self.points[:, 1])**2 + dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 return sqrt(np.min(dist_sq)) else: if o.movement.type == 'MEANDER': @@ -234,8 +231,7 @@ def xyDistanceTo(self, other, cutoff=0): def adaptdist(self, pos, o): # reorders chunk so that it starts at the closest point to pos. if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + \ - (pos[1]-self.points[:, 1])**2 + dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 point_idx = np.argmin(dist_sq) new_points = np.concatenate( (self.points[point_idx:], self.points[:point_idx+1])) @@ -487,8 +483,7 @@ def rampZigZag(self, zstart, zend, o): else: zigzagtraveled = 0.0 haspoints = False - ramppoints = [ - (self.points[0][0], self.points[0][1], self.points[0][2])] + ramppoints = [(self.points[0][0], self.points[0][1], self.points[0][2])] i = 1 while not haspoints: # print(i,zigzaglength,zigzagtraveled) @@ -538,8 +533,7 @@ def rampZigZag(self, zstart, zend, o): if zend < zstart: stepdown = zstart - zend - estlength = (zstart - zend) / \ - tan(o.movement.ramp_out_angle) + estlength = (zstart - zend) / tan(o.movement.ramp_out_angle) self.getLength() if self.length > 0: ramplength = estlength @@ -567,8 +561,7 @@ def rampZigZag(self, zstart, zend, o): d = dist2d(p1, p2) zigzagtraveled += d if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): - ratio = 1 - (zigzagtraveled - - zigzaglength) / d + ratio = 1 - (zigzagtraveled - zigzaglength) / d if (i + 1 == len( self.points)): # this condition is for a rare case of # combined layers+bridges+ramps... @@ -595,8 +588,7 @@ def rampZigZag(self, zstart, zend, o): traveled += d ratio = 1 - (traveled / ramplength) znew = zstart - stepdown * ratio - chunk_points.append( - (p2[0], p2[1], max(p2[2], znew))) + chunk_points.append((p2[0], p2[1], max(p2[2], znew))) # max value here is so that it doesn't go below surface in the case of 3d paths self.points = np.array(chunk_points) @@ -606,8 +598,7 @@ def changePathStart(self, o): newstart = o.profile_start chunkamt = len(self.points) newstart = newstart % chunkamt - self.points = np.concatenate( - (self.points[newstart:], self.points[:newstart])) + self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) def breakPathForLeadinLeadout(self, o): iradius = o.lead_in @@ -810,10 +801,8 @@ def optimizeChunk(chunk, operation): # list comprehension so we don't have to do tons of appends chunk.startpoints = [chunk.startpoints[i] for i, b in enumerate(keep_points) if b == True] - chunk.endpoints = [chunk.endpoints[i] - for i, b in enumerate(keep_points) if b == True] - chunk.rotations = [chunk.rotations[i] - for i, b in enumerate(keep_points) if b == True] + chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] + chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] return chunk @@ -1020,8 +1009,7 @@ def chunksToShapely(chunks): # print('starting and ending points too close, removing ending point') ch.parents[0].points = np.array(newPoints) - ch.parents[0].poly = sgeometry.Polygon( - ch.parents[0].points) + ch.parents[0].poly = sgeometry.Polygon(ch.parents[0].points) ch.parents[0].poly = ch.parents[0].poly.difference( ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) @@ -1066,8 +1054,7 @@ def meshFromCurveToChunk(object): # print('itis') chunk.closed = True - chunk.points.append( - (mesh.vertices[lastvi].co + object.location).to_tuple()) + chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z lastvi = vi + 1 chunk = chunk.to_chunk() @@ -1149,12 +1136,9 @@ def meshFromCurve(o, use_modifiers=False): bpy.data.meshes.remove(oldmesh) try: - bpy.ops.object.transform_apply( - location=True, rotation=False, scale=False) - bpy.ops.object.transform_apply( - location=False, rotation=True, scale=False) - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) except: pass diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 6d0aa2330..48a41986a 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -50,8 +50,7 @@ def getCutterBullet(o): rotation=(0, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -65,8 +64,7 @@ def getCutterBullet(o): location=CUTTER_OFFSET, rotation=(0, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -85,8 +83,7 @@ def getCutterBullet(o): rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -103,8 +100,7 @@ def getCutterBullet(o): location=CUTTER_OFFSET, rotation=(math.pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter = bpy.context.active_object @@ -129,8 +125,7 @@ def getCutterBullet(o): bpy.ops.object.editmode_toggle() bpy.ops.object.convert(target='MESH') bpy.ops.transform.rotate(value=-math.pi / 2, orient_axis='X') - bpy.ops.object.transform_apply( - location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) ob = bpy.context.active_object ob.name = "BallConeTool" ob_scr = ob.modifiers.new(type='SCREW', name='scr') @@ -142,8 +137,7 @@ def getCutterBullet(o): bpy.data.objects['BallConeTool'].select_set(True) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') bpy.ops.rigidbody.object_add(type='ACTIVE') cutter.location = CUTTER_OFFSET @@ -157,8 +151,7 @@ def getCutterBullet(o): cutter = bpy.context.active_object scale = o.cutter_diameter / cutob.dimensions.x cutter.scale *= BULLET_SCALE * scale - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='BOUNDS') # print(cutter.dimensions,scale) @@ -196,8 +189,7 @@ def subdivideLongEdges(ob, threshold): # bpy.ops.mesh.tris_convert_to_quads() bpy.ops.mesh.select_all(action='DESELECT') - bpy.ops.mesh.select_mode( - use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE') bpy.ops.object.editmode_toggle() for i in subdivides: m.edges[i].select = True @@ -270,8 +262,7 @@ def prepareBulletCollision(o): use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, texture_space=False, release_confirm=False) collisionob.location = collisionob.location * BULLET_SCALE - bpy.ops.object.transform_apply( - location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) bpy.context.view_layer.objects.active = collisionob if active_collection in collisionob.users_collection: active_collection.objects.unlink(collisionob) @@ -336,8 +327,7 @@ def getSampleBulletNAxis(cutter, startpoint, endpoint, rotation, cutter_compensa cutterVec.rotate(Euler(rotation)) start = (startpoint * BULLET_SCALE + cutterVec).to_tuple() end = (endpoint * BULLET_SCALE + cutterVec).to_tuple() - pos = bpy.context.scene.rigidbody_world.convex_sweep_test( - cutter, start, end) + pos = bpy.context.scene.rigidbody_world.convex_sweep_test(cutter, start, end) if pos[3] == 1: pos = Vector(pos[0]) diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index c04fa93f4..5630c63d3 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -238,8 +238,7 @@ def startNewFile(): c.flush_nc() - last_cutter = [o.cutter_id, o.cutter_diameter, - o.cutter_type, o.cutter_flutes] + last_cutter = [o.cutter_id, o.cutter_diameter, o.cutter_type, o.cutter_flutes] if o.cutter_type not in ['LASER', 'PLASMA']: if o.enable_hold: c.write('(Hold Down)\n') @@ -306,8 +305,7 @@ def startNewFile(): fadjust = True if m.use_position_definitions: # dhull - last = Vector( - (m.starting_position.x, m.starting_position.y, m.starting_position.z)) + last = Vector((m.starting_position.x, m.starting_position.y, m.starting_position.z)) lastrot = Euler((0, 0, 0)) duration = 0.0 @@ -401,8 +399,7 @@ def startNewFile(): if not cut: if o.cutter_type == 'LASER': c.write("(*************dwell->laser on)\n") - c.write( - "G04 P" + str(round(o.Laser_delay, 2)) + "\n") + c.write("G04 P" + str(round(o.Laser_delay, 2)) + "\n") c.write(o.Laser_on + '\n') elif o.cutter_type == 'PLASMA': c.write("(*************dwell->PLASMA on)\n") @@ -467,8 +464,7 @@ def startNewFile(): processedops += 1 if split and processedops > m.split_limit: - c.rapid(x=last.x * unitcorr, y=last.y * - unitcorr, z=free_height * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=free_height * unitcorr) # @v=(ch.points[-1][0],ch.points[-1][1],free_height) c.program_end() findex += 1 @@ -487,10 +483,8 @@ def startNewFile(): c.flush_nc() c.feedrate(unitcorr * o.feedrate) - c.rapid(x=last.x * unitcorr, y=last.y * - unitcorr, z=free_height * unitcorr) - c.rapid(x=last.x * unitcorr, y=last.y * - unitcorr, z=last.z * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=free_height * unitcorr) + c.rapid(x=last.x * unitcorr, y=last.y * unitcorr, z=last.z * unitcorr) processedops = 0 if o.remove_redundant_points and o.strategy != "DRILL": diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 48580d40a..34a83b2bf 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -118,8 +118,7 @@ def imagetonumpy(i): width = i.size[0] height = i.size[1] - na = numpy.full(shape=(width*height*4,), - fill_value=-10, dtype=numpy.double) + na = numpy.full(shape=(width*height*4,), fill_value=-10, dtype=numpy.double) p = i.pixels[:] # these 2 lines are about 15% faster than na[:]=i.pixels[:].... whyyyyyyyy!!?!?!?!?! @@ -137,8 +136,7 @@ def imagetonumpy(i): def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea): for y in prange(y1, y2): for x in range(0, width-cwidth): - comparearea[x, y] = numpy.nanmax( - sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan) + comparearea[x, y] = numpy.nanmax(sourceArray[x:x+cwidth, y:y+cwidth] + cutterArrayNan) async def offsetArea(o, samples): @@ -154,8 +152,7 @@ async def offsetArea(o, samples): width = len(sourceArray) height = len(sourceArray[0]) cwidth = len(cutterArray) - o.offset_image = numpy.full( - shape=(width, height), fill_value=-10.0, dtype=numpy.double) + o.offset_image = numpy.full(shape=(width, height), fill_value=-10.0, dtype=numpy.double) t = time.time() @@ -163,8 +160,7 @@ async def offsetArea(o, samples): if o.inverse: sourceArray = -sourceArray + minz - comparearea = o.offset_image[m: width - - cwidth + m, m:height - cwidth + m] + comparearea = o.offset_image[m: width - cwidth + m, m:height - cwidth + m] # i=0 cutterArrayNan = np.where(cutterArray > -10, cutterArray, np.full(cutterArray.shape, np.nan)) @@ -174,8 +170,7 @@ async def offsetArea(o, samples): _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, height, comparearea) await progress_async('offset depth image', int((y2 * 100) / comparearea.shape[1])) - o.offset_image[m: width - cwidth + m, - m:height - cwidth + m] = comparearea + o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea print('\nOffset image time ' + str(time.time() - t)) @@ -193,10 +188,8 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly """detects areas in the offset image which are 'cavities' - the curvature changes.""" # i=numpy.logical_xor(lastislice , islice) simple.progress('detect corners in the offset image') - vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - \ - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] - horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - \ - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] + vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] + horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] # if bpy.app.debug_value==2: ar = numpy.logical_or(vertical, horizontal) @@ -230,8 +223,7 @@ def imageEdgeSearch_online(o, ar, zimage): maxarx = ar.shape[0] maxary = ar.shape[1] - directions = ((-1, -1), (0, -1), (1, -1), (1, 0), - (1, 1), (0, 1), (-1, 1), (-1, 0)) + directions = ((-1, -1), (0, -1), (1, -1), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)) indices = ar.nonzero() # first get white pixels startpix = ar.sum() @@ -299,8 +291,7 @@ def imageEdgeSearch_online(o, ar, zimage): if len(indices[0] > 0): xs = indices[0][0] ys = indices[1][0] - nchunk = camPathChunkBuilder( - [(xs, ys, zimage[xs, ys])]) # startposition + nchunk = camPathChunkBuilder([(xs, ys, zimage[xs, ys])]) # startposition ar[xs, ys] = False else: @@ -329,8 +320,7 @@ def imageEdgeSearch_online(o, ar, zimage): test_direction = directions[dindexmod] if 0: - print(xs, ys, test_direction, - last_direction, testangulardistance) + print(xs, ys, test_direction, last_direction, testangulardistance) print(totpix) itests += 1 totaltests += 1 @@ -361,11 +351,9 @@ async def crazyPath(o): resx = ceil(sx / o.optimisation.simulation_detail) + 2 * o.borderwidth resy = ceil(sy / o.optimisation.simulation_detail) + 2 * o.borderwidth - o.millimage = numpy.full( - shape=(resx, resy), fill_value=0., dtype=numpy.float) + o.millimage = numpy.full(shape=(resx, resy), fill_value=0., dtype=numpy.float) # getting inverted cutter - o.cutterArray = - \ - simulation.getCutterArray(o, o.optimisation.simulation_detail) + o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) def buildStroke(start, end, cutterArray): @@ -374,8 +362,7 @@ def buildStroke(start, end, cutterArray): size_y = abs(end[1] - start[1]) + cutterArray.size[0] r = cutterArray.size[0] / 2 - strokeArray = numpy.full(shape=(size_x, size_y), - fill_value=-10.0, dtype=numpy.float) + strokeArray = numpy.full(shape=(size_x, size_y), fill_value=-10.0, dtype=numpy.float) samplesx = numpy.round(numpy.linspace(start[0], end[0], strokelength)) samplesy = numpy.round(numpy.linspace(start[1], end[1], strokelength)) samplesz = numpy.round(numpy.linspace(start[2], end[2], strokelength)) @@ -516,8 +503,7 @@ def crazyStrokeImage(o): testangle = -testangle testleftright = False else: - testangle = abs(testangle) + \ - angleincrement # increment angle + testangle = abs(testangle) + angleincrement # increment angle testleftright = True else: # climb/conv. testangle += angleincrement @@ -646,8 +632,7 @@ def crazyStrokeImageBinary(o, ar, avoidar): margin = 0 # print(xs,ys,indices[0][0],indices[1][0],r) - ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - - r:xs + r, ys - r:ys + r] * cutterArrayNegative + ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, ys - r:ys + r] * cutterArrayNegative anglerange = [-pi, pi] # range for angle of toolpath vector versus material vector - # probably direction negative to the force applied on cutter by material. @@ -809,15 +794,13 @@ def crazyStrokeImageBinary(o, ar, avoidar): if avoidar[xs, ys] == 0: # print(toomuchpix,ar[xs-r:xs-r+d,ys-r:ys-r+d].sum()*pi/4,satisfypix) - testarsum = ar[xs - r:xs - r + d, - ys - r:ys - r + d].sum() * pi / 4 + testarsum = ar[xs - r:xs - r + d, ys - r:ys - r + d].sum() * pi / 4 if toomuchpix > testarsum > 0 or ( totpix < startpix * 0.025): # 0 now instead of satisfypix found = True # print(xs,ys,indices[0][index],indices[1][index]) - nchunk = camPathChunk( - [(xs, ys)]) # startposition + nchunk = camPathChunk([(xs, ys)]) # startposition ar[xs - r:xs + r, ys - r:ys + r] = ar[xs - r:xs + r, ys - r:ys + r] * cutterArrayNegative # lastvect=Vector((r,0,0))#vector is 3d, @@ -1175,13 +1158,11 @@ def renderSampleImage(o): node_out.format.color_mode = 'RGB' node_out.format.color_depth = '32' node_out.file_slots.new(os.path.basename(iname)) - n.links.new(node_in.outputs[node_in.outputs.find( - 'Mist')], node_out.inputs[-1]) + n.links.new(node_in.outputs[node_in.outputs.find('Mist')], node_out.inputs[-1]) ################### # resize operation image - o.offset_image = numpy.full( - shape=(resx, resy), fill_value=-10, dtype=numpy.double) + o.offset_image = numpy.full(shape=(resx, resy), fill_value=-10, dtype=numpy.double) # various settings for faster render r.resolution_percentage = 100 @@ -1218,8 +1199,7 @@ def renderSampleImage(o): os.replace(iname+"%04d.exr" % (s.frame_current), iname) finally: if backup_settings is not None: - _restore_render_settings( - SETTINGS_TO_BACKUP, backup_settings) + _restore_render_settings(SETTINGS_TO_BACKUP, backup_settings) else: print("Failed to backup scene settings") @@ -1248,8 +1228,7 @@ def renderSampleImage(o): #o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth) o.optimisation.pixsize = o.source_image_size_x / i.size[0] - simple.progress('pixel size in the image source', - o.optimisation.pixsize) + simple.progress('pixel size in the image source', o.optimisation.pixsize) rawimage = imagetonumpy(i) maxa = numpy.max(rawimage) diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 92747ffbd..425a922c6 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -102,8 +102,7 @@ def twist_line(length, thickness, finger_play, percentage, amount, distance, cen spacing = distance / amount while amount > 0: position = spacing * amount - interlock_twist(length, thickness, finger_play, - percentage=percentage, cx=position) + interlock_twist(length, thickness, finger_play, percentage=percentage, cx=position) print('twistline', amount, distance, position) amount -= 1 @@ -152,16 +151,13 @@ def horizontal_finger(length, thickness, finger_play, amount, center=True): mortise(length, thickness, finger_play, 0, thickness / 2) simple.active_name("_width_finger") else: - mortise(length, thickness, finger_play, - i * 2 * length, thickness / 2) + mortise(length, thickness, finger_play, i * 2 * length, thickness / 2) simple.active_name("_width_finger") - mortise(length, thickness, finger_play, - - i * 2 * length, thickness / 2) + mortise(length, thickness, finger_play, -i * 2 * length, thickness / 2) simple.active_name("_width_finger") else: for i in range(amount): - mortise(length, thickness, finger_play, - length / 2 + 2 * i * length, 0) + mortise(length, thickness, finger_play, length / 2 + 2 * i * length, 0) simple.active_name("_width_finger") simple.join_multiple("_width_finger") @@ -241,8 +237,7 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): # creates pockets pocket using mortise function for kerf bending dist = 3 * finger_width / 2 while dist < length: - mortise(height - 2 * finger_thick, - pocket_width, 0, dist, 0, math.pi / 2) + mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") dist += finger_width * 2 @@ -253,8 +248,7 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): def make_variable_flex_pocket(height, finger_thick, pocket_width, locations): # creates pockets pocket using mortise function for kerf bending for dist in locations: - mortise(height + 2 * finger_thick, - pocket_width, 0, dist, 0, math.pi / 2) + mortise(height + 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) simple.active_name("_flex_pocket") simple.join_multiple("_flex_pocket") @@ -276,8 +270,7 @@ def create_flex_side(length, height, finger_thick, top_bottom=False): else: simple.make_active("base") fingers = bpy.context.active_object - bpy.ops.transform.translate( - value=(0.0, height / 2 - finger_thick / 2 + 0.0003, 0.0)) + bpy.ops.transform.translate(value=(0.0, height / 2 - finger_thick / 2 + 0.0003, 0.0)) bpy.ops.curve.simple(align='WORLD', location=(length / 2 + 0.00025, 0, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', Simple_width=length, Simple_length=height, shape='3D', @@ -338,8 +331,7 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, math.pi / 4)) # factor for tolerance for the finger if base: - mortise(finger_size, finger_thick, - finger_tolerance * mad, distance, 0, 0) + mortise(finger_size, finger_thick, finger_tolerance * mad, distance, 0, 0) simple.active_name("_base") else: mortise_point = loop.interpolate(distance) @@ -448,8 +440,7 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi if not_start: while distance <= pd: mortise_angle = angle(oldp, p) - mortise_angle_difference = abs( - mortise_angle - old_mortise_angle) + mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( math.pi / 4)) # factor for tolerance for the finger # move finger by the factor mad greater with larger angle difference @@ -487,12 +478,10 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi # reduce the size of finger by a percentage... the closer to 1.0, the slower finger_sz *= 0.95 distance = old_distance + 3 * oldfinger_sz / 2 + finger_sz / 2 - mortise_point = loop.interpolate( - distance) # get the next mortise point + mortise_point = loop.interpolate(distance) # get the next mortise point next_mortise_angle = angle((old_mortise_point.x, old_mortise_point.y), (mortise_point.x, mortise_point.y)) # calculate next angle - next_angle_difference = abs( - next_mortise_angle - mortise_angle) + next_angle_difference = abs(next_mortise_angle - mortise_angle) oldfinger_sz = finger_sz old_mortise_angle = mortise_angle @@ -510,8 +499,7 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi def single_interlock(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle, type, amount=1, twist_percentage=0.5): if type == "GROOVE": - interlock_groove(finger_depth, finger_thick, - finger_tolerance, x, y, groove_angle) + interlock_groove(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle) elif type == "TWIST": interlock_twist(finger_depth, finger_thick, finger_tolerance, x, y, groove_angle, percentage=twist_percentage) diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index b3c896345..8455eeb97 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -19,7 +19,7 @@ # # ***** END GPL LICENCE BLOCK ***** -# blender operators definitions are in this file. They mostly call the functions from py +# blender operators definitions are in this file. They mostly call the functions from utils.py import bpy @@ -133,7 +133,7 @@ def execute(self, context): bpath = bpy.app.binary_path fpath = bpy.data.filepath - for p in bpy.script_paths(): + for p in bpy.utils.script_paths(): scriptpath = p + os.sep + 'addons' + os.sep + 'cam' + os.sep + 'backgroundop.py' print(scriptpath) if os.path.isfile(scriptpath): diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index b15aa01ae..64d6bfe98 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -128,8 +128,7 @@ def packCurves(): p = porig if rotate: - ptrans = affinity.rotate( - p, rot, origin=rotcenter, use_radians=True) + ptrans = affinity.rotate(p, rot, origin=rotcenter, use_radians=True) ptrans = affinity.translate(ptrans, x, y) else: ptrans = affinity.translate(p, x, y) @@ -168,8 +167,7 @@ def packCurves(): # print(iter) # reset polygon to best position here: - ptrans = affinity.rotate( - porig, best[2], rotcenter, use_radians=True) + ptrans = affinity.rotate(porig, best[2], rotcenter, use_radians=True) ptrans = affinity.translate(ptrans, best[0], best[1]) print(best[0], best[1], itera) @@ -197,6 +195,5 @@ def packCurves(): i += 1 t = time.time() - t - polygon_utils_cam.shapelyToCurve( - 'test', sgeometry.MultiPolygon(placedpolys), 0) + polygon_utils_cam.shapelyToCurve('test', sgeometry.MultiPolygon(placedpolys), 0) print(t) diff --git a/scripts/addons/cam/parametric.py b/scripts/addons/cam/parametric.py index 25d660f2b..0de7f67d2 100644 --- a/scripts/addons/cam/parametric.py +++ b/scripts/addons/cam/parametric.py @@ -73,10 +73,8 @@ def derive_bezier_handles(a, b, c, d, tb, tc): final_c = c - (math.pow(1 - tc, 3) * a) - (math.pow(tc, 3) * d) # Multiply the inversed matrix with the position vector to get the handle points - bezier_b = matrix_determinant * \ - ((matrix_d * final_b) + (-matrix_b * final_c)) - bezier_c = matrix_determinant * \ - ((-matrix_c * final_b) + (matrix_a * final_c)) + bezier_b = matrix_determinant * ((matrix_d * final_b) + (-matrix_b * final_c)) + bezier_c = matrix_determinant * ((-matrix_c * final_b) + (matrix_a * final_c)) # Return the handle points return bezier_b, bezier_c @@ -137,8 +135,7 @@ def create_parametric_curve( if use_cubic: points = [ - function(((i - 3) / (3 * iterations)) * - (max - min) + min, *args, **kwargs) + function(((i - 3) / (3 * iterations)) * (max - min) + min, *args, **kwargs) for i in range((3 * (iterations + 2)) + 1) ] @@ -149,12 +146,9 @@ def create_parametric_curve( c = points[(3 * i) + 2] d = points[(3 * i) + 3] - bezier_bx, bezier_cx = derive_bezier_handles( - a[0], b[0], c[0], d[0], 1 / 3, 2 / 3) - bezier_by, bezier_cy = derive_bezier_handles( - a[1], b[1], c[1], d[1], 1 / 3, 2 / 3) - bezier_bz, bezier_cz = derive_bezier_handles( - a[2], b[2], c[2], d[2], 1 / 3, 2 / 3) + bezier_bx, bezier_cx = derive_bezier_handles(a[0], b[0], c[0], d[0], 1 / 3, 2 / 3) + bezier_by, bezier_cy = derive_bezier_handles(a[1], b[1], c[1], d[1], 1 / 3, 2 / 3) + bezier_bz, bezier_cz = derive_bezier_handles(a[2], b[2], c[2], d[2], 1 / 3, 2 / 3) points[(3 * i) + 1] = (bezier_bx, bezier_by, bezier_bz) points[(3 * i) + 2] = (bezier_cx, bezier_cy, bezier_cz) @@ -164,16 +158,13 @@ def create_parametric_curve( spline.bezier_points[i].co = points[3 * (i + 1)] spline.bezier_points[i].handle_left_type = 'FREE' - spline.bezier_points[i].handle_left = Vector( - points[(3 * (i + 1)) - 1]) + spline.bezier_points[i].handle_left = Vector(points[(3 * (i + 1)) - 1]) spline.bezier_points[i].handle_right_type = 'FREE' - spline.bezier_points[i].handle_right = Vector( - points[(3 * (i + 1)) + 1]) + spline.bezier_points[i].handle_right = Vector(points[(3 * (i + 1)) + 1]) else: - points = [function(i / iterations, *args, **kwargs) - for i in range(iterations + 1)] + points = [function(i / iterations, *args, **kwargs) for i in range(iterations + 1)] # Set point coordinates, disable handles for i in range(iterations + 1): @@ -245,8 +236,7 @@ def make_edge_loops(*objects): if bpy.app.version >= (2, 80): ctx['selected_editable_objects'] = mesh_objects else: - ctx['selected_editable_bases'] = [scene.object_bases[o.name] - for o in mesh_objects] + ctx['selected_editable_bases'] = [scene.object_bases[o.name] for o in mesh_objects] # Join them together bpy.ops.object.join(ctx) diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index d2d51c20c..b02b978a2 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -154,8 +154,7 @@ def getPathPattern(operation): elif o.strategy == 'CROSS': pathchunks.extend(getPathPatternParallel(o, o.parallel_angle)) - pathchunks.extend(getPathPatternParallel( - o, o.parallel_angle - math.pi / 2.0)) + pathchunks.extend(getPathPatternParallel(o, o.parallel_angle - math.pi / 2.0)) elif o.strategy == 'BLOCK': @@ -330,8 +329,7 @@ def getPathPattern(operation): pathchunks = [] chunks = [] for p in polys: - p = p.buffer(-o.dist_between_paths / 10, - o.optimisation.circle_detail) + p = p.buffer(-o.dist_between_paths / 10, o.optimisation.circle_detail) # first, move a bit inside, because otherwise the border samples go crazy very often changin between # hit/non hit and making too many jumps in the path. chunks.extend(shapelyToChunks(p, 0)) @@ -346,8 +344,7 @@ def getPathPattern(operation): for porig in polys: p = porig while not p.is_empty: - p = p.buffer(-o.dist_between_paths, - o.optimisation.circle_detail) + p = p.buffer(-o.dist_between_paths, o.optimisation.circle_detail) if not p.is_empty: nchunks = shapelyToChunks(p, zlevel) diff --git a/scripts/addons/cam/puzzle_joinery.py b/scripts/addons/cam/puzzle_joinery.py index 4e6989169..7a554a4b6 100644 --- a/scripts/addons/cam/puzzle_joinery.py +++ b/scripts/addons/cam/puzzle_joinery.py @@ -133,8 +133,7 @@ def fingers(diameter, inside, amount=1, stem=1): def twistf(name, length, diameter, tolerance, twist, tneck, tthick, twist_keep=False): # add twist lock to receptacle if twist: - joinery.interlock_twist(length, tthick, tolerance, - cx=0, cy=0, rotation=0, percentage=tneck) + joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) simple.rotate(math.pi / 2) simple.move(y=-tthick / 2 + 2 * diameter + 2 * tolerance) simple.active_name('xtemptwist') @@ -151,8 +150,7 @@ def twistm(name, length, diameter, tolerance, twist, tneck, tthick, angle, twist # add twist lock to male connector global DT if twist: - joinery.interlock_twist(length, tthick, tolerance, - cx=0, cy=0, rotation=0, percentage=tneck) + joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) simple.rotate(math.pi / 2) simple.move(y=-tthick / 2 + 2 * diameter * DT) simple.rotate(angle) @@ -200,8 +198,7 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= twistm('tmprect', thick, diameter, tolerance, twist, tneck, tthick, -math.pi / 2, x=width / 2, twist_keep=twist_keep) - twistf('receptacle', thick, diameter, tolerance, - twist, tneck, tthick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) simple.rename('receptacle', '_tmpreceptacle') if which == 'FF' or which == 'F' or which == 'MF': simple.rotate(-math.pi / 2) @@ -218,8 +215,7 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= simple.remove_multiple("fingers") # Remove temporary base and holes if twist_line: - joinery.twist_line(thick, tthick, tolerance, - tneck, twist_line_amount, width) + joinery.twist_line(thick, tthick, tolerance, tneck, twist_line_amount, width) if twist_keep: simple.duplicate() simple.active_name('tmptwist') @@ -257,10 +253,8 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False amount = round(thick / ((4 + 2 * (stem - 1)) * diameter * DT)) - 1 fingers(diameter, tolerance, amount, stem=stem) - twistf('receptacle', thick, diameter, tolerance, - twist, tneck, tthick, twist_keep=twist_keep) - twistf('testing', thick, diameter, tolerance, - twist, tneck, tthick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) + twistf('testing', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) print("generating arc") # generate arc bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Segment', @@ -279,8 +273,7 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False if which == 'MF' or which == 'M': simple.union('_tmp') simple.active_name("base") - twistm('base', thick, diameter, tolerance, - twist, tneck, tthick, math.pi, x=radius) + twistm('base', thick, diameter, tolerance, twist, tneck, tthick, math.pi, x=radius) simple.rename('base', '_tmparc') simple.rename('receptacle', '_tmpreceptacle') @@ -299,8 +292,7 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False if which == 'M': simple.rotate(-angle) simple.mirrory() - bpy.ops.object.transform_apply( - location=True, rotation=True, scale=False) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=False) simple.rotate(-math.pi / 2) simple.move(y=radius) simple.rename('PUZZLE_arc', 'PUZZLE_arc_male') @@ -365,8 +357,7 @@ def arcbararc(length, radius, thick, angle, angleb, diameter, tolerance, amount= simple.active_name('tmprect') if twist_line: - joinery.twist_line(thick, tthick, tolerance, tneck, - twist_line_amount, length) + joinery.twist_line(thick, tthick, tolerance, tneck, twist_line_amount, length) if twist_keep: simple.duplicate() simple.active_name('tmptwist') @@ -452,8 +443,7 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis r_exterior = radius + thick / 2 r_interior = radius - thick / 2 - height = math.sqrt(r_exterior * r_exterior - - radius * radius) + r_interior / 4 + height = math.sqrt(r_exterior * r_exterior - radius * radius) + r_interior / 4 bpy.ops.curve.simple(align='WORLD', location=(0, height, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', @@ -717,8 +707,7 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, twistm('tmp_curve', thick, diameter, tolerance, twist, t_neck, t_thick, end_angle, x=p_end[0], y=p_end[1], twist_keep=twist_keep) - twistf('receptacle', thick, diameter, tolerance, - twist, t_neck, t_thick, twist_keep=twist_keep) + twistf('receptacle', thick, diameter, tolerance, twist, t_neck, t_thick, twist_keep=twist_keep) simple.rename('receptacle', 'tmp') simple.rotate(start_angle+math.pi) simple.move(x=p_start[0], y=p_start[1]) @@ -780,5 +769,4 @@ def tile(diameter, tolerance, tile_x_amount, tile_y_amount, stem=1): simple.rotate(math.pi/2) simple.move(x=width/2) simple.difference('_', '_base') - simple.active_name('tile_ ' + str(tile_x_amount) + - '_' + str(tile_y_amount)) + simple.active_name('tile_ ' + str(tile_x_amount) + '_' + str(tile_y_amount)) diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index a4656cd84..c05cf96be 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -325,8 +325,7 @@ def remove_doubles(): def add_overcut(diametre, overcut=True): if overcut: name = bpy.context.active_object.name - bpy.ops.object.curve_overcuts( - diameter=diametre, threshold=math.pi/2.05) + bpy.ops.object.curve_overcuts(diameter=diametre, threshold=math.pi/2.05) overcut_name = bpy.context.active_object.name make_active(name) bpy.ops.object.delete() diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 55fe23077..3682813af 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -352,8 +352,7 @@ def getCutterArray(operation, pixsize): if v.length <= cutter_r: z = -(v.length - ball_r) * s - Ball_R + D_ofset if v.length <= ball_r: - z = math.sin(math.acos(v.length / Ball_R) - ) * Ball_R - Ball_R + z = math.sin(math.acos(v.length / Ball_R)) * Ball_R - Ball_R car.itemset((a, b), z) elif type == 'CUSTOM': cutob = bpy.data.objects[operation.cutter_object_name] @@ -396,8 +395,7 @@ def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False): si[xs - m:xs - m + size, ys - m:ys - m + size] = np.minimum(si[xs - m:xs - m + size, ys - m:ys - m + size], cutterArray + z) if getvolume: - volarray = si[xs - m:xs - m + size, - ys - m:ys - m + size] - volarray + volarray = si[xs - m:xs - m + size, ys - m:ys - m + size] - volarray vsum = abs(volarray.sum()) # print(vsum) return vsum diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 9d728bf2e..5938c9144 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -266,8 +266,7 @@ async def proj_curve(s, o): from cam import chunk if targetCurve.type != 'CURVE': - raise CamException( - 'Projection target and source have to be curve objects!') + raise CamException('Projection target and source have to be curve objects!') if 1: extend_up = 0.1 @@ -329,8 +328,7 @@ async def pocket(o): print("cutter offset", c_offset) p = utils.getObjectOutline(c_offset, o, False) - approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / - o.dist_between_paths) / 2 + approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 print("approximative:" + str(approxn)) print(o) @@ -388,8 +386,7 @@ async def pocket(o): # helix_enter first try here TODO: check if helix radius is not out of operation area. if o.movement.helix_enter: - helix_radius = c_offset * o.movement.helix_diameter * \ - 0.01 # 90 percent of cutter radius + helix_radius = c_offset * o.movement.helix_diameter * 0.01 # 90 percent of cutter radius helix_circumference = helix_radius * pi * 2 revheight = helix_circumference * tan(o.movement.ramp_in_angle) @@ -398,8 +395,7 @@ async def pocket(o): # TODO:intercept closest next point when it should stay low p = ch.get_point(0) # first thing to do is to check if helix enter can really enter. - checkc = Circle(helix_radius + c_offset, - o.optimisation.circle_detail) + checkc = Circle(helix_radius + c_offset, o.optimisation.circle_detail) checkc = affinity.translate(checkc, p[0], p[1]) covers = False for poly in o.silhouete: @@ -410,8 +406,7 @@ async def pocket(o): if covers: revolutions = (l[0] - p[2]) / revheight # print(revolutions) - h = Helix( - helix_radius, o.optimisation.circle_detail, l[0], p, revolutions) + h = Helix(helix_radius, o.optimisation.circle_detail, l[0], p, revolutions) # invert helix if not the typical direction if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW') or ( o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW'): @@ -531,12 +526,9 @@ async def drill(o): if ob.type == 'CURVE': ob.data.dimensions = '3D' try: - bpy.ops.object.transform_apply( - location=True, rotation=False, scale=False) - bpy.ops.object.transform_apply( - location=False, rotation=True, scale=False) - bpy.ops.object.transform_apply( - location=False, rotation=False, scale=True) + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) except: pass @@ -548,8 +540,7 @@ async def drill(o): maxx, minx, maxy, miny, maxz, minz = -10000, 10000, -10000, 10000, -10000, 10000 for p in c.points: if o.drill_type == 'ALL_POINTS': - chunks.append(camPathChunk( - [(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) + chunks.append(camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) @@ -558,8 +549,7 @@ async def drill(o): maxz = max(p.co.z, maxz) for p in c.bezier_points: if o.drill_type == 'ALL_POINTS': - chunks.append(camPathChunk( - [(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) + chunks.append(camPathChunk([(p.co.x + l.x, p.co.y + l.y, p.co.z + l.z)])) minx = min(p.co.x, minx) maxx = max(p.co.x, maxx) miny = min(p.co.y, miny) @@ -573,8 +563,7 @@ async def drill(o): center = (cx, cy) aspect = (maxx - minx) / (maxy - miny) if (1.3 > aspect > 0.7 and o.drill_type == 'MIDDLE_SYMETRIC') or o.drill_type == 'MIDDLE_ALL': - chunks.append(camPathChunk( - [(center[0] + l.x, center[1] + l.y, cz + l.z)])) + chunks.append(camPathChunk([(center[0] + l.x, center[1] + l.y, cz + l.z)])) elif ob.type == 'MESH': for v in ob.data.vertices: @@ -639,8 +628,7 @@ async def medial_axis(o): elif o.cutter_type == 'BALLNOSE': maxdepth = - new_cutter_diameter / 2 - o.skin else: - raise CamException( - "Only Ballnose and V-carve cutters are supported for meial axis.") + raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.") # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] @@ -661,8 +649,7 @@ async def medial_axis(o): # just a multipolygon mpoly = polys else: - raise CamException( - "Failed getting object silhouette. Is input curve closed?") + raise CamException("Failed getting object silhouette. Is input curve closed?") mpoly_boundary = mpoly.boundary ipol = 0 @@ -724,8 +711,7 @@ async def medial_axis(o): vertr.append((False, newIdx)) if o.cutter_type == 'VCARVE': # start the z depth calc from the "start depth" of the operation. - z = o.maxz - \ - mpoly.boundary.distance(sgeometry.Point(p)) * slope + z = o.maxz - mpoly.boundary.distance(sgeometry.Point(p)) * slope if z < maxdepth: z = maxdepth elif o.cutter_type == 'BALL' or o.cutter_type == 'BALLNOSE': @@ -821,13 +807,11 @@ def getLayers(operation, startdepth, enddepth): if operation.use_layers: layers = [] n = math.ceil((startdepth - enddepth) / operation.stepdown) - print("start " + str(startdepth) + " end " + - str(enddepth) + " n " + str(n)) + print("start " + str(startdepth) + " end " + str(enddepth) + " n " + str(n)) layerstart = operation.maxz for x in range(0, n): - layerend = round( - max(startdepth - ((x + 1) * operation.stepdown), enddepth), 6) + layerend = round(max(startdepth - ((x + 1) * operation.stepdown), enddepth), 6) if int(layerstart * 10 ** 8) != int(layerend * 10 ** 8): # it was possible that with precise same end of operation, # last layer was done 2x on exactly same level... @@ -850,8 +834,7 @@ def chunksToMesh(chunks, o): if o.machine_axes == '3': if m.use_position_definitions: - origin = (m.starting_position.x, m.starting_position.y, - m.starting_position.z) # dhull + origin = (m.starting_position.x, m.starting_position.y, m.starting_position.z) # dhull else: origin = (0, 0, free_height) diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 7842d1b22..830b1ceff 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -42,14 +42,12 @@ def addTestCurve(loc): def addTestMesh(loc): - bpy.ops.mesh.primitive_monkey_add( - radius=.01, align='WORLD', enter_editmode=False, location=loc) + bpy.ops.mesh.primitive_monkey_add(radius=.01, align='WORLD', enter_editmode=False, location=loc) bpy.ops.transform.rotate(value=-1.5708, axis=(1, 0, 0), constraint_axis=(True, False, False), orient_type='GLOBAL') bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) bpy.ops.object.editmode_toggle() - bpy.ops.mesh.primitive_plane_add( - radius=1, align='WORLD', enter_editmode=False, location=loc) + bpy.ops.mesh.primitive_plane_add(radius=1, align='WORLD', enter_editmode=False, location=loc) bpy.ops.transform.resize(value=(0.01, 0.01, 0.01), constraint_axis=(False, False, False), orient_type='GLOBAL') bpy.ops.transform.translate(value=(-0.01, 0, 0), constraint_axis=(True, False, False), @@ -163,8 +161,7 @@ def testOperation(i): if v1.co != v2.co: different_co_count += 1 if different_co_count > 0: - report += 'vertex position is different on %i vertices \n\n' % ( - different_co_count) + report += 'vertex position is different on %i vertices \n\n' % (different_co_count) test_ok = False if test_ok: report += 'test ok\n\n' diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index 4a095f382..9423bd83c 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -52,8 +52,7 @@ def draw_minz(self): return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: if self.op.strategy == 'CURVE': - self.layout.label( - text="cannot use depth from object using CURVES") + self.layout.label(text="cannot use depth from object using CURVES") row = self.layout.row(align=True) row.label(text='Set max depth from') @@ -67,10 +66,8 @@ def draw_minz(self): if self.op.source_image_name != '': i = bpy.data.images[self.op.source_image_name] if i is not None: - sy = int((self.op.source_image_size_x / - i.size[0]) * i.size[1] * 1000000) / 1000 - self.layout.label( - text='image size on y axis: ' + strInUnits(sy, 8)) + sy = int((self.op.source_image_size_x / i.size[0]) * i.size[1] * 1000000) / 1000 + self.layout.label(text='image size on y axis: ' + strInUnits(sy, 8)) self.layout.separator() self.layout.prop(self.op, 'source_image_offset') col = self.layout.column(align=True) @@ -96,8 +93,7 @@ def draw_limit_curve(self): if self.op.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: self.layout.prop(self.op, 'use_limit_curve') if self.op.use_limit_curve: - self.layout.prop_search( - self.op, "limit_curve", bpy.data, "objects") + self.layout.prop_search(self.op, "limit_curve", bpy.data, "objects") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 260821d5b..bf8b7c0cc 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -47,8 +47,7 @@ def draw(self, context): row = layout.row() scene = bpy.context.scene - row.template_list("CAM_UL_chains", '', scene, - "cam_chains", scene, 'cam_active_chain') + row.template_list("CAM_UL_chains", '', scene, "cam_chains", scene, 'cam_active_chain') col = row.column(align=True) col.operator("scene.cam_chain_add", icon='ADD', text="") col.operator("scene.cam_chain_remove", icon='REMOVE', text="") @@ -61,28 +60,21 @@ def draw(self, context): row.template_list("CAM_UL_operations", '', chain, "operations", chain, 'active_operation') col = row.column(align=True) - col.operator("scene.cam_chain_operation_add", - icon='ADD', text="") - col.operator("scene.cam_chain_operation_remove", - icon='REMOVE', text="") + col.operator("scene.cam_chain_operation_add", icon='ADD', text="") + col.operator("scene.cam_chain_operation_remove", icon='REMOVE', text="") if len(chain.operations) > 0: - col.operator("scene.cam_chain_operation_up", - icon='TRIA_UP', text="") - col.operator("scene.cam_chain_operation_down", - icon='TRIA_DOWN', text="") + col.operator("scene.cam_chain_operation_up", icon='TRIA_UP', text="") + col.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="") if not chain.computing: layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode") - layout.operator("object.cam_export_paths_chain", - text="Export chain gcode") - layout.operator("object.cam_simulate_chain", - text="Simulate this chain") + layout.operator("object.cam_export_paths_chain", text="Export chain gcode") + layout.operator("object.cam_simulate_chain", text="Simulate this chain") valid, reason = isChainValid(chain, context) if not valid: - layout.label( - icon="ERROR", text=f"Can't compute chain - reason:\n") + layout.label(icon="ERROR", text=f"Can't compute chain - reason:\n") layout.label(text=reason) else: layout.label(text='chain is currently computing') diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index 1a62723ef..08afeb270 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -30,11 +30,9 @@ def draw_cutter_preset_menu(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_CUTTER_MT_presets", - text=bpy.types.CAM_CUTTER_MT_presets.bl_label) + row.menu("CAM_CUTTER_MT_presets", text=bpy.types.CAM_CUTTER_MT_presets.bl_label) row.operator("render.cam_preset_cutter_add", text="", icon='ADD') - row.operator("render.cam_preset_cutter_add", text="", - icon='REMOVE').remove_active = True + row.operator("render.cam_preset_cutter_add", text="", icon='REMOVE').remove_active = True def draw_cutter_id(self): if not self.has_correct_level(): @@ -99,8 +97,7 @@ def draw_custom(self): text='Warning - only convex shapes are supported. ', icon='COLOR_RED') self.layout.label(text='If your custom cutter is concave,') self.layout.label(text='switch exact mode off.') - self.layout.prop_search( - self.op, "cutter_object_name", bpy.data, "objects") + self.layout.prop_search(self.op, "cutter_object_name", bpy.data, "objects") def draw_cutter_diameter(self): if not self.has_correct_level(): @@ -127,11 +124,9 @@ def draw_engagement(self): return if self.op.cutter_type in ['BALLCONE']: - engagement = round( - 100 * self.op.dist_between_paths / self.op.ball_radius, 1) + engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) else: - engagement = round( - 100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) + engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) self.layout.label(text=f"Cutter engagement: {engagement}%") diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index 9ed830e7c..e54b829ba 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -30,11 +30,9 @@ def draw_presets(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_MACHINE_MT_presets", - text=bpy.types.CAM_MACHINE_MT_presets.bl_label) + row.menu("CAM_MACHINE_MT_presets", text=bpy.types.CAM_MACHINE_MT_presets.bl_label) row.operator("render.cam_preset_machine_add", text="", icon='ADD') - row.operator("render.cam_preset_machine_add", text="", - icon='REMOVE').remove_active = True + row.operator("render.cam_preset_machine_add", text="", icon='REMOVE').remove_active = True def draw_post_processor(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index 8f0d786ac..e9e0d2eff 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -38,11 +38,9 @@ def draw_cutter_engagement(self): return if self.op.cutter_type in ['BALLCONE']: - engagement = round( - 100 * self.op.dist_between_paths / self.op.ball_radius, 1) + engagement = round(100 * self.op.dist_between_paths / self.op.ball_radius, 1) else: - engagement = round( - 100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) + engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) if engagement > 50: self.layout.label(text="Warning: High cutter engagement") @@ -202,11 +200,9 @@ def draw_bridges_options(self): if self.op.use_bridges: self.layout.prop(self.op, 'bridges_width') self.layout.prop(self.op, 'bridges_height') - self.layout.prop_search( - self.op, "bridges_collection_name", bpy.data, "collections") + self.layout.prop_search(self.op, "bridges_collection_name", bpy.data, "collections") self.layout.prop(self.op, 'use_bridge_modifiers') - self.layout.operator("scene.cam_bridges_add", - text="Autogenerate bridges") + self.layout.operator("scene.cam_bridges_add", text="Autogenerate bridges") def draw_skin(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index 1baed37dc..e7d3973b0 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -41,10 +41,8 @@ def draw_operations_list(self): col.operator("scene.cam_operation_copy", icon='COPYDOWN', text="") col.operator("scene.cam_operation_remove", icon='REMOVE', text="") col.separator() - col.operator("scene.cam_operation_move", - icon='TRIA_UP', text="").direction = 'UP' - col.operator("scene.cam_operation_move", - icon='TRIA_DOWN', text="").direction = 'DOWN' + col.operator("scene.cam_operation_move", icon='TRIA_UP', text="").direction = 'UP' + col.operator("scene.cam_operation_move", icon='TRIA_DOWN', text="").direction = 'DOWN' # Draw the list of preset operations, and preset add and remove buttons @@ -52,11 +50,9 @@ def draw_presets(self): if not self.has_correct_level(): return row = self.layout.row(align=True) - row.menu("CAM_OPERATION_MT_presets", - text=bpy.types.CAM_OPERATION_MT_presets.bl_label) + row.menu("CAM_OPERATION_MT_presets", text=bpy.types.CAM_OPERATION_MT_presets.bl_label) row.operator("render.cam_preset_operation_add", text="", icon='ADD') - row.operator("render.cam_preset_operation_add", text="", - icon='REMOVE').remove_active = True + row.operator("render.cam_preset_operation_add", text="", icon='REMOVE').remove_active = True def draw_calculate_path(self): if not self.has_correct_level(): @@ -68,11 +64,9 @@ def draw_calculate_path(self): self.layout.prop(self.op.movement, 'free_height') if not self.op.valid: - self.layout.label( - text="Select a valid object to calculate the path.") + self.layout.label(text="Select a valid object to calculate the path.") # will be disable if not valid - self.layout.operator("object.calculate_cam_path", - text="Calculate path & export Gcode") + self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") def draw_export_gcode(self): if not self.has_correct_level(): @@ -81,15 +75,13 @@ def draw_export_gcode(self): if self.op.name is not None: name = f"cam_path_{self.op.name}" if bpy.context.scene.objects.get(name) is not None: - self.layout.operator( - "object.cam_export", text="Export Gcode ") + self.layout.operator("object.cam_export", text="Export Gcode ") def draw_simulate_op(self): if not self.has_correct_level(): return if self.op.valid: - self.layout.operator("object.cam_simulate", - text="Simulate this operation") + self.layout.operator("object.cam_simulate", text="Simulate this operation") def draw_op_name(self): if not self.has_correct_level(): @@ -112,33 +104,26 @@ def draw_operation_source(self): if self.op.strategy == 'CURVE': if self.op.geometry_source == 'OBJECT': - self.layout.prop_search( - self.op, "object_name", bpy.data, "objects") + self.layout.prop_search(self.op, "object_name", bpy.data, "objects") elif self.op.geometry_source == 'COLLECTION': - self.layout.prop_search( - self.op, "collection_name", bpy.data, "collections") + self.layout.prop_search(self.op, "collection_name", bpy.data, "collections") else: if self.op.geometry_source == 'OBJECT': - self.layout.prop_search( - self.op, "object_name", bpy.data, "objects") + self.layout.prop_search(self.op, "object_name", bpy.data, "objects") if self.op.enable_A: self.layout.prop(self.op, 'rotation_A') if self.op.enable_B: self.layout.prop(self.op, 'rotation_B') elif self.op.geometry_source == 'COLLECTION': - self.layout.prop_search( - self.op, "collection_name", bpy.data, "collections") + self.layout.prop_search(self.op, "collection_name", bpy.data, "collections") else: - self.layout.prop_search( - self.op, "source_image_name", bpy.data, "images") + self.layout.prop_search(self.op, "source_image_name", bpy.data, "images") if self.op.strategy in ['CARVE', 'PROJECTED_CURVE']: - self.layout.prop_search( - self.op, "curve_object", bpy.data, "objects") + self.layout.prop_search(self.op, "curve_object", bpy.data, "objects") if self.op.strategy == 'PROJECTED_CURVE': - self.layout.prop_search( - self.op, "curve_object1", bpy.data, "objects") + self.layout.prop_search(self.op, "curve_object1", bpy.data, "objects") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index fd039255a..02ff435a1 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -78,8 +78,7 @@ def positionObject(operation): ob.select_set(True) bpy.context.view_layer.objects.active = ob - minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace( - [ob], operation.use_modifiers) + minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace([ob], operation.use_modifiers) totx = maxx - minx toty = maxy - miny totz = maxz - minz @@ -101,8 +100,7 @@ def positionObject(operation): ob.location.z -= minz + totz / 2 if ob.type != 'CURVE': - bpy.ops.object.transform_apply( - location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) # addMaterialAreaObject() @@ -156,8 +154,7 @@ def getBoundsWorldspace(obs, use_modifiers=False): bpy.ops.outliner.orphans_purge() else: if not hasattr(ob.data, "splines"): - raise CamException( - "Can't do CAM operation on the selected object type") + raise CamException("Can't do CAM operation on the selected object type") # for coord in bb: for c in ob.data.splines: for p in c.bezier_points: @@ -262,8 +259,7 @@ def getBounds(o): # print('kolikrat sem rpijde') if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION' or o.geometry_source == 'CURVE': print("valid geometry") - minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace( - o.objects, o.use_modifiers) + minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace(o.objects, o.use_modifiers) if o.minz_from == 'OBJECT': if minz == 10000000: @@ -368,18 +364,14 @@ def samplePathLow(o, ch1, ch2, dosample): cutterdepth = o.cutter_shape.dimensions.z / 2 for p in bpath_points: - z = getSampleBullet( - o.cutter_shape, p[0], p[1], cutterdepth, 1, o.minz) + z = getSampleBullet(o.cutter_shape, p[0], p[1], cutterdepth, 1, o.minz) if z > p[2]: p[2] = z else: for p in bpath_points: - xs = (p[0] - o.min.x) / pixsize + \ - o.borderwidth + pixsize / 2 # -m - ys = (p[1] - o.min.y) / pixsize + \ - o.borderwidth + pixsize / 2 # -m - z = getSampleImage( - (xs, ys), o.offset_image, o.minz) + o.skin + xs = (p[0] - o.min.x) / pixsize + o.borderwidth + pixsize / 2 # -m + ys = (p[1] - o.min.y) / pixsize + o.borderwidth + pixsize / 2 # -m + z = getSampleImage((xs, ys), o.offset_image, o.minz) + o.skin if z > p[2]: p[2] = z return camPathChunk(bpath_points) @@ -458,8 +450,7 @@ async def sampleChunks(o, pathSamples, layers): # for t in range(0,threads): our_points = patternchunk.get_points_np() - ambient_contains = shapely.contains( - o.ambient, shapely.points(our_points[:, 0:2])) + ambient_contains = shapely.contains(o.ambient, shapely.points(our_points[:, 0:2])) for s, in_ambient in zip(our_points, ambient_contains): if o.strategy != 'WATERLINE' and int(100 * n / totlen) != last_percent: last_percent = int(100 * n / totlen) @@ -539,8 +530,7 @@ async def sampleChunks(o, pathSamples, layers): v1 = lastsample v2 = newsample if o.movement.protect_vertical: - v1, v2 = isVerticalLimit( - v1, v2, o.movement.protect_vertical_limit) + v1, v2 = isVerticalLimit(v1, v2, o.movement.protect_vertical_limit) v1 = Vector(v1) v2 = Vector(v2) # print(v1,v2) @@ -555,16 +545,12 @@ async def sampleChunks(o, pathSamples, layers): layeractivechunks[ls].points.insert(-1, betweensample.to_tuple()) else: - layeractivechunks[ls].points.append( - betweensample.to_tuple()) - layeractivechunks[ls + - 1].points.append(betweensample.to_tuple()) + layeractivechunks[ls].points.append(betweensample.to_tuple()) + layeractivechunks[ls + 1].points.append(betweensample.to_tuple()) else: # print(v1,v2,betweensample,lastlayer,currentlayer) - layeractivechunks[ls].points.insert( - -1, betweensample.to_tuple()) - layeractivechunks[ls + 1].points.insert( - 0, betweensample.to_tuple()) + layeractivechunks[ls].points.insert(-1, betweensample.to_tuple()) + layeractivechunks[ls + 1].points.insert(0, betweensample.to_tuple()) li += 1 # this chunk is terminated, and allready in layerchunks / @@ -717,8 +703,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): bpy.context.scene.frame_set(2) bpy.context.scene.frame_set(0) - newsample = getSampleBulletNAxis( - cutter, startp, endp, rotation, cutterdepth) + newsample = getSampleBulletNAxis(cutter, startp, endp, rotation, cutterdepth) # print('totok',startp,endp,rotation,newsample) ################################ @@ -766,11 +751,9 @@ async def sampleChunksNAxis(o, pathSamples, layers): for ls in r: splitdistance = layers[ls][1] - ratio = (splitdistance - lastdistance) / \ - (distance - lastdistance) + ratio = (splitdistance - lastdistance) / (distance - lastdistance) # print(ratio) - betweensample = lastsample + \ - (newsample - lastsample) * ratio + betweensample = lastsample + (newsample - lastsample) * ratio # this probably doesn't work at all!!!! check this algoritm> betweenrotation = tuple_add(lastrotation, tuple_mul(tuple_sub(rotation, lastrotation), ratio)) @@ -778,54 +761,34 @@ async def sampleChunksNAxis(o, pathSamples, layers): betweenstartpoint = laststartpoint + \ (startp - laststartpoint) * ratio # here, we need to have also possible endpoints always.. - betweenendpoint = lastendpoint + \ - (endp - lastendpoint) * ratio + betweenendpoint = lastendpoint + (endp - lastendpoint) * ratio if growing: if li > 0: - layeractivechunks[ls].points.insert( - -1, betweensample) - layeractivechunks[ls].rotations.insert( - -1, betweenrotation) + layeractivechunks[ls].points.insert(-1, betweensample) + layeractivechunks[ls].rotations.insert(-1, betweenrotation) layeractivechunks[ls].startpoints.insert( -1, betweenstartpoint) - layeractivechunks[ls].endpoints.insert( - -1, betweenendpoint) + layeractivechunks[ls].endpoints.insert(-1, betweenendpoint) else: - layeractivechunks[ls].points.append( - betweensample) - layeractivechunks[ls].rotations.append( - betweenrotation) - layeractivechunks[ls].startpoints.append( - betweenstartpoint) - layeractivechunks[ls].endpoints.append( - betweenendpoint) - layeractivechunks[ls + - 1].points.append(betweensample) - layeractivechunks[ls + - 1].rotations.append(betweenrotation) - layeractivechunks[ls + - 1].startpoints.append(betweenstartpoint) - layeractivechunks[ls + - 1].endpoints.append(betweenendpoint) + layeractivechunks[ls].points.append(betweensample) + layeractivechunks[ls].rotations.append(betweenrotation) + layeractivechunks[ls].startpoints.append(betweenstartpoint) + layeractivechunks[ls].endpoints.append(betweenendpoint) + layeractivechunks[ls + 1].points.append(betweensample) + layeractivechunks[ls + 1].rotations.append(betweenrotation) + layeractivechunks[ls + 1].startpoints.append(betweenstartpoint) + layeractivechunks[ls + 1].endpoints.append(betweenendpoint) else: - layeractivechunks[ls].points.insert( - -1, betweensample) - layeractivechunks[ls].rotations.insert( - -1, betweenrotation) - layeractivechunks[ls].startpoints.insert( - -1, betweenstartpoint) - layeractivechunks[ls].endpoints.insert( - -1, betweenendpoint) - - layeractivechunks[ls + - 1].points.append(betweensample) - layeractivechunks[ls + - 1].rotations.append(betweenrotation) - layeractivechunks[ls + - 1].startpoints.append(betweenstartpoint) - layeractivechunks[ls + - 1].endpoints.append(betweenendpoint) + layeractivechunks[ls].points.insert(-1, betweensample) + layeractivechunks[ls].rotations.insert(-1, betweenrotation) + layeractivechunks[ls].startpoints.insert(-1, betweenstartpoint) + layeractivechunks[ls].endpoints.insert(-1, betweenendpoint) + + layeractivechunks[ls + 1].points.append(betweensample) + layeractivechunks[ls + 1].rotations.append(betweenrotation) + layeractivechunks[ls + 1].startpoints.append(betweenstartpoint) + layeractivechunks[ls + 1].endpoints.append(betweenendpoint) # layeractivechunks[ls+1].points.insert(0,betweensample) li += 1 @@ -941,10 +904,8 @@ def silhoueteOffset(context, offset, style=1, mitrelimit=1.0): mp = shapely.ops.unary_union(silhs) print("offset attributes:") print(offset, style) - mp = mp.buffer(offset, cap_style=1, join_style=style, - resolution=16, mitre_limit=mitrelimit) - shapelyToCurve(ob.name + '_offset_' + - str(round(offset, 5)), mp, ob.location.z) + mp = mp.buffer(offset, cap_style=1, join_style=style, resolution=16, mitre_limit=mitrelimit) + shapelyToCurve(ob.name + '_offset_' + str(round(offset, 5)), mp, ob.location.z) return {'FINISHED'} @@ -1311,8 +1272,7 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): polys = [] for ob in objects: if use_modifiers: - ob = ob.evaluated_get( - bpy.context.evaluated_depsgraph_get()) + ob = ob.evaluated_get(bpy.context.evaluated_depsgraph_get()) m = ob.to_mesh() else: m = ob.data @@ -1443,8 +1403,7 @@ def addOrientationObject(o): name = o.name + ' orientation' s = bpy.context.scene if s.objects.find(name) == -1: - bpy.ops.object.empty_add( - type='ARROWS', align='WORLD', location=(0, 0, 0)) + bpy.ops.object.empty_add(type='ARROWS', align='WORLD', location=(0, 0, 0)) ob = bpy.context.active_object ob.empty_draw_size = 0.05 @@ -1512,13 +1471,11 @@ def addMachineAreaObject(): o = bpy.context.active_object o.name = 'CAM_machine' o.data.name = 'CAM_machine' - bpy.ops.object.transform_apply( - location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) # o.type = 'SOLID' bpy.ops.object.editmode_toggle() bpy.ops.mesh.delete(type='ONLY_FACE') - bpy.ops.mesh.select_mode( - use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') + bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='EDGE', action='TOGGLE') bpy.ops.mesh.select_all(action='TOGGLE') bpy.ops.mesh.subdivide(number_cuts=32, smoothness=0, quadcorner='STRAIGHT_CUT', fractal=0, fractal_along_normal=0, seed=0) @@ -1560,8 +1517,7 @@ def addMaterialAreaObject(): o = bpy.context.active_object o.name = 'CAM_material' o.data.name = 'CAM_material' - bpy.ops.object.transform_apply( - location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) # addTranspMat(o, 'blue_transparent', (0.458695, 0.794658, 0.8), 0.1) o.display_type = 'BOUNDS' diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 08b424b11..6afcba66a 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,7) +__version__ = (1, 0, 7) diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index 77e6b780c..529d0c6cd 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -99,8 +99,7 @@ def getClipEdges(self): x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] pt1, pt2 = (x1, y1), (x2, y2) - inExtentP1, inExtentP2 = self.inExtent( - x1, y1), self.inExtent(x2, y2) + inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) if inExtentP1 and inExtentP2: clipEdges.append((pt1, pt2)) elif inExtentP1 and not inExtentP2: @@ -111,12 +110,10 @@ def getClipEdges(self): clipEdges.append((pt1, pt2)) else: # infinite line if edge[1] != -1: - x1, y1 = self.vertices[edge[1] - ][0], self.vertices[edge[1]][1] + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] leftDir = False else: - x1, y1 = self.vertices[edge[2] - ][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] leftDir = True if self.inExtent(x1, y1): pt1 = (x1, y1) @@ -149,12 +146,10 @@ def getClipPolygons(self, closePoly): clipEdges.append((pt1, pt2)) else: # infinite line if edge[1] != -1: - x1, y1 = self.vertices[edge[1] - ][0], self.vertices[edge[1]][1] + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] leftDir = False else: - x1, y1 = self.vertices[edge[2] - ][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] leftDir = True if self.inExtent(x1, y1): pt1 = (x1, y1) @@ -328,10 +323,8 @@ def outEdge(self, edge): self.polygons[edge.reg[0].sitenum] = [] if edge.reg[1].sitenum not in self.polygons: self.polygons[edge.reg[1].sitenum] = [] - self.polygons[edge.reg[0].sitenum].append( - (edge.edgenum, sitenumL, sitenumR)) - self.polygons[edge.reg[1].sitenum].append( - (edge.edgenum, sitenumL, sitenumR)) + self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR)) + self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR)) self.edges.append((edge.edgenum, sitenumL, sitenumR)) @@ -536,8 +529,7 @@ def __init__(self): self.edgenum = 0 def dump(self): - print("(#%d a=%g, b=%g, c=%g)" % - (self.edgenum, self.a, self.b, self.c)) + print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c)) print("ep", self.ep) print("reg", self.reg) From 2e3acdd7506f77922483bdecdce838be366e1ae0 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:16:55 -0400 Subject: [PATCH 016/100] Import fix update - bpy.utils, autoformat fix While rearranging and cleaning up the utils imports in the initial 'Import fix' commit I deleted `utils.` from `bpy.utils.script_paths()` in the ops.py file. This has been fixed, and I took the opportunity to reverse the autoformat issue (changed line length from 100 to 89 between commits), and restore all the code to its previous state. I made one exception: - comments that came at the end of the line that were moved to the line above All functional code has been restored to its previous format. --- .../parallel/Op_Parallel_Internal.gcode | 511 ++++++++++++ .../parallel/Op_Parallel_Internal_Exact.gcode | 466 +++++++++++ .../parallel_Op_Parallel_Internal_off.exr | Bin 0 -> 228093 bytes .../parallel_Op_Parallel_Internal_z.exr | Bin 0 -> 140914 bytes .../cam/tests/test_data/patterns/Cross.gcode | 730 ++++++++++++++++++ .../temp_cam/patterns_Circles_off.exr | Bin 0 -> 228093 bytes .../patterns/temp_cam/patterns_Circles_z.exr | Bin 0 -> 140914 bytes .../temp_cam/patterns_Parallel_off.exr | Bin 0 -> 228093 bytes .../patterns/temp_cam/patterns_Parallel_z.exr | Bin 0 -> 140914 bytes .../patterns/temp_cam/patterns_Spiral_off.exr | Bin 0 -> 228093 bytes .../patterns/temp_cam/patterns_Spiral_z.exr | Bin 0 -> 140914 bytes ...waterline_Waterline_Internal_Exact_off.exr | Bin 0 -> 446445 bytes .../waterline_Waterline_Internal_Exact_z.exr | Bin 0 -> 286010 bytes .../waterline_Waterline_Internal_off.exr | Bin 0 -> 458922 bytes .../waterline_Waterline_Internal_z.exr | Bin 0 -> 297821 bytes 15 files changed, 1707 insertions(+) create mode 100644 scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal.gcode create mode 100644 scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal_Exact.gcode create mode 100644 scripts/addons/cam/tests/test_data/parallel/temp_cam/parallel_Op_Parallel_Internal_off.exr create mode 100644 scripts/addons/cam/tests/test_data/parallel/temp_cam/parallel_Op_Parallel_Internal_z.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/Cross.gcode create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Circles_off.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Circles_z.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Parallel_off.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Parallel_z.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Spiral_off.exr create mode 100644 scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Spiral_z.exr create mode 100644 scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_off.exr create mode 100644 scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_z.exr create mode 100644 scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_off.exr create mode 100644 scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_z.exr diff --git a/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal.gcode b/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal.gcode new file mode 100644 index 000000000..9a5d6ae66 --- /dev/null +++ b/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal.gcode @@ -0,0 +1,511 @@ +(Created with grbl post processor 2024/03/23 07:35) +G21 +(G-code generated with BlenderCAM and NC library) +G17G90 +(Tool: D = 3.0 mm type END flutes 2) +S12000M03 +G00 Z2.0 + +G0X0Y0Z2 +X-68Y-7.999 +G1Z-3.1F500 +Y8F1000 +G0Z2 +X-66Y18 +G1Z-3.1F500 +Y-17.999F1000 +G0Z2 +X-64Y-23.999 +G1Z-3.1F500 +Y24F1000 +G0Z2 +X-62Y29 +G1Z-3.1F500 +Y-28.999F1000 +X-60Y-32.999 +Y33 +X-58Y36 +Y-35.999 +X-56Y-38.999 +Y39 +X-54Y42 +Y-41.999 +X-52Y-43.999 +Y44 +X-50Y46 +Y-45.999 +X-48Y-47.999 +Y48 +X-46Y50 +Y-49.999 +X-44Y-51.999 +Y52 +X-42Y54 +Y-53.999 +X-40Y-54.999 +Y55 +X-38Y56 +Y-55.999 +X-36Y-57.999 +Y58 +X-34Y59 +Y-58.999 +X-32Y-59.999 +Y60 +X-30Y61 +Y-60.999 +X-28Y-61.999 +Y62 +X-26Y63 +Y-62.999 +X-24Y-63.999 +Y64 +X-22 +Y-63.999 +X-20Y-64.999 +Y65 +X-18Y66 +Y-65.999 +X-16 +Y66 +X-14Y67 +Y-66.999 +X-12 +Y67 +X-10 +Y-66.999 +X-8Y-67.999 +Y68 +X-6 +Y-67.999 +X-4 +Y68 +X-2 +Y-67.999 +X0 +Y68 +X2 +Y-67.999 +X4 +Y68 +X6 +Y-67.999 +X8 +Y68 +X10Y67 +Y-66.999 +X12 +Y67 +X14 +Y-66.999 +X16Y-65.999 +Y66 +X18 +Y-65.999 +X20Y-64.999 +Y65 +X22Y64 +Y-63.999 +X24 +Y64 +X26Y63 +Y-62.999 +X28Y-61.999 +Y62 +X30Y61 +Y-60.999 +X32Y-59.999 +Y60 +X34Y59 +Y-58.999 +X36Y-57.999 +Y58 +X38Y56 +Y-55.999 +X40Y-54.999 +Y55 +X42Y54 +Y-53.999 +X44Y-51.999 +Y52 +X46Y50 +Y-49.999 +X48Y-47.999 +Y48 +X50Y46 +Y-45.999 +X52Y-43.999 +Y44 +X54Y42 +Y-41.999 +X56Y-38.999 +Y39 +X58Y36 +Y-35.999 +X60Y-32.999 +Y33 +X62Y29 +Y-28.999 +G0Z2 +X64Y-23.999 +G1Z-3.1F500 +Y24F1000 +G0Z2 +X66Y18 +G1Z-3.1F500 +Y-17.999F1000 +G0Z2 +X68Y-7.999 +G1Z-3.1F500 +Y8F1000 +G0Z2 +X-68 +G1Z-6.1F500 +Y-7.999F1000 +G0Z2 +X-66Y-17.999 +G1Z-6.1F500 +Y18F1000 +G0Z2 +X-64Y24 +G1Z-6.1F500 +Y-23.999F1000 +G0Z2 +X-62Y-28.999 +G1Z-6.1F500 +Y29F1000 +X-60Y33 +Y-32.999 +X-58Y-35.999 +Y36 +X-56Y39 +Y-38.999 +X-54Y-41.999 +Y42 +X-52Y44 +Y-43.999 +X-50Y-45.999 +Y46 +X-48Y48 +Y-47.999 +X-46Y-49.999 +Y50 +X-44Y52 +Y-51.999 +X-42Y-53.999 +Y54 +X-40Y55 +Y-54.999 +X-38Y-55.999 +Y56 +X-36Y58 +Y-57.999 +X-34Y-58.999 +Y59 +X-32Y60 +Y-59.999 +X-30Y-60.999 +Y61 +X-28Y62 +Y-61.999 +X-26Y-62.999 +Y63 +X-24Y64 +Y-63.999 +X-22 +Y64 +X-20Y65 +Y-64.999 +X-18Y-65.999 +Y66 +X-16 +Y-65.999 +X-14Y-66.999 +Y67 +X-12 +Y-66.999 +X-10 +Y67 +X-8Y68 +Y-67.999 +X-6 +Y68 +X-4 +Y-67.999 +X-2 +Y68 +X0 +Y-67.999 +X2 +Y68 +X4 +Y-67.999 +X6 +Y68 +X8 +Y-67.999 +X10Y-66.999 +Y67 +X12 +Y-66.999 +X14 +Y67 +X16Y66 +Y-65.999 +X18 +Y66 +X20Y65 +Y-64.999 +X22Y-63.999 +Y64 +X24 +Y-63.999 +X26Y-62.999 +Y63 +X28Y62 +Y-61.999 +X30Y-60.999 +Y61 +X32Y60 +Y-59.999 +X34Y-58.999 +Y59 +X36Y58 +Y-57.999 +X38Y-55.999 +Y56 +X40Y55 +Y-54.999 +X42Y-53.999 +Y54 +X44Y52 +Y-51.999 +X46Y-49.999 +Y50 +X48Y48 +Y-47.999 +X50Y-45.999 +Y46 +X52Y44 +Y-43.999 +X54Y-41.999 +Y42 +X56Y39 +Y-38.999 +X58Y-35.999 +Y36 +X60Y33 +Y-32.999 +X62Y-28.999 +Y29 +G0Z2 +X64Y24 +G1Z-6.1F500 +Y-23.999F1000 +G0Z2 +X66Y-17.999 +G1Z-6.1F500 +Y18F1000 +G0Z2 +X68Y8 +G1Z-6.1F500 +Y-7.999F1000 +G0Z2 +X-68 +G1Z-6.912F500 +Y8F1000 +G0Z2 +X-66Y18 +G1Z-6.912F500 +Y-17.999F1000 +G0Z2 +X-64Y-23.999 +G1Z-6.912F500 +Y24F1000 +G0Z2 +X-62Y29 +G1Z-6.912F500 +Y-28.999F1000 +X-60Y-32.999 +Y32 +Y33Z-6.897 +G0Z2 +X-58Y36 +G1Z-6.912F500 +Y-35.999F1000 +X-56Y-38.999 +Y39 +X-54Y42 +Y-41.999 +X-52Y-43.999 +Y44 +X-50Y46 +Y-45.999 +X-48Y-47.999 +Y48 +X-46Y50 +Y-49.999 +X-44Y-51.999 +Y52 +G0Z2 +X-42Y54 +G1Z-6.899F500 +Y53Z-6.912F1000 +Y-53.999 +X-40Y-54.999 +Y55 +X-38Y56 +Y-55.999 +X-36Y-57.999 +Y58 +X-34Y59 +Y-58.999 +X-32Y-59.999 +Y60 +X-30Y61 +Y-60.999 +X-28Y-61.999 +Y62 +X-26Y63 +Y-62.999 +X-24Y-63.999 +Y64 +X-22 +Y-63.999 +X-20Y-64.999 +Y65 +G0Z2 +X-18Y66 +G1Z-6.9F500 +Y65Z-6.912F1000 +Y-65.999 +X-16 +Y66 +G0Z2 +X-14Y67 +G1Z-6.899F500 +Y66Z-6.912F1000 +Y-66.999 +X-12 +Y67 +X-10 +Y-66.999 +X-8Y-67.999 +Y67 +Y68Z-6.899 +G0Z2 +X-6 +G1Z-6.912F500 +Y-67.999F1000 +X-4 +Y68 +X-2 +Y-67.999 +X0 +Y68 +X2 +Y-67.999 +X4 +Y68 +X6 +Y-67.999 +X8 +Y67 +Y68Z-6.899 +G0Z2 +X10Y67 +G1Z-6.912F500 +Y-66.999F1000 +X12 +Y67 +G0Z2 +X14 +G1Z-6.899F500 +Y66Z-6.912F1000 +Y-66.999 +X16Y-65.999 +Y66 +G0Z2 +X18 +G1Z-6.9F500 +Y65Z-6.912F1000 +Y-65.999 +X20Y-64.999 +Y65 +X22Y64 +Y-63.999 +X24 +Y64 +X26Y63 +Y-62.999 +X28Y-61.999 +Y62 +X30Y61 +Y-60.999 +X32Y-59.999 +Y60 +X34Y59 +Y-58.999 +X36Y-57.999 +Y58 +X38Y56 +Y-55.999 +X40Y-54.999 +Y55 +G0Z2 +X42Y54 +G1Z-6.899F500 +Y53Z-6.912F1000 +Y-53.999 +X44Y-51.999 +Y52 +X46Y50 +Y-49.999 +X48Y-47.999 +Y48 +X50Y46 +Y-45.999 +X52Y-43.999 +Y44 +G0Z2 +X54Y42 +G1Z-6.911F500 +Y41Z-6.912F1000 +Y-40.999 +Y-41.999Z-6.911 +G0Z2 +X56Y-38.999 +G1Z-6.912F500 +Y39F1000 +X58Y36 +Y-35.999 +G0Z2 +X60Y-32.999 +G1Z-6.911F500 +Y-31.999Z-6.912F1000 +Y32 +Y33Z-6.896 +G0Z2 +X62Y29 +G1Z-6.911F500 +Y28Z-6.912F1000 +Y-27.999 +Y-28.999Z-6.911 +G0Z2 +X64Y-23.999 +G1Z-6.912F500 +Y24F1000 +G0Z2 +X66Y18 +G1Z-6.911F500 +Y17Z-6.912F1000 +Y-16.999 +Y-17.999Z-6.911 +G0Z2 +X68Y-7.999 +G1Z-6.911F500 +Y-6.999Z-6.912F1000 +Y7 +Y8Z-6.911 +G0Z2 + diff --git a/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal_Exact.gcode b/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal_Exact.gcode new file mode 100644 index 000000000..4f3a183a0 --- /dev/null +++ b/scripts/addons/cam/tests/test_data/parallel/Op_Parallel_Internal_Exact.gcode @@ -0,0 +1,466 @@ +(Created with grbl post processor 2024/03/23 07:35) +G21 +(G-code generated with BlenderCAM and NC library) +G17G90 +(Tool: D = 3.0 mm type END flutes 2) +S12000M03 +G00 Z2.0 + +G0X0Y0Z2 +X-68Y-7.999 +G1Z-3.1F500 +Y8F1000 +G0Z2 +X-66Y18 +G1Z-3.1F500 +Y-17.999F1000 +G0Z2 +X-64Y-23.999 +G1Z-3.1F500 +Y24F1000 +G0Z2 +X-62Y29 +G1Z-3.1F500 +Y-28.999F1000 +X-60Y-32.999 +Y33 +X-58Y36 +Y-35.999 +X-56Y-38.999 +Y39 +X-54Y42 +Y-41.999 +X-52Y-43.999 +Y44 +X-50Y46 +Y-45.999 +X-48Y-47.999 +Y48 +X-46Y50 +Y-49.999 +X-44Y-51.999 +Y52 +X-42Y54 +Y-53.999 +X-40Y-54.999 +Y55 +X-38Y56 +Y-55.999 +X-36Y-57.999 +Y58 +X-34Y59 +Y-58.999 +X-32Y-59.999 +Y60 +X-30Y61 +Y-60.999 +X-28Y-61.999 +Y62 +X-26Y63 +Y-62.999 +X-24Y-63.999 +Y64 +X-22 +Y-63.999 +X-20Y-64.999 +Y65 +X-18Y66 +Y-65.999 +X-16 +Y66 +X-14Y67 +Y-66.999 +X-12 +Y67 +X-10 +Y-66.999 +X-8Y-67.999 +Y68 +X-6 +Y-67.999 +X-4 +Y68 +X-2 +Y-67.999 +X0 +Y68 +X2 +Y-67.999 +X4 +Y68 +X6 +Y-67.999 +X8 +Y68 +X10Y67 +Y-66.999 +X12 +Y67 +X14 +Y-66.999 +X16Y-65.999 +Y66 +X18 +Y-65.999 +X20Y-64.999 +Y65 +X22Y64 +Y-63.999 +X24 +Y64 +X26Y63 +Y-62.999 +X28Y-61.999 +Y62 +X30Y61 +Y-60.999 +X32Y-59.999 +Y60 +X34Y59 +Y-58.999 +X36Y-57.999 +Y58 +X38Y56 +Y-55.999 +X40Y-54.999 +Y55 +X42Y54 +Y-53.999 +X44Y-51.999 +Y52 +X46Y50 +Y-49.999 +X48Y-47.999 +Y48 +X50Y46 +Y-45.999 +X52Y-43.999 +Y44 +X54Y42 +Y-41.999 +X56Y-38.999 +Y39 +X58Y36 +Y-35.999 +X60Y-32.999 +Y33 +X62Y29 +Y-28.999 +G0Z2 +X64Y-23.999 +G1Z-3.1F500 +Y24F1000 +G0Z2 +X66Y18 +G1Z-3.1F500 +Y-17.999F1000 +G0Z2 +X68Y-7.999 +G1Z-3.1F500 +Y8F1000 +G0Z2 +X-68 +G1Z-6.1F500 +Y-7.999F1000 +G0Z2 +X-66Y-17.999 +G1Z-6.1F500 +Y18F1000 +G0Z2 +X-64Y24 +G1Z-6.1F500 +Y-23.999F1000 +G0Z2 +X-62Y-28.999 +G1Z-6.1F500 +Y29F1000 +X-60Y33 +Y-32.999 +X-58Y-35.999 +Y36 +X-56Y39 +Y-38.999 +X-54Y-41.999 +Y42 +X-52Y44 +Y-43.999 +X-50Y-45.999 +Y46 +X-48Y48 +Y-47.999 +X-46Y-49.999 +Y50 +X-44Y52 +Y-51.999 +X-42Y-53.999 +Y54 +X-40Y55 +Y-54.999 +X-38Y-55.999 +Y56 +X-36Y58 +Y-57.999 +X-34Y-58.999 +Y59 +X-32Y60 +Y-59.999 +X-30Y-60.999 +Y61 +X-28Y62 +Y-61.999 +X-26Y-62.999 +Y63 +X-24Y64 +Y-63.999 +X-22 +Y64 +X-20Y65 +Y-64.999 +X-18Y-65.999 +Y66 +X-16 +Y-65.999 +X-14Y-66.999 +Y67 +X-12 +Y-66.999 +X-10 +Y67 +X-8Y68 +Y-67.999 +X-6 +Y68 +X-4 +Y-67.999 +X-2 +Y68 +X0 +Y-67.999 +X2 +Y68 +X4 +Y-67.999 +X6 +Y68 +X8 +Y-67.999 +X10Y-66.999 +Y67 +X12 +Y-66.999 +X14 +Y67 +X16Y66 +Y-65.999 +X18 +Y66 +X20Y65 +Y-64.999 +X22Y-63.999 +Y64 +X24 +Y-63.999 +X26Y-62.999 +Y63 +X28Y62 +Y-61.999 +X30Y-60.999 +Y61 +X32Y60 +Y-59.999 +X34Y-58.999 +Y59 +X36Y58 +Y-57.999 +X38Y-55.999 +Y56 +X40Y55 +Y-54.999 +X42Y-53.999 +Y54 +X44Y52 +Y-51.999 +X46Y-49.999 +Y50 +X48Y48 +Y-47.999 +X50Y-45.999 +Y46 +X52Y44 +Y-43.999 +X54Y-41.999 +Y42 +X56Y39 +Y-38.999 +X58Y-35.999 +Y36 +X60Y33 +Y-32.999 +X62Y-28.999 +Y29 +G0Z2 +X64Y24 +G1Z-6.1F500 +Y-23.999F1000 +G0Z2 +X66Y-17.999 +G1Z-6.1F500 +Y18F1000 +G0Z2 +X68Y8 +G1Z-6.1F500 +Y-7.999F1000 +G0Z2 +X-68 +G1Z-6.912F500 +Y8F1000 +G0Z2 +X-66Y18 +G1Z-6.912F500 +Y-17.999F1000 +G0Z2 +X-64Y-23.999 +G1Z-6.912F500 +Y24F1000 +G0Z2 +X-62Y29 +G1Z-6.912F500 +Y-28.999F1000 +X-60Y-32.999 +Y33 +X-58Y36 +Y-35.999 +X-56Y-38.999 +Y39 +X-54Y42 +Y-41.999 +X-52Y-43.999 +Y44 +X-50Y46 +Y-45.999 +X-48Y-47.999 +Y48 +X-46Y50 +Y-49.999 +X-44Y-51.999 +Y52 +X-42Y54 +Y-53.999 +X-40Y-54.999 +Y55 +X-38Y56 +Y-55.999 +X-36Y-57.999 +Y58 +X-34Y59 +Y-58.999 +X-32Y-59.999 +Y60 +X-30Y61 +Y-60.999 +X-28Y-61.999 +Y62 +X-26Y63 +Y-62.999 +X-24Y-63.999 +Y64 +X-22 +Y-63.999 +X-20Y-64.999 +Y65 +X-18Y66 +Y-65.999 +X-16 +Y66 +X-14Y67 +Y-66.999 +X-12 +Y67 +X-10 +Y-66.999 +X-8Y-67.999 +Y68 +X-6 +Y-67.999 +X-4 +Y68 +X-2 +Y-67.999 +X0 +Y68 +X2 +Y-67.999 +X4 +Y68 +X6 +Y-67.999 +X8 +Y68 +X10Y67 +Y-66.999 +X12 +Y67 +X14 +Y-66.999 +X16Y-65.999 +Y66 +X18 +Y-65.999 +X20Y-64.999 +Y65 +X22Y64 +Y-63.999 +X24 +Y64 +X26Y63 +Y-62.999 +X28Y-61.999 +Y62 +X30Y61 +Y-60.999 +X32Y-59.999 +Y60 +X34Y59 +Y-58.999 +X36Y-57.999 +Y58 +X38Y56 +Y-55.999 +X40Y-54.999 +Y55 +X42Y54 +Y-53.999 +X44Y-51.999 +Y52 +X46Y50 +Y-49.999 +X48Y-47.999 +Y48 +X50Y46 +Y-45.999 +X52Y-43.999 +Y44 +X54Y42 +Y-41.999 +X56Y-38.999 +Y39 +X58Y36 +Y-35.999 +X60Y-32.999 +Y33 +X62Y29 +Y-28.999 +G0Z2 +X64Y-23.999 +G1Z-6.912F500 +Y24F1000 +G0Z2 +X66Y18 +G1Z-6.912F500 +Y-17.999F1000 +G0Z2 +X68Y-7.999 +G1Z-6.912F500 +Y8F1000 +G0Z2 + diff --git a/scripts/addons/cam/tests/test_data/parallel/temp_cam/parallel_Op_Parallel_Internal_off.exr b/scripts/addons/cam/tests/test_data/parallel/temp_cam/parallel_Op_Parallel_Internal_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..3db5d6c10f92214dcc32df59850223f6e6e81345 GIT binary patch literal 228093 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̏CYd$dO%T|?}roR|2nTi)R$EKh*g|B8~&-2oG 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

!nH2BVx|J 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 z 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

mz7S-k1+G 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 literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/patterns/Cross.gcode b/scripts/addons/cam/tests/test_data/patterns/Cross.gcode new file mode 100644 index 000000000..a556d03ae --- /dev/null +++ b/scripts/addons/cam/tests/test_data/patterns/Cross.gcode @@ -0,0 +1,730 @@ +(Created with grbl post processor 2024/03/23 07:39) +G21 +(G-code generated with BlenderCAM and NC library) +G17G90 +(Tool: D = 3.0 mm type END flutes 2) +S12000M03 +G00 Z10.0 + +G0X0Y0Z10 +X-66Y-18 +G1Z-5.1F500 +Y17.999F1000 +G0Z10 +X-63Y-26.5 +G1Z-5.1F500 +Y26.499F1000 +G0Z10 +X-60Y-33 +G1Z-5.1F500 +Y32.999F1000 +G0Z10 +X-18Y66 +G1Z-5.1F500 +X17.999F1000 +G0Z10 +X-26.5Y63 +G1Z-5.1F500 +X26.499F1000 +G0Z10 +X-33Y60 +G1Z-5.1F500 +X32.999F1000 +G0Z10 +X-37.5Y57 +G1Z-5.1F500 +X37.499F1000 +G0Z10 +X-42Y54 +G1Z-5.1F500 +X41.999F1000 +G0Z10 +X-45.5Y51 +G1Z-5.1F500 +X45.499F1000 +G0Z10 +X-48.5Y48 +G1Z-5.1F500 +X48.499F1000 +G0Z10 +X-51.5Y45 +G1Z-5.1F500 +X51.499F1000 +G0Z10 +X-54Y42 +G1Z-5.1F500 +X53.999F1000 +G0Z10 +X-56Y39 +G1Z-5.1F500 +X55.999F1000 +G0Z10 +X-58Y36 +G1Z-5.1F500 +X57.999F1000 +G0Z10 +X-60Y33 +G1Z-5.1F500 +X59.999F1000 +G0Z10 +X-61.5Y30 +G1Z-5.1F500 +X61.499F1000 +G0Z10 +X-62.5Y27 +G1Z-5.1F500 +X62.499F1000 +G0Z10 +X-64Y24 +G1Z-5.1F500 +X63.999F1000 +G0Z10 +X-65Y21 +G1Z-5.1F500 +X64.999F1000 +G0Z10 +X-66Y18 +G1Z-5.1F500 +X65.999F1000 +G0Z10 +X-66.5Y15 +G1Z-5.1F500 +X66.499F1000 +G0Z10 +X-67Y12 +G1Z-5.1F500 +X66.999F1000 +G0Z10 +X-57Y-37.5 +G1Z-5.1F500 +Y37.499F1000 +G0Z10 +X-67.5Y9 +G1Z-5.1F500 +X67.499F1000 +G0Z10 +X-54Y-42 +G1Z-5.1F500 +Y41.999F1000 +G0Z10 +X-68Y6 +G1Z-5.1F500 +X67.999F1000 +G0Z10 +X-51Y-45.5 +G1Z-5.1F500 +Y45.499F1000 +G0Z10 +X-68Y3 +G1Z-5.1F500 +X67.999F1000 +G0Z10 +X-48Y-48.5 +G1Z-5.1F500 +Y48.499F1000 +G0Z10 +X-68Y0 +G1Z-5.1F500 +X68.499F1000 +G0Z10 +X-45Y-51.5 +G1Z-5.1F500 +Y51.499F1000 +G0Z10 +X-68Y-2.999 +G1Z-5.1F500 +X67.999F1000 +G0Z10 +X-42Y-54 +G1Z-5.1F500 +Y53.999F1000 +G0Z10 +X-68Y-5.999 +G1Z-5.1F500 +X67.999F1000 +G0Z10 +X-39Y-56 +G1Z-5.1F500 +Y55.999F1000 +G0Z10 +X-67.5Y-8.999 +G1Z-5.1F500 +X67.499F1000 +G0Z10 +X-36Y-58 +G1Z-5.1F500 +Y57.999F1000 +G0Z10 +X-67Y-11.999 +G1Z-5.1F500 +X66.999F1000 +G0Z10 +X-33Y-60 +G1Z-5.1F500 +Y59.999F1000 +G0Z10 +X-66.5Y-14.999 +G1Z-5.1F500 +X66.499F1000 +G0Z10 +X-30Y-61.5 +G1Z-5.1F500 +Y61.499F1000 +G0Z10 +X-66Y-17.999 +G1Z-5.1F500 +X65.999F1000 +G0Z10 +X-27Y-62.5 +G1Z-5.1F500 +Y62.499F1000 +G0Z10 +X-65Y-20.999 +G1Z-5.1F500 +X64.999F1000 +G0Z10 +X-24Y-64 +G1Z-5.1F500 +Y63.999F1000 +G0Z10 +X-64Y-23.999 +G1Z-5.1F500 +X63.999F1000 +G0Z10 +X-21Y-65 +G1Z-5.1F500 +Y64.999F1000 +G0Z10 +X-62.5Y-26.999 +G1Z-5.1F500 +X62.499F1000 +G0Z10 +X-18Y-66 +G1Z-5.1F500 +Y65.999F1000 +G0Z10 +X-61.5Y-29.999 +G1Z-5.1F500 +X61.499F1000 +G0Z10 +X-15Y-66.5 +G1Z-5.1F500 +Y66.499F1000 +G0Z10 +X-60Y-32.999 +G1Z-5.1F500 +X59.999F1000 +G0Z10 +X-12Y-67 +G1Z-5.1F500 +Y66.999F1000 +G0Z10 +X-58Y-35.999 +G1Z-5.1F500 +X57.999F1000 +G0Z10 +X-9Y-67.5 +G1Z-5.1F500 +Y67.499F1000 +G0Z10 +X-56Y-38.999 +G1Z-5.1F500 +X55.999F1000 +G0Z10 +X-6Y-68 +G1Z-5.1F500 +Y67.999F1000 +G0Z10 +X-54Y-41.999 +G1Z-5.1F500 +X53.999F1000 +G0Z10 +X-3Y-68 +G1Z-5.1F500 +Y67.999F1000 +G0Z10 +X-51.5Y-44.999 +G1Z-5.1F500 +X51.499F1000 +G0Z10 +X0Y-68 +G1Z-5.1F500 +Y68.499F1000 +G0Z10 +X-48.5Y-47.999 +G1Z-5.1F500 +X48.499F1000 +G0Z10 +X3Y-68 +G1Z-5.1F500 +Y67.999F1000 +G0Z10 +X-45.5Y-50.999 +G1Z-5.1F500 +X45.499F1000 +G0Z10 +X6Y-68 +G1Z-5.1F500 +Y67.999F1000 +G0Z10 +X-42Y-53.999 +G1Z-5.1F500 +X41.999F1000 +G0Z10 +X9Y-67.5 +G1Z-5.1F500 +Y67.499F1000 +G0Z10 +X-37.5Y-56.999 +G1Z-5.1F500 +X37.499F1000 +G0Z10 +X12Y-67 +G1Z-5.1F500 +Y66.999F1000 +G0Z10 +X15Y-66.5 +G1Z-5.1F500 +Y66.499F1000 +G0Z10 +X18Y-66 +G1Z-5.1F500 +Y65.999F1000 +G0Z10 +X21Y-65 +G1Z-5.1F500 +Y64.999F1000 +G0Z10 +X24Y-64 +G1Z-5.1F500 +Y63.999F1000 +G0Z10 +X27Y-62.5 +G1Z-5.1F500 +Y62.499F1000 +G0Z10 +X30Y-61.5 +G1Z-5.1F500 +Y61.499F1000 +G0Z10 +X33Y-60 +G1Z-5.1F500 +Y59.999F1000 +G0Z10 +X36Y-58 +G1Z-5.1F500 +Y57.999F1000 +G0Z10 +X39Y-56 +G1Z-5.1F500 +Y55.999F1000 +G0Z10 +X42Y-54 +G1Z-5.1F500 +Y53.999F1000 +G0Z10 +X45Y-51.5 +G1Z-5.1F500 +Y51.499F1000 +G0Z10 +X48Y-48.5 +G1Z-5.1F500 +Y48.499F1000 +G0Z10 +X51Y-45.5 +G1Z-5.1F500 +Y45.499F1000 +G0Z10 +X54Y-42 +G1Z-5.1F500 +Y41.999F1000 +G0Z10 +X57Y-37.5 +G1Z-5.1F500 +Y37.499F1000 +G0Z10 +X60Y-33 +G1Z-5.1F500 +Y32.999F1000 +G0Z10 +X63Y-26.5 +G1Z-5.1F500 +Y26.499F1000 +G0Z10 +X66Y-18 +G1Z-5.1F500 +Y17.999F1000 +G0Z10 +X-33Y-59.999 +G1Z-5.1F500 +X32.999F1000 +G0Z10 +X-26.5Y-63 +G1Z-5.1F500 +X26.499F1000 +G0Z10 +X-18Y-66 +G1Z-5.1F500 +X17.999F1000 +G0Z10 +X-66Y-18 +G1Z-6.912F500 +Y17.999F1000 +G0Z10 +X-63Y-26.5 +G1Z-6.912F500 +Y26.499F1000 +G0Z10 +X-60Y-33 +G1Z-6.912F500 +Y32.999F1000 +G0Z10 +X-18Y66 +G1Z-6.912F500 +X17.999F1000 +G0Z10 +X-26.5Y63 +G1Z-6.912F500 +X26.499F1000 +G0Z10 +X-33Y60 +G1Z-6.912F500 +X32.999F1000 +G0Z10 +X-37.5Y57 +G1Z-6.912F500 +X37.499F1000 +G0Z10 +X-42Y54 +G1Z-6.912F500 +X41.999F1000 +G0Z10 +X-45.5Y51 +G1Z-6.912F500 +X45.499F1000 +G0Z10 +X-48.5Y48 +G1Z-6.912F500 +X48.499F1000 +G0Z10 +X-51.5Y45 +G1Z-6.912F500 +X51.499F1000 +G0Z10 +X-54Y42 +G1Z-6.912F500 +X53.999F1000 +G0Z10 +X-56Y39 +G1Z-6.912F500 +X55.999F1000 +G0Z10 +X-58Y36 +G1Z-6.912F500 +X57.999F1000 +G0Z10 +X-60Y33 +G1Z-6.912F500 +X59.999F1000 +G0Z10 +X-61.5Y30 +G1Z-6.912F500 +X61.499F1000 +G0Z10 +X-62.5Y27 +G1Z-6.912F500 +X62.499F1000 +G0Z10 +X-64Y24 +G1Z-6.912F500 +X63.999F1000 +G0Z10 +X-65Y21 +G1Z-6.912F500 +X64.999F1000 +G0Z10 +X-66Y18 +G1Z-6.912F500 +X65.999F1000 +G0Z10 +X-66.5Y15 +G1Z-6.912F500 +X66.499F1000 +G0Z10 +X-67Y12 +G1Z-6.912F500 +X66.999F1000 +G0Z10 +X-57Y-37.5 +G1Z-6.912F500 +Y37.499F1000 +G0Z10 +X-67.5Y9 +G1Z-6.912F500 +X67.499F1000 +G0Z10 +X-54Y-42 +G1Z-6.912F500 +Y41.999F1000 +G0Z10 +X-68Y6 +G1Z-6.912F500 +X67.999F1000 +G0Z10 +X-51Y-45.5 +G1Z-6.912F500 +Y45.499F1000 +G0Z10 +X-68Y3 +G1Z-6.912F500 +X67.999F1000 +G0Z10 +X-48Y-48.5 +G1Z-6.912F500 +Y48.499F1000 +G0Z10 +X-68Y0 +G1Z-6.912F500 +X68.499F1000 +G0Z10 +X-45Y-51.5 +G1Z-6.912F500 +Y51.499F1000 +G0Z10 +X-68Y-2.999 +G1Z-6.912F500 +X67.999F1000 +G0Z10 +X-42Y-54 +G1Z-6.912F500 +Y53.999F1000 +G0Z10 +X-68Y-5.999 +G1Z-6.912F500 +X67.999F1000 +G0Z10 +X-39Y-56 +G1Z-6.912F500 +Y55.999F1000 +G0Z10 +X-67.5Y-8.999 +G1Z-6.912F500 +X67.499F1000 +G0Z10 +X-36Y-58 +G1Z-6.912F500 +Y57.999F1000 +G0Z10 +X-67Y-11.999 +G1Z-6.912F500 +X66.999F1000 +G0Z10 +X-33Y-60 +G1Z-6.912F500 +Y59.999F1000 +G0Z10 +X-66.5Y-14.999 +G1Z-6.912F500 +X66.499F1000 +G0Z10 +X-30Y-61.5 +G1Z-6.912F500 +Y61.499F1000 +G0Z10 +X-66Y-17.999 +G1Z-6.912F500 +X65.999F1000 +G0Z10 +X-27Y-62.5 +G1Z-6.912F500 +Y62.499F1000 +G0Z10 +X-65Y-20.999 +G1Z-6.912F500 +X64.999F1000 +G0Z10 +X-24Y-64 +G1Z-6.912F500 +Y63.999F1000 +G0Z10 +X-64Y-23.999 +G1Z-6.912F500 +X63.999F1000 +G0Z10 +X-21Y-65 +G1Z-6.912F500 +Y64.999F1000 +G0Z10 +X-62.5Y-26.999 +G1Z-6.912F500 +X62.499F1000 +G0Z10 +X-18Y-66 +G1Z-6.912F500 +Y65.999F1000 +G0Z10 +X-61.5Y-29.999 +G1Z-6.912F500 +X61.499F1000 +G0Z10 +X-15Y-66.5 +G1Z-6.912F500 +Y66.499F1000 +G0Z10 +X-60Y-32.999 +G1Z-6.912F500 +X59.999F1000 +G0Z10 +X-12Y-67 +G1Z-6.912F500 +Y66.999F1000 +G0Z10 +X-58Y-35.999 +G1Z-6.912F500 +X57.999F1000 +G0Z10 +X-9Y-67.5 +G1Z-6.912F500 +Y67.499F1000 +G0Z10 +X-56Y-38.999 +G1Z-6.912F500 +X55.999F1000 +G0Z10 +X-6Y-68 +G1Z-6.912F500 +Y67.999F1000 +G0Z10 +X-54Y-41.999 +G1Z-6.912F500 +X53.999F1000 +G0Z10 +X-3Y-68 +G1Z-6.912F500 +Y67.999F1000 +G0Z10 +X-51.5Y-44.999 +G1Z-6.912F500 +X51.499F1000 +G0Z10 +X0Y-68 +G1Z-6.912F500 +Y68.499F1000 +G0Z10 +X-48.5Y-47.999 +G1Z-6.912F500 +X48.499F1000 +G0Z10 +X3Y-68 +G1Z-6.912F500 +Y67.999F1000 +G0Z10 +X-45.5Y-50.999 +G1Z-6.912F500 +X45.499F1000 +G0Z10 +X6Y-68 +G1Z-6.912F500 +Y67.999F1000 +G0Z10 +X-42Y-53.999 +G1Z-6.912F500 +X41.999F1000 +G0Z10 +X9Y-67.5 +G1Z-6.912F500 +Y67.499F1000 +G0Z10 +X-37.5Y-56.999 +G1Z-6.912F500 +X37.499F1000 +G0Z10 +X12Y-67 +G1Z-6.912F500 +Y66.999F1000 +G0Z10 +X15Y-66.5 +G1Z-6.912F500 +Y66.499F1000 +G0Z10 +X18Y-66 +G1Z-6.912F500 +Y65.999F1000 +G0Z10 +X21Y-65 +G1Z-6.912F500 +Y64.999F1000 +G0Z10 +X24Y-64 +G1Z-6.912F500 +Y63.999F1000 +G0Z10 +X27Y-62.5 +G1Z-6.912F500 +Y62.499F1000 +G0Z10 +X30Y-61.5 +G1Z-6.912F500 +Y61.499F1000 +G0Z10 +X33Y-60 +G1Z-6.912F500 +Y59.999F1000 +G0Z10 +X36Y-58 +G1Z-6.912F500 +Y57.999F1000 +G0Z10 +X39Y-56 +G1Z-6.912F500 +Y55.999F1000 +G0Z10 +X42Y-54 +G1Z-6.912F500 +Y53.999F1000 +G0Z10 +X45Y-51.5 +G1Z-6.912F500 +Y51.499F1000 +G0Z10 +X48Y-48.5 +G1Z-6.912F500 +Y48.499F1000 +G0Z10 +X51Y-45.5 +G1Z-6.912F500 +Y45.499F1000 +G0Z10 +X54Y-42 +G1Z-6.912F500 +Y41.999F1000 +G0Z10 +X57Y-37.5 +G1Z-6.912F500 +Y37.499F1000 +G0Z10 +X60Y-33 +G1Z-6.912F500 +Y32.999F1000 +G0Z10 +X63Y-26.5 +G1Z-6.912F500 +Y26.499F1000 +G0Z10 +X66Y-18 +G1Z-6.912F500 +Y17.999F1000 +G0Z10 +X-33Y-59.999 +G1Z-6.912F500 +X32.999F1000 +G0Z10 +X-26.5Y-63 +G1Z-6.912F500 +X26.499F1000 +G0Z10 +X-18Y-66 +G1Z-6.912F500 +X17.999F1000 +G0Z10 + diff --git a/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Circles_off.exr b/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Circles_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..3db5d6c10f92214dcc32df59850223f6e6e81345 GIT binary patch literal 228093 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̏CYd$dO%T|?}roR|2nTi)R$EKh*g|B8~&-2oG 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

!nH2BVx|J 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 z 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

mz7S-k1+G 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 literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Parallel_off.exr b/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Parallel_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..3db5d6c10f92214dcc32df59850223f6e6e81345 GIT binary patch literal 228093 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̏CYd$dO%T|?}roR|2nTi)R$EKh*g|B8~&-2oG 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

!nH2BVx|J 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 z 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

mz7S-k1+G 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 literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Spiral_off.exr b/scripts/addons/cam/tests/test_data/patterns/temp_cam/patterns_Spiral_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..3db5d6c10f92214dcc32df59850223f6e6e81345 GIT binary patch literal 228093 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̏CYd$dO%T|?}roR|2nTi)R$EKh*g|B8~&-2oG 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

!nH2BVx|J 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 z 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

mz7S-k1+G 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 literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_off.exr b/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..3eba1ac98c926c20feaf12f9653b496bfaf6e838 GIT binary patch literal 446445 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%0AaG*4{cYs4q(ndS=xKsw@8%vmhB5?S}iXk}rS$ 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 z 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

<6s1> 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͏ZbEZ0tbrd=&(@LRMp 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

!W5<%r+?NO@hAWr3k

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 z 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°+*Yb1WERhBwI_w6rf}QQ z&1Y8j0=u`&od^afwUP}LKYhraD41e=F`PeDpkw~wQ&`((UxiahjAb8>Bh(nEg)D`( z+3Uo9)ypLeSB4F81RZ-{shuDKE|ktxuDX+&&OJ_*m2a-z3g)W0+CR_(23~CoKUn#C z8VGs}F@u{64&NM)8_{W1H6;=j4cRQYQ`(JZ(T!m-KUbT#U#@7HDv=5t&epWm-i*D6 zt-iU38!oh6$<|098$e>yWwi2k+)Y9+LaFqHU2Cxx%i_$dkVI;$w5QX4jJ%3_fK%8~ zkY@A7o`L%%7jJdjaB_u|P-JEGfYWl#r=!`popjhpQD<_xl&keG=R3^`l1( zMKNxA>VL@O?jK;C^mD&Y5EAEsobphv<(_l_Iw)Uu{S>cPD*c#Nt)BL=RKBqN=dasV z3gyb?XErobEDLZ7C-;X_HYu7#=z&2{Ow*xObd&__YvpLMq(esCEuVd;K!e*TRg^!p zX}TH%okWrGKOP7SuqFt>x@9VxEK^q2b!3+!d0TOUME>ipG9w^`{7=)-zLf?|pZ%MU zGbBQ^XB!WZucYst+MFHC@<8^&xtT(plRVD@kfg~lE*=>61g0viWhU+KINOE z!e>`@1TIqdOJ=iJYGC+MJ_k)>_jbRe!UrEBEqm{$WqQT^{G%KUtrSTUGRJx2agTUk z-MQUWzaFn{`4D|$4Cm!&K9>V*Mdy}xQn)Ps*fy=(FdoFW+BDKJOP72}kWaBt)DL*> z=(MB+%EMDnl7jHRJ_ga}2%mpF=HVFmiA?$Za7TV7BrEId{O7(Gz~(AdN##-Ro2iV| z>No4`4ZaIQ?pILPj%95_55U;gw;n& zl+S*Xul@Lx)U&)5)dCUWVwlawoHI<>s6-l{Y^V6{rP$Vs41f{`!SW5f&f{V1LZcVj z2tQMZi$Wv!!gxuIOne6-c&2^{IoZ+f*4bAH6S0lAP5C}>1hl&UQTBVg93njYJE;Qx z*WHu4P5Orc`1C~MFGN$HgS-CAK>A`6%ATx0j?I|fn^~r)vVm;1!nti`3{_~ybqZ_G zj^o~u@~LW4dq5o5*XNViZ?qUv?a%S=Ni^iUG4I}LZZMA5%fdzf>V%)krhJ~lrJE03 zf6aq~^UmR+c3Hvq;9f_HD6O>)blioNOg8NnOU=rM(%SC3Cck6I&U^{ti76}D&UNbu z=oZB!nPrEuW7;Bs-#e3SvIEU*G+HhX@mR<#WDyhhmsv7gV86rNYXWIw>9>493-&b3K@ zDGyFVO#2O>FW)-b%9jgNW~eoak-9?>ye-Sbq<{OVIe>m@YNl(_2b=*Bs$n!T*CKDDe>EQ7 zXbBf8A>P%?)=e-cME^Q6Gcnr?B@?AMjm`}H7e*V)b&YVON5K^+i;K35z7aQk#-%N4gP7UQFR_004?e1p^| zM1uHX*)nY5_vo5_%C(s*nAB{`rnNbh;92Cfk?tb#K)aJg_fC99-Gr6%w9-4724B5+ z%{I)pSfSP^sg(Mzd5o@&F?@>n$ChpwH#sU3pIIopa{D|ha6t4c3!aJVQ!b@>54yyI z0F=4QN=K1#Gme0l8mjArKbY^O< zx#zvj-V;JG<7TT7U7$Id zP8JyFVu?nhS}tCmq1L0`<7jFctHoxUnUL2CC6^0luzIaWzfPk>l0O!$G!7JP+-__hJd(pNLGnT=m@vQobLzlT z&HufQyLAeX{^^&G$?_c)6rT5T>t~{LM$WTdVDdw0`OH->YwU;PZjx|UE1@sN)J+D8zUZONuBv48nRf$}9ZZ-5qPqNc3!k^A5v^?X;|;wF07ywSDD(&O{L z;mTj7k~E8bKb4CHTGu&?|1Dk35dS)6Zz`*L5BV_i*++zgJz=i(L2r5ZhD18R@iNlH*c^%h(1==FqSWfsN}-dlXV+5 z`=N}LdEtp_l;_(K$})8sm5iXd$0R9UI@zDj-)x94mgs1W>;jQIplabicR!O-n^pj~ zWd6P8u#3ELEB3RiQ|VHe>Km$A=bBEmN9F3Hx|YFLqWQi2n9hnq0e4!TTSolO zw#lwQ@#tC5J5za9f6rfU&U*`>O)pNr=JKq}jcw~wcUSfE6b|JAWdOtD9&p}-r7AE{ z%yw(B=Kel?Mtrg2tt&cT;HJE$^GQ~~zFkfJ=v%<}XF92ks7j$E+81)yG3E1nJ9%)> zinzPv*`;SZ^&_(FCrgyD)S!dSd!VM0h4e30KB}hpPFHjeP~JEY}2gWbm8 zt{6!k`BEpwY}3)@eKaK!Qe@`+0(4?%IA$FT8vQ(9Wu;cXFq8Ln&Y`yRhEui#It4k% ziz|=btp_4`FFPQTe}b=5o5XHrp_xbl7_T6&C z5|#L+(zsikyo6qYc7&3je3No!Fps(kA00{6hK8A{C$^SVwA(RG5G!!s*j2|FN4%lEcKySF10Y3(DO6VT2_h_l-bP31e5nYYPRaL%(`HpYU8Chl_mi{8e*sy!=>lSory3o?^O| zO*Ae?qeq+fO+!I_9IwzjD%wggh0kAmkQR>D7A=%cOt%`9K#N&*pPt1?tbb6b1{<=k_)%-ry_R0JSAJVUd9?Rf-y+_X1-wT3 z6tHp|yP>GrpEq@CiRiBldQ#+eoqg_td;6-!uiXnW=+A!_*=+&m3L(IoHJmr*Z@zeBWgFoT(kzH>q#j`59E9AI{9?gtI_^E3#H%sJWKInai|aCt8E`x4^{gsKd_)yPr{U}d5E%+z z&ZmSH#X5;0k?0k9=D_1J8D;Y!X6$(q@YK(}jUivcaPRBRs0gM`i{bT;DO}EcXn`9- zeF~QWyntRh0-X58Tk6pBBjq@G1m$*QOClgul%3me1_u4@_t`Mr6QVVR7#dx2@BLJj0w zN%lF-1IRB>$ZKC~{WvA%=*K}I;0l(_5=tv?&4_Tj7iB4KnuE)H8E1Z-bsPUH_iC=z zborw)c@H!Q83fh7i{+%a7rYB~%CwRUu*!4_eOFMgmjmSJl0r+80~AlV=?R4tZBxja&dYaVgk+b0$0?xa$OcSB3Vg z>hU~dp3+S4`;t2R3U4!`l^U_~Mfw(;F$u*JNDtT+& z5A;mye*Zb8yR@5wk&}hnV`S8$?o1^FbFL1}r5N{EC3p0GI&2_te4DF8WKQy+LUP?~=ONqhFTv-&>3DuT{B{QWvzP54N?uROE9bAbT# zj!rETRebzJ&OM#l(4Id!9Jl-vggMFl3BsNnFVOeA?8^A}D&4M1S5FhGk}p)Euo|oy zD-f>1v5MKp$M;|(wQ`oq8P@QOExA0{=8Ji#y{gsb(ItGg8^mG`jm3$2Lwq8`H%w3D zXb~~ne4t)9$Wy7X7a4dwlIO;Y14P=K-W{OpimC)qY&H60$QTG87B{IBwax~NFtsrK z*=3SM%o(XQ8z2@zD;4pB);t`TVgmj>>j`@%H9{-(FrM~>R5&oG6hX@uPQ!QoQ{Zg| zFu)|$WSg?#86UR?f0ViFSjaV4(C+~EG?~yw>!6b^>v0n`=E-rnX)r0(D=F`Pv=tCr z3zken%Qi@=>+`F{exqne!}%tA)A>qx{~7eBS%U)3!+D-nH00$+(SX$hV!YJ&-v*)tp(5m z;!3`T7wxA*o%;1Y3EsQJRz%>tpO2kqnmeFnw)w>li$;rTU#RbtKnQ*<;@$sE_M2?; zpI^ec&)uDfY(9aeiiCcvx2TrPO!4ZATUl9AO(m9`x0LTQesLyhF#O$k6aK)3n#lTI zsZhfU;DQ7c+Yazo3quPt!=c52C4!~=BQJX|#(K@lM;SlKVlj*pv;$|mWiIIuj4YdA z_3=eG?Ii^JexkmF_Z^Sw#z-sY9&qZ)qW)%nR^^qMIi?dpCgb)hwgbJYwOkwu=cmB} zqXduOa?D}OG(wb;BoMAV`OV8AlLgi}?ZR6k#HjYk_C?;^`Gabjg}amA$AZ1ytk)W4 zs&x2vkx=(UcRyBW4yYO+t3-ruEgD6%xNFkz}`I0*L|IpQD<+kx`lu6tP1d=J&G zz*llU)X8SaaK4(yy*TTIxBGeEOa6)Sk3$H1_u1ktb}z_HCtlNE<0$&>GZAv53-}H4 zW|12Li5$NzE~BfJ9zRq~+ab2T2cD{S90ajI_uRRuONg`_M|kB~$(=CA0;BuAPkJ6J zxbYr_f%$elzT#jyTc~k8m(778yi%7zo)VS3LpDT|l?x8r(XdvnT-r9BFItql+2}LqSv&K6)qbQBLk#0SmcKPy} z0oKALp7+&vekzDn;qX73aWN9Al&3&n`-uS2Debaf~s& z$72VY*;BSI9_eHu2ATJ|&zt*fgas&TsjEd8W!*W{Z=dOXE-&|1zZe&Uj@H5rW+gSj zt7r$AxF&^RV-%*>BMV+WZqsjq64t{zy-awp+eCdN`ct&Tt34sC_?<<2UY8&>I(Qt3 z?>7qXuPF@AkLTDx%~{5iYi<#+GB=g%7PyYprJst<;y6@3Bm+dmi!)345!j1#Bt$Z` zlz2mZr|bC&^39LKdK~g*>@_sVj4p%$aZLh05z!BDz|k(GMntP{?{CF!yyV8^xxMeY(kaf5oiMAo5me#58GT(slxW#RIyIr~$N31nd&$u^ zZV3}xEgL#}g$b@-%yRJCd)1MSoj{6sjEhsYU`9XXeYdY(Yf!j@J z^- z+ff>oywrf$TWTer=I(o+TE9C(B=Sr`8>|QB%$eV?J9HK#gkpu;hRQ`OW8kgM*d;2evxNJd! zR7jOXZi}`SIgEVMK)thzi_N_0lZMl&@1y>BK`hGy>V0akI9JO~>yDd#t@KEc$PaqI zBvuuWhRE^d58?*cZ5-s2sdFeuFtHu%mCQbR{#!j>c0*717uc(eYV;g?(F4Z7n-d0! z){W_63qOAK45dhVT!SyzTm0`v6MX9QOiBw7&;Rk|f6{{RZXMp9LeA;ky|=Lbg4)07 zNx_pmA5T_g{_~bdG``>WI~+EH4Vm%j(IIHB;dgDmkFzJK$_WfrXJP@^L)}_yYhm4oYQ2BFG*#u;#_@QM|DuCFWHgA33CQoNtRX# z92M6Kb$gdVFiz+`8HQkd)t5;vUnjD*&pCHqZBoXMozc!hj8?=AV3EXGh?S7Kor}dm z6y>dii%BQ9F0c)HA4R30aI2|nbo2}S@R;+r`$Y!xy8CVh^TFl3IWk)?QH4c^emV@e zS(7A>W<;{_p(nk3M9xzr_=02#S#FbOoNo%N=f{3`ikIIWamadOwB0H8NW|GDIl5)` zBFTIAEBqB>7<~E~E=24YR>ECZBM+mCMdH7fwefrfJMt@8vQx4Xd->%rss^>T?0H&pFb6P5;_?b!N=Cym{S`IuB3S**c!vJi!zQXDvc z{#<#l!|eBkQrss#wT+ED@alyOLEM1Ch$(-wsS^UiLxI`-IQuPAL%rb6qfrgd!O7cb-7@=&2t25A&>Ur;Vb^dJrv%DSxYJcxT&6mGdlyS@iTpm89N#HR*)v^Uza=zZzfk35ITt0z`3* zfdAt?;f+L*(>RUF&wB?jboE)yuKi-^Y!9SuX&fZ`DJHQiu{bO@nG4-nkl=zV?z92* zdVx}^iGGhud4Ddy-%NICwHtBx>)a3j&Ue|3#l#MP>rw6gZSoia9Q%57&F-STzQZ?bjgu+UZlv`e<#OF-7fvho4u%5$ z!tTf61J6fB9=F-OY(F0bibrXjbWgVNpcPN87T5N-_sB{x-`=eraiF9MDDTGtuP z$DNtA8cq;>vrZx|J6a_66lhSZ(JYW&?m3erp_auNfY%Vb%ty?^y}RR13^r(XunZ+L zO@(m2QcraqqG?ZJXFets7q;`^cF;;bZC;0k-y#u2VN}bOZm1OSxQQ*1L zsz(9tCk|U0?CespWYeR<@SGnEG(xd+x=+Nj5G}==0^8nG9QA;*Jd*~j2jk!$6=s|k z)(%jKr*+SSW3?sne&mI_(4*W|XO}`QU=126ZK0m5mzASS74?9N-N;=nLt&thkQKG0 z3ZhvLee`n{sL(9Znh?x;ta+R%{&*WaCH#k-{eS)2DS1o@97gsJG6}za1&}EK95(ik zi+_3(RQ9RgVK_z{#dhW4b%cbpJ$8L2D7m1TPELn@elZmzTpSt#&&vcLyBt&J8d*<2 zW1j3Q_&hz|xwF+ClQ!7p0eR96pM^;g-`U{}MVOp={D6z3v@sMZiP zRiA?4$>o%32|O-|h>N3QzydUnwz8l8DHb&&f^PocfqjI`7eV`P zf5Uqq_bE(4Z3CF3R71pnJ&;qV@}Nz_iOdoD zF{T&^e@2=54mEYJSj?Q*E}>+D1`evU9ispUv@1k~@V}oTvB%>m9&jt=dd*M&kv~!d z{;6{dbj%)Hf)pwT2eXxdVOcISe%E+c)viJy(c1{;`=9~pTQK~K-b1m@HirRJuS(D_ zA?v9c1c^Sc5@)D^PcOUYwmAO4oCpYV3HgZlW3`<6Qzhsb#^2#Gbe;sKVF76P{+&xXwXKHX1=@Q^h109)uk5-A6xQee-vF%YekQWy6>Zr7Ut$qQ;SQ|>Ng ziV|}lj@$GZTrE+50?dc!r{s^22+3QURyBxwWOujgkHvg)TC>)bOgcxygI<8)_3ntt z`aT#_uKg&BQ<5qDfv?41|Nlf}3YwG+rn=27exWSG`OnK(I!{VMA4%LX1)U@Z8~p>w z{bw&NYXtOB5z#O5Gw>cihk!sa+qs?eIK?$OQB9sIo_@Xe1A}G<1cX(C#r}1N6`S9X z0V|R=xbXE%A&g%iSNB?eBo%nn7qYMlQG{qg%Mp+2p67dFh6H}J@N>1|mub?IC=CP? z@r%_~sKBd>Z3JJ3opjZX2lBvqrrz$_CtY}PdWeILMJ=bb|KDImI!}Og3^6*#{y(~u zzMt;%eHi;)2`q7a2e!+4d^_wJuW^I4(>O!)oedb>T2@cBdAY~R_wa&u`a8-G{E_m2 ze8=>-s`ilFDLSfm7B2%Ee%6@r*gc0&UHQ~e7(=P{%~xz@Q9weeN0h1rZRr9hBNVgN z6vWi6G$`zB5s>4FoFXP0d*(XKu7QjbX!GY+M^T6r6mjRTi#G%AzkK~763|M(Q}xhG zvPYaZlfgz$rpW&pUPhw-Ms(h3I=^Af1Vs~<&q2Vg4wUpJEjz0_EGu3qGd>E>;Zcrk zvNCuNLFH0xmbc9~mt1!+8ah<$a*K%5lK0i_`smgu2FtMjUt~)Z% z^{2^4b*OjThfkU{zLD|CO&Xbj24%9D&IVbgWvO<-atu)hY;lFe=5{0n@vOR2)R7rv zc56!+X>{7N-I{c1mJ7SqWVD8DFrDYInN+3@L`6xo>KZI5>7=&nk8&XlT61mjITRl@ zR~!;vGRLrJvA*fj92i!d=p0oUdCjlH8NwGqqhTkt8}I^LG@nW-&VHrR;{K8tLa_01 zarf(fQ;(mKpS8>D>J~Uwtj^af3AHucJ7)@9+3yR@hn(8)evvi(g%_ogooy2`DtLE| zDRkro_}Ei4|=DJ(4(cu>|b;kaZKlMwf=*Q{gV;TBKXf)HK ztSGahm^52PqzDGo!uj?yChw8cF%v)948Mqt*4-L17iZ^B_wG87{d6RIz%E`!fA1@y zgp*!sne>lW%5}tvikUfEIx7f;)syY4G*9%1Gy^qV|A#mnLf@3qvd^LyecL&Ki@zeu$<` zj?i7q88gdq?9kENHJw1LDaK9hSNj6pB#G^w^$cW$UDeKAMB9wDCo;)I%)IwsX>HNZ z+zZmR$kz;gyh)Z=^}NeFHoDu3ILf;#ia4F$!2VcLYw^ni;XK9DZ?|1(63bK!7#bSfh2{#mOQXXsemu^wSO+H z&$lu1r8N+{+v~$Po2aSve3&%3c4{vo_11z#U1Ov8R8RRyNQA^@>VAtV*}d)4Bu_m(&!m&B zdp3bqOGN5f+_DF!ibB5<$*iWkydQiiWl&pt>zu1xq5F;5vDzFnzn+cu0;29_xzy07 zcuE|V3Y06dxF3E}%2NZ5&B&C6s`C^JDl?9CNwo261gH?^YxPv&;Z&KRd}C$ul&bWS z+{3I&AWBTJtY=R=&et$%W*qvHeAqOxYJ6u$TFF0ef!^zRR_*%o{AQ?->K25^bAh>e4KKv=?Ii`b)zi}>ZK#9wGB)&hM@tE3L3}vd}&*}uOpevri!bf-w!lhpuPf4EX&ok{M zVBZby#jucW0V8;Lq2Lh)JH>-qe^ZBoqLlL9-pf-eQ!hw*MPg~>R&69UGIs|TJJ5Vc zoL%AUkMsM&16jnHuV&sdUm+tEV2g&kG z#P`Rw6*4!zOiq9PWf3y6^|@?R+U^Coj_HZ!Ln(jb@3rN{PARquII5&fErUIM+;<8H zR)-6=W$LtQVnZW>a;o#G=sThLb0FbT$sdW;v7yZywgOEa;jkkW@H@VmWAIwmjcE>g4O_$qHXWC>c7Q z4oj^z2%3B~Yx|q~Y2jKC^mde7J-IoGklIr4jX^Gxiir znBiG&nN;IDc%BZg_td+9$XI_Xgq++jz#vV+VTUE+jo;obnV_wPw@&z%4*cw04Q&=~ zhJ*UT^cEkzbI|72q$f$qRi3!RXk9tn5;Fash?z7cHH7P1-xO0q?1}Lu9_3!^?$;i;NP#DTkN>bUsMyp5IsB;`Q$Ir zA`!wba3SyEvG>QS*U-ValKX!6g`1R=TS>hyWI61$sh1uhc1zx@4uK=Hb{$1ZcUH8E zW^>;ALRUv-0sVZr&rNFd@k&STxTS?e^-SfcgI@s?b!c?&6o;;`fU`uTO2R`RF&RN?#ixI#?L3c5ypNJ#Q5 zR&&W1L=zg$?&@GAdChs?Ufmz}e-#=M(a&~o`=fm~WYh0bpS=$b;t)9f`9xP0zjg_m zNdj9?jpeO8f$G?U%9i1=t$QsYdKQMP`h{<9XXYoieR8dIwqn%}cY~CKmEe`*xF2|I zx(Y~WXT2=dDGGbbq{}uwW-ksW)hhpR*Z_mZ!HE~-K*XH>cxr2jWJKz0qD#DE6frtq z4NIQ=8KBeRlTR7{t?@iI@39%>#w}Rj;ubFZo+TtezvO)JT1utG!aqQwg>iBX(-Nym zuRy&l>@BPIkI6ASXiSrSiCco%-8K8jr0nR>tnAp=0i(PzI;?z!fmoT@Lg$|}YL7nY zSnK8D3dXX$#Zp;4Q)A9t`6?!+Xu2BOFgxjM$bLBi^T^)LUa*g-c$6np^Pr|NaYB4! zEO(5NQi)%wQw`#`y7V&-Wvooc5>Z5cC-UkhY@+j}B8ddy ztBihH@!975PYd!^o%EG33mv3|7QlmWny>< zJfdWnC5#u=?t*DJx9UkK!!`Cu7Zy}Y@>)2GBfoO3g0Rx~ObXxE$;52}Xvi5E}P#WDBk@EN_~Mvu6m?bG68PE?|a)CZk=!azYyy8++Wml{7Fm) zV_ElK@wsW?OaO2It=HNt(O8?-KX7PXX=7}E)~(R0v;Qjj(N{iBeoJoH;cK7z)tKIM!(v&`|k8@ZNZA>Q{f+qyF zdXu5eIRp-OZ5^mGqi5W)B(&*Yi|Mn-3Jg*A*OWg4m0K427Ba`dO<2piJfgWzzhQAo_p`1;X7S9stmPz!wPwL(=wL#vP#)m3(_TF<}8Fkk%o zCCb2)B8Cc zfedRvv6?-8yZWiz+M)D(sL~?e-oiflxf$nYuZ@h3odfCfcj_FI^LXG2-Ou!?rQ`c} zKy5~`$k_iq*M{?b^^vcuFRha#Y2uQ`JMq;=b^=O9c^CB}lZZC}mK^YP5e0}|Fo+i( zasmsI-3rHiZ%N3gQT-ELILCqP4zwEgwtbU;+t43Yw}s;_e3b&?oO@G9euVS2;AB^m zTpP{7P+QhIGyR`mxCHKqnudkTK|83F+rC^K6R#6WKg007P8cZSL=CO&JTTo`;Qk#8 zgdkIjolna58vxY&H|)?lUIe4w2-R%jh%L^I=ib6_*8l+V<)OGqk1u3XruDOFBiFT? zbVH%5>s-=u3{?ZUyVZ05Qitu%i4Wt$tNU^zi1!9R5~Fe+j{8@3Sj(hbvHHs5uPjtJR;-!2$>>se$4H&>CrzB$0_MVo zb&?EGTvA*X9zYfg{~NM2-(ot8jb^UBQqg?!OUNIO*Xhq4s(5FsEKuUG#I+OhD|ZtQ zWDX?_LpVDu-nKXN*%^{qt`Rls3+utjhJ^@3v*VXjo_fQy?$An?hp47I_U&hE!<7pu zaqGwp-dF6fLN>OQn5Cie7q4#Yj#+Qy(M${2ogDy&0pltRu=~aR|HqF^5zl51o%Xsl zwAYnAcTk1`Z9MHW$NlxfC(CnI^5cx#R;C7NE4FAVeTv}8yk`AseTy8y<;_PU~gyVK**tzAH89DBLXg; zfz%UCvvl~P*au%S^C&f|k0hM{+>xe!rJPUHu0HZ{*ft~cv-kw)7bp~bebXNA{`=e6KFB*p#X;LH*!&E@jLf)Zw&@8r%`#uO%sk2hVftI03z{(cRobpLILx(J zm>0+<`;YQ=DMlt?)=_*K2!{^?jyX8pr{FPUgP61qoD0AN(sd9sIk z?7yLZ!=gL&|NE4p_7@SfcZO#6pfJ!v%gU6%x8lXCGPosl*y;5)XW;JvjN*Z*rq$-P zAr6(o?`#UgX>Q2r_mEMkd*vHB%ls5Y1jZg$fq%f?#~8mOzkoc&88jK2qm&?S8b21N zCh2@xIbf_w&96AykOwBpFAo}}o&!5W!(#=lFYCxQK0P(YsiOuKL2_VB|NPkM+Fg6_ zxtA3n(AJr7cuVwP=vAM!@3Np@j%_6|W$$TCg!ga98qGxTc34puzBYo=k;uJHfTJ1J zdmD?207LYyaK)Z!{;3FQN9nZq>$!;E);|7UL|6N7^ug3Tp11>#HcP-@XGB)E&?&IO z5q_MRtsC?q&*~2D!rP*V*99SFcT{Y$_l?R-noB$YftChrQgMUNjp7lB74RxXh21=OsG!{YTFXrhy?xFr>b(TrBy!4_1rxblPZ?~p4g4oFBz5k}m(*Vrl@VyrN#MACm zdVI1%fioo_pYj^j*=&eEv3}Q9^x@ve8H1l+87LiO_B94*I1gf_ zY(LCsbZIRf$Uf1e;gfQMRRu&?6ii_Sz60<`BBkbIQzp;MW?D~d^RD%052D7G=x#`y zpng2i>hNe#IT73;YK^F(yJTF(0&Bf^?=(GgG&{^a{6kd(p4?V;F5VW`%`0ghp-Y58!AQKyiRS&e@JEt$solN^XGS=_`i%E^0v zV(-%kO7=PbVX*#R`Yoi$D_1T~>3?zn3oBv!mQ}%JqM_l{%HJd;yR)?$#&*kV zzx}Q8m-m>@1AOUdL1nimz$fhzIQJh1IC@Mcu2QD<^moUB7TKe(&x`fpd^FS?O?s@I z25jU5J@}Q2R!mXhJec~Ah;RTU5LskSfDIHAJ}a8`WaZOY%Ph=Yd^;GPhV`jVSg%;< z8H)7vvFJvq(8VUo2Tqm&XHAXLaF1rcjH<59aW8Vp;KrRG)hDGYn)h8$+;O!qe=5d< zUl3Q#a#MZ|m(5hNf;`IK?aO#&=4xD)3hXrdU{w3k`qgP^i<}fR&D+)o7}BcKtFi0T zykoqpK4L3XI?CJP9htPYO=0n#rf1}dwKU_B*NNounDLfwi#s1p^QP>9VU5}AILOhS zXnBLqH_3#PPRirLIsG9ZT1xc=MKpMK@pf<|n4~ z=>7u5GIalEI)$Xj0+_hZHOj|=GJv$gZx`Hup5owgoZ{6U-vAdPxBLAyI+4dJ`mO)`ss= z(vdR)n|0dpnVb5$Nx!bQtC=w4s_&X0BC1FP|*QI9yQU+CNCigqF z4r7Gs=tr+6%@0R}p-i*HmxnaAD)zw@;+5jvTZeU)zzJR^to=nHeAlb>g5@sx%gRHr zk9T8mSO~&7ie7QoRftaQ9h4CU7&a-7HI?xyBetSl=U~vT!P+W8jq|ggtc5TVbDMb` zM;n80?Yp(LTOMmGd8_&J5d9y{e!zq1%$$k1HPtyf8Yz+PgaPC2u$G>Yu9=pJei*}~ zlt~y!U~6|nz{DWJmk*w0TKP8CHA^Ybw)Xv0h5QIrzb7XWst6nbp*KG}gS59Yw=uJ_ z03&Cj^S^ZE59rEH1;GV37SESced{ktGv>c}v8l0`xp+Ujn!#FJ{Nh3aB^%{?MAdeE z;eSFcZt>rG+SpC6O5;roa0GC#Ic=+}C*Z#Q{_Y>PRg&8|>Xnk37UZ;%IpW#9@wr*O zRQsvi*8|1kcqW5MpUHt6o_x3#r!u)~p9Ip-|GtobX7@G;Q6&yQ&gK&FxA2S3ihNP? zE*WA6S8Jf|a|tLz1BW`Z+KU(;{>?w1h)=yjjxMbvvY zEnse5mi1)sZ>!7%3Sq(L*LN!&P*?cBl>tlnJeYSOwionR%$MA`voCouKpKp{KLP|W8Uz-S_MW!iz;FnMh=WgPtKV8>C6^J`ijb2}qR8ME5=>SJftcy*zgeq5w7?`1P`uS9lL$}#}}zyA^vRr*?xs!lz<$3J~XI(gfd_>0ZJm@HP_iP1fF?d zJLmZLoO)9uWXanbQ%bnHv9AjQCIiX)^-ruHoxcP^6>Is5wloFSp4R@Lmmm5 zK8G3``sYPxQ1ek6MM}fxSUi-;*0)y{H!04nCgq2HqU44! zhtER>7{zECwOO^8jn^CP>%hMheoymMEzaQiT_fikp_E`YT385w@PzW@ePSoFzgW)Q z1169042>?;f9-KI-_1K(tV(ywfZgZAsd{+HYl7}C1zx=unh?%N5#X^4t4HOieqxjA%YX$>C`_m7`I)m_ zkz|G+&3`>dTEHM~5xi8&n!riS8e^}JLvQ(pFI1)9-Af82lim7b-7b-K z;_93SDm6}b_E%J0bIB3^aP=}5m@S|AHkQ;mbY}sz;_I)N?p$h|cQMj6z&IXOOkzW5 zMQlxU!u$P@QdyO@QGR6P6=LX+clcZvWe%fnWL(Qpo1c-f6oO7M+GBXdNeAy)cBI6I45VIA7iIC z%`b-g|C|+wR}(i8)-uDq2X32DJ2I|(qN%RRD3N;RD9r8m!ySpG?rSVhHM=g_4V;-i z<3F5Aj;NoYoQu`Xy>L8R&QoR7Rv1$p4Q+gO1eVEviN$e{ni3q*z{t2ni#~6<(}!0q z`~ZBV>^_eWUD#Y6c|It-)c7hRp>qpJ>y(;2x2sv}}AKW7Zj0sWfA*}!7Vqo-(ZdJg0Usv5`ES{y^% zEiT{=9*gc##ZA9Zy z)T#3CPT(-%$WzN-(~Y`~d9eCZ{V~MZLw4sTfuk1R{_VN5`AT^SBCXvaRlJE*0))>v zrI^*ZCD{Qb#QDTqDTebPwO#|yVonc`x4OJD#Xek0nZZLE-Swv)_df1DFn~r0mi;02=68}5&Vr?6o*>vTWxY$Nd3!a0d}oy z8OqDJF-OC$BMQiKA8eYWIMvy#DcIbFKY%pV(k-|L)%rLs0JN)71)}RbwKB(jFF!Ll zYAIhv6%qr|^OiJ=4@==WZa=LD(bDz9%Z-zj9^WQ1|X2*X+KxYy$ z^h+Ag-zXx8&676aG7wRJ>>$#xc~Uo$Kfu=Xxp$)LM=#=>DL9;_O+TqkDEM*?Z6zV6 zw1gkmhAwGi-kH9LaX;{`50Y|sfxv>|iHF(GkdsT1)kj_t**Tk8Y)utsvvniM-+;er z(-dE=iscEr{_A8*F90XAAOL*N@9Mwya55l=3NF&qzU*9HS>#aHbLPJ&7tEmhjdnP0 zzhnJwP%U@&1zc5qPEFp~G=TFBLM0JFN*fQB~AeB(nbWQiG}%Na3(K#+7T-q^gkWE133n)bU2W z$lf$+P3-&`Xh{KhPzS*6SF~>3i*4)N*CMNLN^1bZ#2e^3?bjQQrddUl)DUfp$^A>etJEsof^)mq_&icrZE;USEu|PX1nB+^zJ1eG7Q^&0f%o z(k5l{TH}$I@Q^(?|K+s@I)tjsGBRu|4=i1g?^qy2qbaK?tzh*>7?Yub=V8mu5Nq_s7HDEoL1pf-RBGC{VMc%N*18ZHu~n)_^I>dyl$=hhA?Z;}!lS6jPhrR-5R}8CeYp|m zmXjkcUaeQlsqC06xc-Y4c7k8=O;t%dzI+?B(@5Z`ZtI_J&#{bb7?!E%{p z!T{Gf)Cn96yb{Gz|Jb<}co z1?EN=|NM}AThzR%T^*|u@g^b=z zU{@?(aQ0KrvyE&VJ`xtW$;mG)L|m4n!v6i{PQ$UFU5KdNMW570!f)?V>n`j>^;4QG z&=^HHYoY5LVSvoCV7X<_{p2_2)Qo=GhWH!~9(Aujnw01#xMj+QSv9CdgMX=fvGa`7 z_@WZ{Fg!ighH`s5%JeP!Ojm|ZK`QxzI(^prjcqlC9QBtT9PyfcsDplOJEBN7LvR-a zY`zN-dN`eFfyq>VEU$aHe^ei(uKBvS=5EStWd0SnEiU6Z9&dY!$hvmyi-HD6pCT2x zq*Uj{+Oe;HwpFO+oIQ?9BFKcI`I^A>2U3ks=k%*YHD*MQ<>Fxy_s=YVjBru(*e?>% zJh9HZH1Y=RXt>|dg2ASZYDHx4NGSY-mk^++bU@ZlEe|Ed^wo!xr1A-3Rq)n2e^rqb z!#HT&|DM1Xu%65DpS$OS=IIDOb3Q{Bo8E2VeVfn-JNJhMAms-5Q37jW#=9!2VPz`A zkr&P;k_mHFtsCCd`ic4QC)Ap;8H_=* z4=W42;!@!aEf1>-DZzKcc0Q6%{uR<*`K0EIh5iM=D8 zQJ}agOQx{uSSuKM`2D^>g-w7V=zyhdVx4uVTofcr_Ifp=U`JXLd-|ljBB4^7-S%$KwJzF9-nqd<8|fu@x@ zjgB!!I1chy9ArKNKlr|mhG;9q_Qbs)WM0pkOG(RvWNWi^!LpMah*l9vM(WH16YPf8 z(UHZ>@8BvNZ=zU`du(sxemOtr)Re#8_^gCqUSIk{%@h=Fe|9XrSf#61+z~v%23>m! zvURC4hnyr#=;;>B`<(IOOa(y`O4bUDdOneh;HMZK8GrJe0}Irau@>@KNIaBiuv@#F zacS|fK?vDQeSdUK!(hwJ!hM8 z+MZm+0RPi70jk$rCHO>kor9$~1XfBDmALV_edDeVkN5Z@G?|w+S+$h-Gwa{AS+2Yw z&L_-Ww>EdFPEIRK%s2R$rax4{BcJU8Lx5Tq^fX_Tik^REbVhjz(Um z=y>Y{m*6{%pW{p~^tCgG*jc6JD_X>PHGGhzMjbYx3M2S-suP8z9K$sWU-Wf8_QYms zs?)-(R%}HdZ-Cso$`uOcRY73We8&-F$g+)=!DQ1mD)6CRa&uH+Iehik%on7q++^v-t#8UCnze?075cRtOta%6PP zo}XKMO*jka`;ycQ zUi#mcrW5hhV8cGfisI<60&Wiy{9>wURoNz^bbVa093x}A3Y26ap3zYvkjCS-u}{D) zDQooti5`0~xCBbZeHjH$!h2RuW(XCPmKz(@kGw>BO$fqIr`e37!%k+Do-FkWdnqkH z7-_m{WwyR9BEFCJ!8i^G^@5(!LNgr|B;*)-Np` z20=ve&D#=pPlxC}OlK5d$xL846pyVFh6X)+UWBz)%7Z|VCwHX82U#J_8Q(m&Myu~Xo|MvEU)f4kE%H3-M zk&~$;QsKNa1-bofF5=mpg`QXiZN<^A{P+1-NXQty&)O168RXwp?UU30(cyitoh3WT zi?f*{rx)LI;NIKdK>0eUNT=Z}rJ98MGG8M0tJ0I>i>DUHMskb;he6rPA><(bj74q-H)EYs4a_V#eUnl?XPDL7?zf7?p`PpIdYLa z7VY0pCWQMlu$zlmJQZy3-~SwMu}baQK_Kn2p<>H`CI#wJp(SlL`>EO|? z)_qkv$#7@4N_|ViWun&vsX6Adto&!|ayBW@abPcjp8oghPEiXxSR3hcoh0meIor0i ztUTs|)a*0Ko3iYkCb*UwW&K{Xy=)cvyTCUowK^WmyvAn)0Qgz!-hVx4$ zE;%N= z2f2)&;Zy6TqhR;7No@!DxfDN8jK-iqlfo#JRM`NZp~3PAlrN%zvVqD!d(-dQrIaQq z-Y|1zlh#nNzX!E%3v4V*pLzWR3_GhYlE{h^xR4I)T{T-dS~zWcdx;^^wcRpCZ+VE z2~$h$6I<7pwuXxrESPgGo(V>Vjq2|oS99g-Z|3y$oZdqd@A#I2#pF`bv zt|doBYQ+_+NsX?nlg?ig>Tv7NY^K}?m(M<@w7?i=B-9$8AjEbLOp}@G80Z9=BxpB zP6Gzr3Pb1QLo;+yVrT1s>gpzIb2MTdgchagvJ7@xL*28D_fL}2)A|dK4-DK4(pLtG zzkYwB1fDe^7FfQmy573EpADsKJNohV>bD}5>VbEp>F4atmf=;i~XAPTm zzm>nWcv%m}ri$;ZXF1g+tL}7oXi|wu=O@EAV~hu>nYG&sz`wv5!Js_Nx4FLl*nDb+ z>2#A(b90GtLxJ5$2L`g&qnI@n_k}+bvbZl6;~&|I-`JXKf&@{oRj~1BnC9sMjass$ z=(TxznHleSJr9v$t`+JYbNaEC+Cf%0P2O4WbkF~{#w8foa7X84m->JKQIkHQ!4dg7 zZq+_6ouB;r)dBG-K~xTP@QSpVRkO^^b9Y8ku|%mP@CUQJi$T>+SHp-<7D|=%&6NFS zR(OZl1BQs~%hOn?u3t!l`E@P)OJ1n?cq6^GZX?yzV(+x^&Fk-olJ zU7~KBy*kr{ekft=;-!U0o$}`o2cO6_sCa-M>xqNy)XQ^FM-pkQc z4`h9+V`oFM>YP=`sD9HXCT=QTTh1>T_t+1)_m!xS_T61yQF?0=*0XpFBmNOIH&k;<&pPjLPaN?m?jFt8ClO>lng8V88fm`6VIEfIVZs%C>yyKdaS z=r`fNPWzfAz0Q9o@!7=IxT2r29S~S46*tX;v$XTDpVI{|MeIwOZt1;eFx`0nG24%|AEy zLZJa&m6&C^pH?Sl6;VX&JJnK+exL-QMZ7pko_QK>OrxjIJ;`({0a=pZxR z9mD6G4+q_6919D$inrT}+3?(FK}tOD{!=KSkRsM6$!9Fa5+(&gx0hICf!xSp*S5d@ zWZ&tNN_()EH8#B~id!f=%9|Vosi&s(&6o{WbwEHH<7~#!cepb6B z3Lz8f_jfXn9ez^TOK_y|P_RivLxOMW4V1Stqv({mFzxA+=k*)A{S} z2P>A50~2ILF4 zOY`R2%!lDa*@0A^-H?`>?r&><$~CaxnQI3;@F3wVYRbLT0a_)|Sq^CP=7o5~Ik z>o7z-c2&71n|heHwk71C`O$S{yv=5<3P-b#5$LMK&+V3mcQDsL*hnxD!!jhC&CZuGK8HHv5}M1jOIsq zwKn{ZXXhb#v9Y!Po@>R#GLl$i0HAaQKeipcLlgWNc;VnlDgjLB909*bFXfN-x9d=#J_NumR{ZAbt zH?@Az^ndCQI_H!=l~D?l`1 zL0kLA=^?H~taU|k0+;EotCQNi?UBz5@0e_R3UB|RRBbF>_}y)id^$~~s?2+}iG`d@ zM0tsHn{lw^)EA3osB`X^BvN>ZJ+hTVySZc*Z&G$d5tf!3mdATtJ(drokRlef*D4w; zF&gX-9rM1Achk5d_r0uk+>sB^m4)5d>AYJyDZr z(R*(pdYh<&VI)cfLG*4Sdat9Gh~9hey&DY17&CJwA>a3&>%8Y5_PF-NdRDpbwbyfd zc4yasy+fE?jS4(FjeiB!S6ge=CiUlml(zRP?+iR$_IHI0O+CUg)p%_WdY){0_?^Bj zcta~{!cS&okk9+b^T6|)8Pwst|He=(GOv}7KZVFCk;=Hv!!A!O)P!ytY{(_bTehB2*SPH@X5>NU1vGGl`i&(qw$JDBpXY|efO?^Tqi zJ97!QST|+$Y2B6F4)!FhziD{GbsF8) zS#91Z1YwJik%Gy zIxW}s{56>;9vcIX`viT!=!pYFi`(uL)EPE@gF3uGwDhQVn2@lzw@wVv zuH4jH^9nUyFmo>D*;dOalsRp#w!`Sip)McKK%$6{$6t`n_G*oN$>J^a=Aq zoftFBcU8z$$wKAb-nj$=gMx699fWovkC$H5MUpC4$2j{K>qL%N>hTN-Nmi^i_WVO` zKiZnH7Li4{Gnb;8;bUo$*1V^C^GlV*2K8KWl;uT_a??e$t-b50Qbn*M zA_v6naHGo`>rOWfVe+8kt&=Qtc7xB$wV_oTsNMt4m9}c_516w`-6E+<#q*XDpD(y$ zr;(PM9VT>*=b$rCg4PrTm2r`7BLM<@0f(h7&D(0&Xx*D1sF!dD#M&pABrV6|-c&=o zMzc=m@1eZXBN%GYR@?fD`Q_;u>htYKc}u?oYN;eQKV9vL+on-*8sy}^@xP1XSKQ7y zritS67PD(x7L5w|(SYyRBGDP}yo>wXp=Nemim}Vo^BKO5XOx0I?Q>g*+wigc+whKt z*X!Y)@IAVpDeHvkVot^-=bdO?_OAVOfQrvt$12QPJj(}gy3%~X7|6z_d61fh!E=?G z1&a8lbYKQFHn_J?wxYwSCA8Mz^M=jS>B^EC(}cp!*wb)Tz<0!o(Y^V6?fdh}O@b?& z`wg;1)?vD*KYM!mi-j8Y&$3!@)v(@z>mJtl&SeVleEPR$ z`KvYGisfZjE)*+0oy-SdA0pja>xwI3bC2$Al5OLT%2^=?#M#A!B><8wqr;QL_b}m2 z%j@}?`QwZ0;oh&-$8rRJCa#BQp+DD!cP-GNxY68}UCP&<&KXi%h-`9C@A(-}uzM2TlPKRoU&z z2CYV@(<*s?0s&O4bUuN^vD3@)Jp+YeP|W1IL?eA2&<|nErqL2W=*r*u`cHl0OvSEA zT-GoV-<9FUVpq3pJev<{`DUE~UrjV`cUy{Q0 z#y-MT^Qj!b3nO0Kr5GhR2&dSj%Q+XF>rh%g9q?8FFYPhE^{xVmk>H8m#|u)5SMoof z@pq<;YHS++kZoJ1FaB|FC|ho-}&om5nf8Ky^d4<$~&+<0XEei(~52(lRuqp z3jfh^KEcFYVbPJttA=2kM&lCgYJI+_^5xyg5W=Emdt@~-N0V3(Vx>t2PQ-e70Y)w` zIpMRgA~C0?y7L=Ax+uDGNc=8h04x&Uw5k%21tt}hKoe{SSHs-TcSKHWOC}~KjCz%I zmB(nk@617EAkih$WMmf`D*=#2vOVi+ggb-S5iOwA_@irA+XKsl$5mFvAH`+!5QATF zLtVx0H;j+q=I3118 zj#Pew74zA)W;|jVTeCL!<9qem;L|9Vo~1$hoclgAZ*W&hrCc2?9Dz?sE(`gn=1<-q zcmKin9{(#p&;BWH&$T~EWqwr+USQJ4E7ifo>BeO12rPx9sscQJv}?p`D2Hnj4evSM z5M#n-@4-9-;tn8>%Q}4__}&4v|D6afwgZ8wlipKF@c3hNDXleEr{|DOIMbQt_Rsi5 z1S(bnw-FESSrV6N(z1L1ngK(3`^qllzj{==0vIvze`$S{`CnOlleiWn;Y9GeTOAx< zS4XN=ji8^E;p}`;`IuigprjlJ8~8Zg4nm=z< zZ9@JUN6+|Opc-$gMvq)DlS$=@4Hb_YBT8?1|NdJ(9ezmpyPBJ+1AdqUB_+fHYckF+ zW`9_$igrh#rJ_l|n0#71fu)-O45@k?EPn=D0KeZ!>yuGoz=Xh375$|zxULXOlJBpF zAm`fG?@L{A_0T0wZyVo~B)@W-+yw5`jetP@PN%qIFE_$rRQc6b#tj9B>;mHmv zU-W#n);3&hE_GUvWtr>((IO7;Rj$Y;s9pPr(l#}f7~%+AL^s{LB1nzPSUx?!DvY0Z z{_^9!p}@<8yKhKJv%C|%{eWuAA%b=(hoebCAF*Jv4JT(P( zd)kRx0CCX7{c+FZSiI>zUo#yB{GRM$sw$`IaoJ?*ec>;-p3ChW${!@W^7qSd*>L^v3Ru(0m{c6=51o17Ao|N4-MD>rxlb94Vz_6h{Y zuQn}9iukJfm_T}L&CMbDAtu+Q2g}EWi`I>vF(o4xZn^WL*uB8dISX|tou27zTrJD! zzn?+$?_wXFn!L1WU2q|vb2{_-Z`SE7Kmt8}=T9rCivPEb_`3P_|L+G88-;Qlelv4k z%M`8V8->Fg#$;=J#`XE~1}||@hp1tl=WsE<0}!X}HFV11A@saV(2>goh1qghER6f; zfsU;S(!JxV-Q!r8&)~(nnOwrkwQJuGz!G*h{PO<`Ju?;=1wj8$%6*SNbM4l^uh0LN zAMo?HZxZ4m)f`{du4fn?{bpyX;oY~{Rd0cpOIM8&CDlV@1rk>x>LSbP3 zaUqmtT>j&1;{wlz-y;HBsFCHA3tviOwL;{J0tHmB+`_%2QQ2@|Mp`A=vV|JpM5=R7 z{%BiWmY9yC!05&_j$dfVh6}B{5IJ59wqtNNhiXNR^_iP*f z8jGaS7qI_Y?#ia9Rc+#RP-!BWLVBNIACFMz&XFQ=Bm?yx2o3NsOcomkcjc-H_V-l0 z|Cl~2%qYTOqft0q-p9v2`8+`jRx*)puNHC)?IESHx_E+R^4O@BBTl~{lFvL>qbdFv z(2n(}GTo|wtiJIw_w>1Q2VPLWnQ;Cw=b5j%hK%5;X`h4qCb{$ANr@1)U*^oABQy{YGY!FIg)$5fgF zUzLJbfVTuke!nc1OX;$XyK`FJ`vZZZdVl12?d{RPiQFfSCK7Ke;Yi{c%Q9MqgWm+)w(40#VD^0KC#-3tRHP&U_dl^;)i zSYwZzA=-l*oz3oy>+ z5B^a9Pun;cH&nrSxIoip>iHojtVGB>Y1Dvh zuS|!MYC$kVcAm3F>D8oidYM5Fvqm9R)dB-)xpC2>ss%BED)D)OsR5H)c(qdc7FIDm z{8gP=Hf9MO)u<f6EfOF~mN>X~;nahMb~l@!`aCxLhcOZjD8uQx8$qrV8C%GTf=j z{=xu=zcAOtw^y+HLBhpadt`b=nW~R=wX^NwW}m^R->!7jTE(p;PG*d=f&znprMhfT z>+!5tYkEc89A#-@E=IwHMvl2*-fHE^#w7;4fw}6zwhOGPR`>SJlYVL}{D`-a7&j_S zGh%HUXP^C{^0Qo0u-owaDOJp-b%bfs=6Ace#ZHql-3VHf`DXC-&yDm(OZ%#u!-@&x z8q}zayRvsjL_%aN%a22BG$g#n^5tBWil#h&@mY>0i8uD~2RRvo-d0Iwx&9tOPSpQa zE@B;TQCRR|8DHyH5VM~pEZ^n84=?0;aRH|eXs;0zc^Y0^_bN~(Zd1c5`NFEd&wN9W z_K>9ZJ;?P769_BUk!`<7+e`A`g9MC1$Kow={RLFfox4H3%wHEM*6!!Uv3f=xobD5rM-#t&LP2wY)0p(_Y2B>+*L%sZ^G;FN*7vF zBKUp$4E5H}lkt%A)g!POBfuf97gBH8Y}NFw=Jk1DHPq^W3(UN7?3 zwOJ4JPZfwKW6OU6@?{Le5`{u7!y@@=EgZFqLbLK|+;O~yJVYY?QSm->NQq3dVTaW*tH}Xt*e4bu4GgfAx zUW`O8+_dGd#m1yfk~!}VXm9$1bIf(qXKzg$$YHDmwOL8Y!ho`G}FoL8ry8La=dGA{iV?)^);_XL+S8!@v3$-_q} zf*0O#XQ(BS^Kuv)znrTBRpunEaTRbQtEFck!5lWT^gTzP=fXSXC?+6HEMI9Sy1P<@ z4mSsVpYJ?-4B{y~gTHRF+6piPsoCd^26_*2I0lPXuT5V_2dDzpXOF*rb8HZnXy_Kt zk=D!?nGKNrWj`>@hR3XJ0ESNv6u=YFj@iuX^nx_j*UabLb%y1`{5=m zFa#LtF0e$kMlr+0-{yvJn2GKR{{k-1s;hq1>4SXL7fg>8Nr-m{NX1vDd*^n65sl+M+Upg5 z?iP(IobvI?pDbD29=|~lmMKIRXrnQmwL9nGC|ek;XcO6B7U-=O`zocwlyZw*;OtBE zww1Q{gabJ-ijKgLY~A2W+(7(Ga1ls!w<{J`J>ieW#=C4H|A1;lqR#L7n$w! z%@4>bzuask(4Qmi#jv@~f^Shm#zBEoj60O90^hSw)^(|3$~gL*TH(L=WD50aLuDw* zw*;UzBaeR{TfCa02$h3BKk_#&ebdy*IVDU4IpD3XhHGxe!)qB^Sq)~9lK874mCn3l zw^Dx7Z0`4pd?-8R1IKtKm)m~R@8Thvlk1 zXS$MZ*@x{Lfvw)c!-c5|(KZtWI$+LD>~f?HG>s10ZLjVIQ!0hZnwQ@AFMY1RsG1wG z6rGuyuT(y%PqI5py$o_E3-oZ4s(s*2f&^SXpgs{ZEBQY$k3afwNYcbQ5$eG>!?Ow@ zX-Ql&`i4^9-}0~H<>V4773iH2E|55C9+eEFK@|)$i&@9z4T@CFid%I_LAS#tdZ2^w z_ai2E%im$q2e4G#Ofq{Lr9+*hL<4wo5_MR6HN``oYRsEe0e-PQKIZ09#c6>y?$2E& zrJ_$8S}uHpQexxJaa-2@=bmk}5wfSQ-RZ{qnSp1KW6Yitv*|dYT;VXPN;t~v_vr|+ zO|i_%scoEQF4>4@K>OV;y%>=Xrg~f>)6a~vQXC$vDT$C6g>w!`jU9`s|3rE6nRrHn zYBdwwqUsgcxt9ibi!`^sYgj#Fy3pL7Hm`;k%OJ84%2j>#@RV4+fbW!(=^N)v$ zccPF${|jI-5vU)7k&?C z|7&?TK`gT|8*S^L@k@Uc_hwwZ(Sl)3+j$bM;f)u9G}2k(i5HGJdMA+~m^T}-+Ey4| zvt_dUV;U4G4Xwi7_v5>S@GxLEPm}P!>6B4@Ffl-8K8bdt{^XO# z_}KXpOA)AlS(;aCKGYRQ3%fj#4z?D>PTCX2sq{pX7%CRA39{Q>937yS{q$ z@EN?|8R(}X$An~QGeen%ObzFgdk~zS{w5?%Y8&OBr7OtxqS~$NeyY^D;j-BY=(LD& zs)wJ%3}_Klq$=k4TPy9pxCwjCn?eavTNRw&x};8{vQKK59Q!|CSGX%y$TcQg9(H_y zld=r4w?pt*E0>IDBXHYaF{U9&McQ^r%MWO`A}kE^{FKiUl7D@AxnV$tdspu(Kx7E6 z6&yX%Na)GT*#;wqreqP(ovIE)54&H`y;s>)5#539<> zbzTC{`!J$Af{#VZN`HNy&Kl2opGdYc&14@Dl!a^+Ni7g_{6b+X!!XJ<5$V9&4=0wb z@)XspDFUU7g*zk-L*^2ejC|cJNr`RF2xgQ8bEdN1p8_G{!W{{#dE@V(A4WV!T>(*f z`aI{ZbsC{;7?BdW2~INde3#i{sxi0Rp<-8gJ2lm8F@dEx8|^~wF4wShrLzclC98XS zqMbY})TWhnR)y|O9dAp+dAab2z|E8lM%!Wogr`ew4B)F{s*8RNg)V5cR$44wevGyc zJA}9Jv|LfBeT*+#_4=IaS7=UJ-QBJF|5_NR1t0UXJU+Y&P?Z4Em~z-?ZMla`XSGNp z85SBJCz*py_X?*Jk3JYBtU3riUhxEtC#ky3Cnq@!$q1Sy#aIinxV;N=7hKxX87NTK+FviR()2LS-tA-0`G+qzodNCv5F=L~y}%3`;_<9gv#0Y}v9`Xr zxEVMDrIRY0=9cBJf9`S%XI`?m$rZ>^)dgo3@{4p^FU zX6Vs&x`Fol&HJnq$+-3X30LS5ow8gd;cWvi*_tB7#Oa@3_JvyYMtv{hj8qD@zSoQI z9kUf5)Cdw0j?)QOGtTU`y`=J4r-_@CNGj%B`{f3Pl5#LYgSligP*hKg0%%IKJRJ3(XYWxY;w!iLOJ zBG@Z4p|jE8;<$UN@8~w|y;Ye_Myp6cHo{h=Jl%vodg_!!1^H5yE1HZ8mATG%-Zyta zbgI0a{|1-%eR~_t?Ht8It+pR8+Q%4pdg}yFN_i`$&7Zr~t3wkFcy+y9gtQxjrgMtb zgZKLFV##m{=7C%M7ykADVw-rhX!eQJd6%2X{JyG{9@oMsn|fxdJ;ly4oxg*OMs6Df z-`6)O%z^iqB-ph}AYTm-69ITj21ew(fSJ`jMW>R1dp&MV2xcIp`IpH#hjO%j{-HFX z%+X2^lF21&}N7E);VC1q$&5%mJ#lJ5J)r0~xCp8!a5*fd?Zn5E0_9)lR> z$MVc{BK1cH4Fh#Trb|!UL-b;in7vb3y){$Qa3=ATCvyUOjp|+{3z=>Fc`agAal}ZO zM%jjL7N5gR*AC$Rn0to05GvMVe3+1Zpt{}e)g~p;yu>YuzY|IPLwY~P|5Hq4xxi&Z zAD^*jxUH-h&xF%G-6@M?epD6URrS#~sVoNqzdGk)*F@>ec%964r1|yF3QE-zp*12x zDT0Vw^I2JXepR`|s83b7{A`Q%HbBdZ6R1;))Owh=rIwZz)f$e*mJKUeAH|+aRk9Xw z&yB&rUt$Zu4UwjPiZ=1U&nAHxO1b)cy574koN+_gxdexyIiRH)^`8Rrq5}Vpo&ND< zw|J18uMikg**nM!$GF?PiPXt<}9qDVeyNUk5>}SjdF*c zvQHFfIwUMna9I5|P78X)m7~frPXN$hP+D3JwYjm7(q+ELY~3}z6Ychn;^7yMnG808 zQT7NZ3@lw$cUr_<$$BpJC{`zgs&?ldG)L2lX=ZQIG9uRiF);zOyzOv>a)|6S3TTbs zrU)&%efA?46Z4aUi!wQ4Ztl=-^9|sB%9&(U>(h}}{md@d(XD{e`v;F3s zQOJsEYo8hAV|!$<$Me?vZj|R2id@+DhyP;&Ca(pVuFIMnMC~i|1vk1`o{s z!i9naP9aj5eTb~M)2Bcv&?DGNKUDIm`2p-VK&CYn7YBt7053Vr^dm9hV^VL4$pVFt zJBnhV?UW)*Byb0n)?~`joLCcFqcHsoXvrp^Qa98&ojvIpKus>^61yo~BmC^t&QQH* zjSjy)U_Z^A@is3BGEK48G-g!eu(eQ zX4>Q*S4h|UswM4OQvJHQLb_7v;E%rzkCu# zFiD{A|{ZPWb14GEF?MmLmS!2@HAU zNO8>Cj%`^Z7=>vS8%y{u$9P?BCmmiAPesURoD^Wn`SEJ`M>a%|Q^y<);ZX6@Oq;$@ z{69r-^X~@E?SIv9+w29Sy-Dz1F79CFB_og<(%(Vk_bQ(^+vl*;U(py_- zj1x>ODI%ps&L`v6byo$TPkibX?e_@k75n&bHGskZaUVOyr_J+~(e)fNM9|!|#As{h zNB-xrkDgZ=zetKP(Y1Q*AS1C|)orC!{Xd2vW+#X9{6YN#Qz=v0et)3IN@a&sxm@}b zAy!x=KhC9Q_is8zbjq+07H`7JvN}WLhbDat58aLyW+bOR4%v+aK@J!;MhYA^f~5Ix zQU1jrH{4fPze@;(129XHc|KHneExsTDaHz{V(4wd`!^}5%yi0h!OhPr&2OcN#;In7 z>W!M|mG^WM&mA*kuGhcy;2M-m@=#yO3 zbBr_2<-KdtS{ne2I|J)0&+bf~tLLlR6?0zOqxZY)h$Ws}kpu36+1bfrZk!W^ zK^zXOB{iPcsV~pno}M9TF$yml?B$m&lL1|SG%FQ{_KVyN5`aE;u2uCRm|zz%V#T@PR@p$8fRMNRCUmoJ)>o z8vHr0FWO15bDMD6P`>Isc`iX~>#~i)HNd8#3-@L*UJSh$WyKxgXSSM0-&Rt5FAPm{ zs~GOcA!djSP5mxbQjWgaY6?9Q7hRI+-=IFR`vdu>b{adleID%bA4J2Q5~xr`I*RkV z*u=BR0Up2pd`(%GkKw~P1zPOG%B7)>CnQc`<#H3OjJ6Q#`zz-=;g0?DCh($g(9pB+ zzb$Vm1@wXC&A;sw?ou-w_vIM~`$H=3(X3ViGa1+O2L8IY@RLo>!w7)Bj7t!S57z=8 zrp7bIZK`0#VhWekrv!uJ(=$&dxp29*N*Y06^ z-hBrVo(psZN}+#A5tr$xkpls!1~tyn zC@fmVYBYzh$HJ+;<*BHeJ;%y@f`L6k{Ev}}m}KL6B*|5swu%)b3cynoYu?|_NPavHq^ z6Qt#S{@nq@U8E_UrCGDzVT?f1Oh4XnvM^6PIq?X%m5Cd=eZ~R(Wmo!tfgY=cp%V>Q zSbvX`=hCqLmFK2-^J&;(v9ghTvQ{GG1i0^A5e$Ei#Z-^fhGn;rlnW>*xRkWtMfO^_+fm zUP_)f@Nlk;Rftsx7(1&QSpWiU?Pbb(%6eAC&gsSG_&6i`)(CD9+!EB9)wvIF_g*`R zag_dQn_pBI%r9+V82Xj_je)M;R{<#lqmZvW5*H`b%fmv+Av;}?iBUBrpJ_Ua!}yq+ zTG~!pQ6C`qD7(pxRx#dFWGT$on`>A2Cm_TdaE!N1JT&rJ`KD-!r1x>gAen?Rr!J+a z)G^O$>%#odzvcHi|(q>OVloIBC!hNclin0Uec z5pRiqpN#4aQ61)W0T_F->l4oPvdpIQx46~_AKW2_#~ejFTOZeiZ-}0+L69UqXi)Qw zK9@qiJ&hFdxz#m6ZA0FWY4>>{vyGqytjG)BeB9}W16>RBgPfC06>?<;)Abzyy0iW7 zH2t)zW1r}+eIsedU%9bBP}VZ#S5Nf<@6%!P*?X@=;WutCLEp7+eDSwPh-3vYX$IQ~ z*Q}gzE@-7i70n*W5z86F7WF(XCtib9i{r?_G)2Ylefn!MAHsm<38D3o&4IZ> ztT{RRPE%rg*a-*$F59na2v2}aq2tl074R&k_a4)}z08xDWavm&{-TBDEiAb=h!%z0 zXN%Zs+VeC+%^M7a?HMb6Fh_Bv*Uo^CH@G2ZdaOhkiqA~V(rI6UT%W>P^O!Z|G~UI^ z$H>NYQ}jIOqU?9)h9e6s%OU+9Jcv@cFq=xfSkEl!c3*r#7*}o8*?y$3Jb=2rxW<&$ z`avZ++7CO=u%8?c3x^5StQ=RC$EeSD7Qhbc@V{KUa^$lsN7lW*awOghv4?cuNW3UJ z?^==^+5EaU)Q6N255tH!H3in?Po$h)pQols z&Nq(t9W1|i4}8d?E7(C!McAwD?!GDhqn*ux4~S1N9}%;_a(oZ| z_g}BA_~F9&3;1dGBKHTcZWl&n>-YQ6v;4D`-jYUQ|31e;xc^Z#|wFFJhKNM*{p{ z-{rp};c}Qq?{Viz=P4Q}S|+-Fj+w&rMWTLc%JOygc9rH3v)d9wZv85 z4<@>fdNOlwr=&{FylltwV*WF|@0GQWF0FmYceR|(tL6Nl7fu%K%)c%n2lq(^JTRrv z5I5NTR*XJ>AoL1DF?9xkD?K4!&$ooW)?yXPS= z4o}?6W$FE|EM&d1ko?NRch@f+{^QA&g$nS|3DO6qV;V`n+7J84WgyrrB>r7+IG+zo zfw(*D!@A`Hx*D6XwZDwn!}k`ZK?w1-TD~8E$8X{9cv{SeC&0b!u%e_FHyEUU;5CIV zyL)NB$Laig$DJRj3m%_^-jKL6wY2-ji1?xDFI?Ha5PNa^UwYlZ)wSQ@=QscIFSS!m zwx>Kv!p2`U5&O9~rekgJ$%t3bo#=Ihxp$G0)_5_+j2SKNW1(dGD_kwM93vU%j z8^3|--jCR8+B=`OGnU^g-W&SHF?H&vaDYHos6Jd#^)+U%JTVB+cvO*%Cc3hM;_k1?Wa>5wp^V zrxP3O6fE`0^K++)|B2d*Zxgc5C>MONM_p$+!-oAP!@VyedJz!(2@lKsFH(;$jeAdd z33>-Bmlb-g?h0S%kOXY_nrXMJk79g1Y7 zF>p_p@r>$G1Jl`T@_yhcC>_rS5rp`C@|2~SEA9&(Hv6BQELV0CUfC&eS*O?Z|Jm8s zMw-hLq-d#<2mpG;(fL)$BmziBv=av!PdsKk^_pVBOTcRxcL;h>QcgGEViuU@YRENS zE?zUisJ%X_qx{bNWjE%BIUC#~B&{b>PVqbJ&)AsnR|3{{tU>bvr}hilz5vpe=tVk_ zucTg<;_YV^Z=x5q5atgp_C4)Qjtw~O}5YE4)i;4_{dPB@>?!FH1<{@Wp3+pIsTPajZyf zO!ymX+VJzS_y%LPxe=V6Qx7oT(JTG}!3(?A>r%y?kNzS4U7Nm%(BbT}CoQixtVpJk zxsFzGf~O9H_Fq3Fl4D=k7%H$6KVA9t$E~nk_yQi5u(%wu;uyU+lbjoy^W7H@>w|X{ zYUj(8eC=}u$>R8zJba9Qbp!mXq%~%JWX5-!`3GN9Ot!H#Sb3-1K|4uf%6JF(u?qe^ zNNfsau7*b7!v47iav*-a+z(YpcRaDxYr*_PjPOr+FBnh6$wnB4XCn4QXlBt+bcUQ( zB?MWYy@z^t&>*7X3tJ9hK7%p6@x_YYyu`sXkIN3>2iKK92X0&j+xx2Z0zd8JVd*Qr zm!CC_H%$Yet?TIc!_Euu;DO!ehRM|k)fPPE4N*rS~7blZnD@>{>r`O=E`Jd73I8kSHj`jGB4AV!?Bwr zzZ zz+81iz{k37n^j!5y1TOCWo=k%(P$cV%`(oSwp=+`H8jVp#=4?OB36$w+osa{b+m3m zv_^ui#wYJeHp@7@*rprW+2zVL8L|qA6rr>hjDxG&W6$1GNM+|*bj07DK)vK;8K?J< z6Jm9g9?k@ZB^y@0F<0}H&7JP9Nz>1eFW20=;lHEqV&{jK7=!I)w!*Q%{O zd9P-eAW?<$5tL{`DTZU+x;S$^rq~zTQTg?6MA^$grjub`V+^T(9BfUQp@eCU95`Pz19rJNS#XHGb z(iQdJvxVZ`H()n*4OGsOTgH>i!i%~#w#(W7oZkn3#H#CvYUm|{C0(CO<2ci2uQr!H z`5mw16z_4vVFm48N~`T^I}L_8P?+RC#}ROnR0wuo8I*er z)eJmMYE=N8xIRmvux{MQQ3`g~^c(cM`PnULpx>u+LXXqqLt8|Hg(|Pn$B(P5QVDya zKjWV1&Woff30GO8SWkLO=^ntL>e|Mck9K|)BriS!T5XgPK98PIRv3-rBC1%i1|AfX3Vs_H zROt;bS+sRUyjSUEfJcw=u6&cUy?8w1Ztr)8{5Ue$Ty%CVc#}_1c@~|pU5#QTnng2l z$D?N49TB!!RdDj2ESo@m&uaaEt&GkGhm~W5Bi4@6*H6R1Ox#xDi~BkE`?Ny251lJW zUIeEP1?vSqpWO!*1UesLc9#Qcx8G7O5xAu_=uCs85RF_19h(J(jfsxv0 zUwakmVo17`VseD1MQWtSGr#41dILI>w0KCG$wOnt{l$S1S}SJb_?%*h4hw*CvVR5k z#OMJaCnBQw>r`Vb6<~9@Fs6MbHk;hk{*lt8QCV_+xpc*AdR@WZ?ujn3Um|Mkij_Jg zrJ+?i0{Z;p@g<_#8;vc*Zz(5W!Ud3*xkoM4M4|U=&FnvC-3gkJ_fGaf*`BKR7rjOH z*c#RouVmLF`IzK6dTllc`dpUIZA$Ax(2oTT;!*K+A4Y0)2WnqBYxFx*5^D5N!MnzG z%BDge6`Pic5bFs8qhaSyx&Kl|g*kn5p?5$4I zx>(p&+AvS_Zs?F2b3NB8j9c9VnzXFc(9H506*O*dQP>(hZJEf6EJyB^&gD)Mza`so zQQN)IZ-2fKCopeatYrj5d=>D1m6C1v3b=yg$+gcW*n!OO!C!Un$!8|vo$0LwkCrKz zKE4p0`^~XJyj7uRgD(h;m7b74`lvi^zZA!1RtXNi>-SS-aaWzojx?%JE?p*pfX|~w zfIuf;$Xd@j=sa@mvs;S6T})fqm&2~wU}Up^SD{iPRAXiGIse+M{LY!a$Q;$p8+)T= z-5Dxx!)xdmkK<8{J&p+d!1wzb_948#X946B?MXk$EY|jiKziY)epc$n%yq9@+}8}6 z+qD|sBF*LoIUG_8ZqG#)=(8+zGf(Cu+dc$PZtjrV2fu2l@?V%^=w*}24xM1Rix2@& zuY2s=n}tWNRc{ckaSxdJ>EcFP03*U zn7WScoe?AG;ZKEmvDeJzriOkhd8egwNdtMS2hiBgRw__!VWZHB6h1I@IDj||>?vzH z*0;jTcR@2qm13a0o;+nk-P&Bks3GjyMm(~qxUu^mf)=?#V$6IXdhOn2OLFZY=f4Xv z751Bn(%xH(7Ao^W8cISRKbp?@JcrYjv#+Hciy4-C)GGuzKF7yDM?^HpdagyTB=L{f z`zixKB|idux|7VS4C)#BAbrcbYAk1a#S$noBDu^Cu*SeFsi#v%gH)(*2uv&qx=(6? zz6R<#>apDreOu#ADBis{#!F0U=i6Z5XHQDRGuFc>Tyv&Re#a%sac5++@=e5gTbXa@ zc~GhYt58{+-z%JM@O|Y0{>%@u_)hdBfx;?y7mvxQDMWA`e3<8=*o)RO=|nrEQry?G z9?opYJedT)|NbMId9d{dIm@%H0G&F|1R0y3QzC&s!T+1YWV}!pg|HVL^`%0>If19YWe49x4Fopu`;zX9xKPpevxuxKP zKebFwqb!J)Y;HH5dgnazgo>uB!J+K!bsu?V?HaQpC*Lg?&C2xE^nYvH(&j`tR z_|`>h>$S>6ex;V6Yvl(lI2vbI;?o?RI$~8^D$CwJZC)J2EMU}F7I!@b*oSA^KvRXR z0KGH1=Pw)jm~?o(y_}&sk@FD^)SN79j=+Z<-O&|CnYO8uL72YgLwARZ?jTqEyW?NS z70pyIG|e+igM7K9UL@D%t{#S77+gKQy-2=2bI)!C_R$KL(%%c!=39`|13t73`^$%h z)_O2z1#f%ujWthakA{+u=;gXFu!&aq2AGOLzy5stvtMa@z)^Z*&Z>^&;~8&|b!g7( zk>9A(>-6K;izAa{jj8S()Lq=U$rcO7?{u&u@e(N3Z-X310fJi3>#=4^95Hn_)=s}1 z#x6Oe(vNd9oklbxDolQ?_wNXwSsUn45ghz^p5~>V{TRP$@VtT9AhX^ms3Bd*1K3+vx2(SWg5Gv z97yrxZ{?RMn!h2LT8#Z;teRPoejf8(lyI-cr~4kp%(L+6-0F=W)>$iczJQ){7bas{ ztdgak{n<~wt2OQNGPXf`<$x0H;?5Knwou;P@+oZ zVc@9`mBb_l^LA|4YQ#HG+bE9Bb_Kl=ch45}DFDGdKK6Cq$kVP$%3rQOhb!Qyv>pF$ zQ6roFg>~;rjfqwmI0P1~IYGE!u)kKj-aR@&B$qgTXc)V+moAt(przSOSh=+-RI#wI z)H_Z{{p9YK)}TPBs#18r>AcS8b^DXZ5&hOB+qv->ZM1f zC)qt%Ukzb$8mSnRLeIix&+7Le{b%&SbUT9QL-#^P-$Vd)8)_csAF0G zurKrfu=UkpQMKLPCMYO~hzJNM2q+;c-O}A9-7s{6)Qm{CNaxT9CXW1s_Fuqe(UZ3p zDrB=8GC0)ZlRfIxJ`Fx{!qwssGFH{z8UGv$fHak&h5*%gn{ugekSyIq??$i9oQ_PU zo+d}${-g~ao*7h=z5;31$l2gFpIJt^_ms`=R--7uhepwU+W0!rhcrj)#Hy|+=C?&#_KAm<$loV%>tCloi_v9Aibf9!5vfB2p5CGxGv5F9)0j|&y2 zM9`>57n?urNPDJpU?)FXlKg64j^p%Lpro*6X0){s*pR!8tpOnaKAC~+Tuylc8>tIn zi()~2u?konZC-u3@R^*ZV3v{Y&zumH>LF=|XVa_ESV~Yp&B{TQ92k&7kudLEAGMFw z2I~&q-12APZKoWaUmySFe*@><{h1vAu7vKTDXW~P{Uo6H9tz7g)4X)~&`cRGR$vmO z?(7gFnQbYS=uuZrHNvY?A(I~^-6+M>%S!5cHq*mL2)=mQ;fWDR(q~*?j%(+=M2fK) z;>6V{TI=VsAa9ky6ZZV;d%BMVt5+jaZN^`M@?G||G6)2Q@Yoi&XwtP|LWZcZfCIJ< zi=jD9+)dVfboj1VCeomwzF;z6r0D!9kDZGFlLhYX5|hPK0ssW)?bF9(5we@{w2Hw} zyU8g^bbd^U5QV?1<&2~@nag(SCt(iXJ>#AJz$-2LhPnF)LGa7t#tHjX-{*!u?!O=K zhYMn7-B>!N0$?=LP74@yxC`ZybqFiWK;fmg`$*|q&**soZp5Jm;E=ps`m8OSNau5o z?6+HP`=SURRE|*eYkAaZ_VK4h^-f6sSZMX1l8d{J|vfGZfUT zJ|Ie1SFGE@z8bj@TjaT&JW!bZUEU1bAovhkO2hHvYx>;2_7G|#@l&!o2+ zb$(;g(8$0-{pS8huJtv&+0M(p0xd7(^CZ8tzIY7Ax_iJ|AXeoh+o%{8-)dr`LJbAJ zwA_)qB8GW(g!jW_=NPW+82#F!a#c^gqNxZq*k{Ne_byI-PfvXXRCn^l=&!BO9urCh zrs^Yy;&L_3uQv_QKG^)>&c`4B!Y%*D_W#@`Z(w7Zhe+C3!R(@3YyFAY*A~%(GD=rU z4>s!s3`p6V)(py_tPqRejQyqgKO+c;#L;u&FwR`lRFUbtSx#9Gp;3Nj(Nwx)Z3cyb zJ=nr(WT}n1dWM+B3_mAn#*%KaSK$PeQTAt8h`5r|6#|pr9Vv4yw&4q?bERez%%lZ9Q^m-CZUF5!=QW$h;Tw6#wcE+DVVl;x@kYOc4?tYOUQ?eXZVREEycG=8*KO+|>Nz!2g>VtfC zt`ZUb#w3sS_UT{ZNMZO6mzNn!MW2))?#=9S>rz`h^}F8jXY1Pl04~yDAVnUSeV1fd zs6C~wY{+~2BMtWk^VZD*S7LcDqiUPYVK2i2;1uABkKU#H zdX|Bx(TNH5x1Yadn?62p*$6C)k8BJJ z%uP5GfV-07IjmA7bTcu(gLIFYQGvSIoE5uk#|V@kE2rgoJpQ5M+)mm%huQ-cI&Hs? zG>(dAth$R|ToTQIQ?_bJV|8gUy`OY>{`D^`^)CkU4+zgvaqE}Z8e16|fnwA}@5XS> zLXsG`3YSqqVX(0l+y}f`9&A!Ik|Eo2eB&RA|KK^{!*l+Dk!;u4!us(S%Ny*c))^Q3 zNn?gE$E@o1BP>vHiaB)Y*!>9ZxQqUAYciVABCPD{cgre zA5-6DjC_l7wphTPx&6;S|K_0om5h4cn@Nj}DFKnS&)K^i%y!AITHQx3KB&Gwb!IF_ z7w53kd2oS8Lb`4$>~#6kF)2~>&N!g#JB#ieo~>tU22AH@yod`=iw0x2!ZP<&1gyAT zjnR^DwiDe&xx}A5$EX!5zZ@%_5j5L|_9dO=Q_uoe)6d67$hzT7)Eq=h52J`WO*%wOTl~L;d z5x|_O1g8#77w32j0`YP@UgCtQZH}+CP|2zLzQC$mh9XltDS+h4vGZWhmy9(Evj75x z$R%WP$GaXWl|AF;yH$1{G-cQ#HW~;8P!5Z@Y|rNdXrhx;9 z5i%Oi$X&fL7NanlUU_Ruj2mL%DY)QW>rwD2@r2R;=ifPzV*&Ogq;#%Uyd}vqJt$+j z+7&_2N$W;@r(yQ>TrXB@Nk_ip(7o5TE9GGmt^wmt-uRU%$KzNEvqvFXNj z^zxFlY1tTT+x!T5;A}}Ta9!UmkVyTex_0?n5w?u#q49Y#Y`PywnZc5s<-o?{G(GDR@H)NKrGy6 zW3t+iy`JYO(XaSUTMx+S8jtOFepTZZCReWFR3Lq9Ph|i-*iW^w?f8Yb1*k2ZI{rjQKyr3?E za&XQ)CyHTG?9J}NdDxmj1b-PEqMrEF=K~6r$e>%y5eFj_G@~tJuj;h~E-7@);fD@1 zh0u)5xI?S)&VyY#PPCotooU(p#&Ul#`3crg2$$DZLWB|E+=H~M7+E9H?(~V>D5o}B zhq7wVRmX(MCifywQHQ+g$woS)CP$9)D%{2W)dMn29E}7OT&Po~Z#scQXTzGfzQ}FD@q_w}_ev#+bu%C6GS}Cm) z|5jo>^a~sk|L_gYdH)+3JX3y)v9bC|Cj&oc$s~>3HK1Y;eV=Sl zASpX8IzxDJz8AICokBOH&GgrYSuNy_Hxo*Zqa1V`MT-LkPtg-g4YypuBvpH!)%i-g zr4O;Y##!W7`@Ei;0)4qJySM)p%zsI!oy^`fBFK3{jj2h8c4d)i$jJ?v;Zc$!*(`WW zLA#+Ea)08_{)kXzUbe+Vpr${^{YT@_{ncsuv27XWrOC$$$Xx!2BfB-*`xS{to2w3C zq0fmbe&F0^jod>eW1tRE|auo{{kbo^Q5JX-YJdMdLU!U(^f$REd?A3g(1^NmY zm7q|2tDLhEh@KYP%0mM*?af}hD;%ok=qxYldwYIC$M?hVZBc8B4*1i#koN>Y-BtYU zlRNqh=xOXwuw?)_(n<5bJ*)q2UOlf-{n@KDW4CayzSLmaC^5krVyJL&K4+9*5?l2|Fiabl~`()hi_qY)ZchnRlqf{vr-PF&Fs4 z);44^{^X=%vYmgc-yUMahZY-&co(D7^CRv_`WTT8&x@zg<8+(YGqs-yYp6~0m(VvE z-sPI*6V{N_f5x79wVCM)g^9k`6Bwqgt?`9wZvBdI<5$;cEE3Q?CaQHcvhJt!&a5ubKJ@j|r#1jUMq}!f1LCga8Bf`8mQbUAYOh$t6E}5f*-?SU z-FRxB@*So6)lgzO7chl2NosaZw5vNAPy#pq?}YW)^Go~*{H;|x03<1Q1(KZe;#8eN zob2JreK-654VYN^?G=gS_t008A0n6E7`>MKZVdd*IMnwQ>0jjJD@h>$D=GBne-VXK z2rsCS>rfv0n0)7;ZvL|07;0TyJW*fFwpB6UTcIVs*)9jNB2uPNW0~6=-Lmuxt9Zz}ny_{S<9Iv3Y-(ud%vBLn(eG_0lyq2#Smwfriis^AStXx=e_~&l&a0)e z@3;)eg%|fwPPu>bpaYw3m{I7T36UnhGx)~(&#d^435K&SB-5uLEZ1+hE&5m)AzMIP zOjJZr@U-OnkLbd0^l@*a-o!A)GAq4S`rKHmP^PdqOr_$j2vufo*k=$wWeqqGsIMtz8IFE>aYR^hq6X0+?uM0r#^>0t!BeT3GB zksWy$!dT9bWtVvCu$El%)f2m9^8<=Y#%Iavf_&paB#ABai}?{+^gQ_kUxEgYSPbC z@Ug=j)?`!}va#res7tH#gSpoHRQ#My8_f=&KV#p1rqU}^$of>fKD-$4^OW9AdiT;v zo=Kd~0n%x16cs1CSe;tMg?!Wq?2klDypR?sNP=*vV?&JsF?p`=q6|jE zzCeg@I)o!*;ZW3*^w9R|KuA_kk+zMH6V`RapCcH6!*@;s{o%xW=0h;u87nyGpj*=* zNulAFX)1g89GH(#fQ6jXZfI=q&F={TCTdxuu*Zi{>WELyjw?lxqj3zU6ev0`LnO^H z!Q3Tl&^kEf-Y|v^*Y5cJDWn~|N%W;YofNK#-%PWp90>i2ducR4y|Ko5&_oOZ;Mw zDs+cSnq2C2aSX5#2al#(T)F03CaAu+{6++~;9Wcm195s0K(WGtuEtn_#8{J?1Lb`> zMgG1zCd^7rDyP?;D`@npGD(jqcz1P|*Wcyfm^^E`pCe?-$-oIk%%7v~Rg4#mUawqL zI7Vv(?r`9Mkrx$i@@sKF!6m?`vgp@Gcr_G5ngoFakgwB+>D9`uh(Pd~v_k>*YrnT(=T#Ozk>B+Q^ltQwz=tZ=TD3AT!{7Z=gyJ2&1#SiB&DKW+WowUl(DSZ z2sukU=Sy6|VGONz`VK5PY#G9Tj7H}ACP@eV_)tV-P#Y*9`99_G-oxgu(>2G)LuNDl zs%FVAAPg~W1}cjGVO44kkva3wA@y`gEvD*dMjlh)BTt)7ey-~hx6~k)L4J-Tu7HApS3HSG@JT*7;{RS3}JnT`x_mBV*397tWp@b`Iizg&+kQPCX(xu{{gUr0!OZ z#ua82@mFUGjCxr;b~QduI<-GW$U#^##kNs>yJeIUQ1^#v;XmF3q*n&Y|0A0le#TUK zoH21a!D87y-}jk^yrww;;hwUH6sykA;Xu6UCk%m1|afUps$PsD=* z`T;{?3+{@OEtCfY4|ACp;b~=m&Ij^2dE|Vp%vOIVY4VGZUlZ;K@H%nBHrEfzde%nRI z2TmDkh<;*qNN-)_sNTN5F{-1W!@{(Yq~Gi3!?U(JM4SKe?(*{iFl&Nz*=0v^`q5G@ z$D4&sFxvf{XFD`2o`2Lus$-v;OOJs&`5+}(;*6qtxNvQqwEv96w%ww=KztnB;}cnl zaKM|G?~jv0&w0zykqam0G$rBjoGiV*a&W9_R}Syi9?8{Y`j?NO1{O&erCHv6=&6?X z%S{&Y6NDnv0X7ZVF^a0KoD)YxOb(8SUx3ljeuk(wQEuFns`Me_H!B~f1+R$f;fqC_ z+&NU)K3$I*qBDes&EIP32Ms{tg=S9q9RzwO5GE4Vd`H&))LIdU1b<6r*5#{5k0Qc3M}fJU#sbq*xs!-~IqS=SHXmmVU z;)1e_awFngob4Xly)oR(m>VdUvzlV;b}eT>x7j{=Jh4Pt2?XG{tZq0>d;F zLN144C~*uXb6e;6It9B5HnHZzQq&DCg5Osc{S!b{5R2zhk5Jpvg41o zqrGQ;6eSZLpQ**H!PSTgp$IF)_)CvY@A7O*8lDNc9N$G=4vJof!mD$xSw3C6Ywas{ z?ep(r9UzLUZ1SXnmbOKXSJ96~IU;EDOCm*!njJ2>ca&|0D0{q|bPu|=DfPuL80HJz z+kjSYnjlRu=q}oqUSyd>_ULdalX^AB(7AoBw>8LhR^5zP29xUb&wsis19*finjJH%WKEbXV6Mv1eR& z*gm-5u`$hHgV3vAP>S&tRbkkjrzRA@<_;RCea7VOSgYmQxxTP|b{ed7)gbLTdvE&ksM)4G zyRm3)n@A}kG2LZ{e|0!9L5XO4uBb7)+?;!1JXJR~a(d%~$t*G@d1Zt|)AYR?eSX~< z2QJ$k&XBR?{ON$-)j1oJ{`M|8>j~{;s8=M>9>@D`(e5xhV0+|TfjcG}!-Nq(D*4VdCR>Gza^`_?i5H1keS4)@kZAdMZIjp# znxRfL@w0d8jcySz!(nNM&@?GWnHJvDGefVQ5obubxTcx)LT3l5NY9|Pxur&(qp-ZI zM3<3DZaVY&M*@lYRGaCyC)V@g>{1dR^tavzNe?e=S3Z=6FIZRM_oxyT+_!sbA@M<> zzhC)%NDnZSU}t&RChiSft|s*G5T`~C{wBtH#bh@&f2fZ&!sR%KeQ>n!+otK7wJ7wd zA*E4lAvF6va&DL}OK|NAHvxRI3pWlS36Fd$&=6CyGIYUTE^JqI(iwsu%h&`hWL9*Z z_RrkpBP(WtDqqU5EgLOe1&0H>J#^r=aW1@h zsr~a92bYZWq}{=Z+-3-EW-Osm=WrEzGK4EJVXw2X2#p6zb=xg~Wj?xtvAr^{JMX2g zGSEj$%P_~CgN9z2#pybXj%ibUl|x%L?M=#KKJa}p=G9WvkCpSIuBjqX9KCqc#=1&x zO)vB}-Ekl6&(p0|+4M@zadTdk>v1p((Ue=*lHx~lUX+&fT8ZgZ`{Pk}wFcR7>dzw4 z;k$_!M~h+|tyU;#i2&5nu|8&^EvX6Jkt`l!Q&L;LGaD-a=4rIuCD1;6jcbcP^Dd0B zN2)s2^x|=y%G^6GeA|%MMGrO{1_oSqJ|s9HC8XQhd5Qco9L(`;gYrF1IQ06!k@Z&2 zec)b+*^4{Wvlnu5eHV{B#P`b{%OJPm;V8xe4^g$;_hOVEzuZypfQL9PQ^NU879L*r-e22i~ zOAP__Dz#|V*J_!u6{n3!wm)?8ZSt&mII5Litf@K*Z@*vT;j&*N47FQhqJFyD0pAx0UXnGhs`o0%d53x}D20kJ*$-Lf z-{C)blRUTi*WnA{fH|!bcJ2BqrScI(b`<>8&Q7`8pxpMI-Iwc^w%4kY z`RB;c7yNhLD`h*nl0(nmkE^iNsEogi&%W%d?!d<-nrcvSbiI=@rS?ODlvT!409E-= z6k~YGv_|TyeV%|Fj(l1SE0s)tEp3DfGdn1xN<5wOnsk@r9`N*XYjOY=Z%!95n_Zrn z(dEX`xuWxsj=I`fnX$@gl?x+DBs}u(@SQ3+?x{);y*9C+B@s!&<~gZtIGgfY%Tert*8jr^$038S5=4bvNcaD1f2@rt-7`(E+@{qD)u=oeh{NiTgSwuy50( z_|>6VL71{mib|S#dUmFbTboCxKDntO0VZ*?2)j~hoLLF9YPK8uc0#TO@AxQ?eQ@N* z_jPJqw%2Q9G@*@c5nLs4kIlYv$?vqK434&^xbAf&GskqK9J}U4tI*$b3a&hNui+nk zD_SCPbEarQvI6gTzg_)KgMzvM<*|MM=-`!)O#iKHoII>_8<$n@6uSAocR>saO1*W} zrUN0$Gj%gfygXbG%)yFPTf4UE%6adWSu_7e_xREdyw8?s&ni_{D%M|U*iW_<)#bv1 zUwguF&zeh}QGWQMvLBt7^!6JTl##Q-T6}1q=eZ8eeh6jiROj^8BW>EuL^8{t!&O_K zvtNPm1us*sD(M$uf6f{g^t@-7jMpHcGN)a&FcR=4;5cYJXLFNo0+*8WM9a=swe0+b zc}o;34k4HOqeSIfrs=ms!+|4Zv8@)1Cm0x%IvvaS+j5~GP;J|*(HoP?P zc)QagtdQz%MZEjMTo+b6M#q6JnuikIH7S1AgWJLF_H@ge~8eUM{ zodvgu&4Ld=ec+K7w8y_2P)&s~-xCrQpX#Q@Mrx8M5n1uc-tr5U#1iC5z8(4)wDSB> zB{eW>qfGKkk$AokL)(XuUY4I!$`wlGan8%+z&DP(SZ#D;-D%XP-tmmQwaUva9mJ(a z_cb|!#e7YzA7#(1^vkBpb&IXM&v4fBO#@1J5OlFWair`KPB9G`+1noP-Lft6tOeHP zzM!i%uIo)3MBm%}fdqLt!>}AOXiZ@8+tuK#9Q~=OR6{26tJrwh*g>l3r#%jI?QsQX zswW?ulV4AR?Rax~iuv+9rUDM%T3SZt>h`^t_^GH&p9Vs_uDvKXTzHZ9iGOc0} zy8&jIm3(-*4Lzg_&dCvpf#H4z1l{z%1)W9FJ00*1O;z|*z_W3P+w`H_@Lo|*FQI6hxyUXsOQGqTmiT_iyz^-rqdcfwnHt#K6yc?_%h z;@TTZ)28ptx-Jth?nCn*mWeA@S@VMO)i{WNJ0~IKs@+^ok(i$rCbOD#zj~&MzJ`H| z32C>vFI>)65B!2RrzRk&=;RDcgE^KYs06wpeeu{Ytd0sC36 zaakinf{J6Q)v0v*5i&bl29{3>qZw8DMrtb;`l<RxTVz_Xrh^n7TS`_$09kVblOz>e+Y4$8Ie27`0b%nyMMwePT zBY1Y=MRZBB{*zyg)ZH&6%M{Y`0*Zu!)%dJ#$p-sjN%DNXl}s4AA4BHb?e$odRAY6R z8u@#PLDc*44)2N>+nDybSk(h22PoJf-r6r(Bu3aS5zkzKx)+fxBUae?mUX^1%CS}p zE`$WvA0MD(iLK;z`CWeh6O>|BovsDPA!48*yX!I8?RDP2IOafO!8IBDCC4I@aYzNZ z76i&4e|-~>V+F8nqw^e)s=(>R2o3)C2kiwu*OZ^L8zxeK8c&=I%f}xMEB`UGPT*<}v z&vOep_3tH(sO?-yI&xR?!O_t=IYkJKi70mPC^pInIJV8nm3(PKIvayt75e@MMp}wIWVB*D^p0POsCT zU7wT!(@2*Pn0h&hpqb~C&zAFb)6XKmc)(%CW1RlVl06!E?oX=59IN zU`|O_ad=SG=h^h@J}UT;ZD@{##@Sxp^ccs`ThCCE#C%Mvul!hXZAvkd@>6rJm9_;h zC@u0{KEaIB!onhShfgz0<;zYJ!Zkjl3A}IJ4;*I14Nk2^Sv8jnDgK!Az0aOY$$ST} zLBzxBh#7tDSv$?46QjrK<3-JiOgpQ+Gl}}614XgIikJPNb6t}S)J(*D^{`RKoEX=! z{@jC_6!XV+2PS6@hHIf5_Ot-LtDnu2%5{W}H8KelNb4}6*GcD4$6Z_~?3(tFNT<-h z#<_N8j9pjT%V-00DsR@O z?UT^*cT)Ph18U-CoJprfeKLzfByz?O2Ig`~_Z=**M z13rW-B%^8;X~DHPw%n|b zol+^Nlwa%>D5DYf=|_q#&cVm<#H12!$rOoN)apTFksCe5e6ZgniJW)NEt>5Ibcw^F zW}Lmow^F{%ZcZ(Zwe5~)qP`r}P>%$U*N`mcz~>i;OVkF%7(~;Q6CQB82K5hRIL6(T zx+S|hygr+4m>%GoietOAday9=Oyxjh)68Yjcia6-5@;gHSdy6Gh{LcL;KhrsI4-gbc{tY1}?F|-sIy}KJp`rZPWa%+trdc2*rlq>mE@$_dJ z-<`1kOf6kb_o_R|k~&invz>rl{BC~CmAk}WduJNb$A@=OX@jSKgC2Xy*ye$_)v87T-#%^u+3R{k3`dHM~SIx;vtW# z8`zPdL@KpLJe|rc*WR_Fv}#ySH(~HMPfJ2z@QQ$F}6hetq$Et zk0hxG!&jLEsrlawU;E8H^MTr$RaJTiuo10608LYtL*;yPz$c4O93hKdVLl|2@q&OL zESIYX$6(D9&e_Bwvxz-Ahk#?nO4}ta*c2;!OJI#N!T_+7PPP9i?h<<7@*A0#xM@eaSzJ}M{ols9zj&kTGLb+W@;@My{ZNHJbnK>-M{5=cgn z_Kpi{q=)rG_DG(@^@CJdNblHt10n2*nT^3!zE2L0V-qz6OrOiQZQ`w&xBR6gv7!XN zqa9S|yq*M;`cg6V8=fQ`#PBsmy5%aVKnL4qs1+`{gG7#7n zCTjfvQOpe>k=|gAn8sT)PIzOzQLxi<)0K126+o;~T{CB6Eq#zhYt*68{t5%b8Whoo z*j}0X88X0_zub{)}|O1;}O zRj2=b_)~q=mOUh>(&~}YYlq_=Rhnbji+JPG1tn(7cw~8l@S53hk$D{ZtTsx&r^3xP z*VRro{-3cuCWfz)&lB^TsPzc>LEj{o(nMs zm=*<&H;n$4$$q(Ep5z`0XUCFUZq{OWGd&2_c;n_u0+ee;e~!WiW_<=K{yplB^6Rs_ z*UG_X^ltn+5yUubRyRf;LfSn;r{rUqs_Dt!L-%@wo z=xOa`Z>`sav#T>^saA5iHUzjveUa2o#i>^gZ6>+_Ot@ZqJN$sjWk-o`X0S>{t#X{5 zaUd&2i*2H-N#Z6ZXJS0Q-OGTxLYC!VuJXp3Uj&rV4wab-4Xc!SCp9sk{3a@o$a*cu z*^(6406m*=8yweeXu1W>H#z&7W6^I2ByM3qA~(DWpC_M#4=z5pa~jN*WX~NAMolm) z^QR%)+p-&a`O9>4VaQO0wk1FQ9eTGXNF!N6k9(w78!q_&BQZ1BKFEeR?p# z!D9UL=z0H${w|oI{PK}Hw!_|DYmpgG?T}Swfx#U@=Qmyv_sj}#|2|5#nAs#@h22Ok5QR7C69i?Zm2 zwiB?EZxwW)ls;5u^$H`qy#{WAE?AY7a~S(#t0p>NYER~#GiwFQy{|tT3wlOtTF@IK zqFL^0#8SCFOT#W4GN3cO{Wzq%=98E9G5aqdRO3; zt`{V)-~4EYVXKhJh5Qn8ywpHd_|~Se?CfGB(ngl7p6bDOPM$Xmx7byChy8LatlI!7 z&Y94=-50CS#q7AKG?E%oLTzf=vg>DzN&GUutVzJROf^!JH}42cx~anmVh{Nsm~!!w`f`%W~SrfUkaH`>Vp zw6HlT5o51w_tm01)`DQ?G5Ni4Q%VQOMl!K(Dzh2jh@7^6jY!v(2D$0?*3B^Zk`5J_ zd&`~mEc0855&sqEAx=q>CQTW2AnUX37a9pUnNeZ|tmHjE^|42Og{RTqpHn$gzrp&4 zH92)?>?WEgI0 zTHb7jiEN^z%e=%bJ25ESKop3|=4}u2ZB9|r>CJ)&*ic^S8F6dhvHQ>e1pb=;Xf+hC zf5(C}Qy5-aj5nGM+^kVAJtCzQ`^0vyhnShZTY1W%IBf0LP1ctB#UuFX(aYO?g1~$M zW-HC}chbVl*-DRPm+R4`s8B}tV*0oBxpB&ACd9^tlL%%TleApGbx~mSH+K>%a%z$S%g8ZaM7W&l0leus-bf zYqqiG)fz}w{(;l+P>4XyKPXCIUUy+pU>C?N12Kt~SuJD*f4mj+Kg!5C<)@%&hqv>! zK9*BDdNqb_k=#yFKv!#pX$co3=RDmILdI4uWdgxeK<{#q0Ja3Q?s|8Z8OXXOf;dyR z%}&{enXS039j%VNeR$-pcq$e~@{^<~9<4c{yORd>E)!}{H3Iu8S@d`mKfZt3e=(UJ z<%k^nY!nMVSZ92=;7)vL=$7yS66bCZR(P*~f_>?vz>W9)muHNU$@VneW z>bdLYCehL}ek1wXnheyU@{Qa>=3hvrBzAGql9?5OXnLfaoT5HK+I3tZ^2ywI3>V$d zO-$U|aDqEwX8uXE^stMpq-nax%>w-$TlFzEzJw~T=HU!wW6=a;-V@wHHzZ-5XRk75RcDl%^?x`$y5r`r+gzkBxE8bgcF06C2~9a)j{%0$LMsb4^eJ%i68|jkV3leD=-Xm`cZ!vz1NM>!s zb8_6mR%;=TXJ>2N=(@8|rO%(;8&0{gru{Xkw-X21v5VGRQK>b4b!j(kZgf1W`T?p@z>z`Ms&ou*+|k zq7eH~htPc}a}H^?*!e+{xxaaUdBC@}b*xCET)zwM{h03Ej}T@D90yYOxBECJ6Ky&- zcY2xBZfNV*!EWe14?1yd zGjv9Tw)HIKXHDXo-_2{5tG1f^a%qaCg6qf;j$U2diJ;No@j%rO^`g%-igo+h#vS>@ zN-HBKAF>Oey|IOE)kk^SWkL(}bX%0RpDXtkaYK!M*C(Vip*o2Egyce z_q`wLpNL9sZR?t|1?{+E1t>JwC3O!zCl8S!3{y(H>y+z>_@Z%d#;#Rg{&vOVCzQeX z9drbf{)AW_r13FnCkmdg`GfvpPkuKE|ECZI+w4kecVay0vmmq}9u+v|Nhydx2SEyU zpS-I(Bh>(*YG#G=OFn&Z(Q96xnA6j})SDCUz2rcOzw{DSq*A14lfQk=`;YvcLx}x> zKE3wvo@^J?TY&g@wsZh0 zy`0fzj}|k$gU?azYWzR=2wJX5yW0`4D7+_mqk6%&B^Aq*=`}yF6!}}F59uBpvA%1_ zX4_{wkkRsDF6kK)urqK#4fC9Vj)`y}b6yMe3H1@Nkkun<6f_TWa+XPW1ABwXV%6=P z8&%((%PKerxgSJlHYUw=+iZt-zu0t>SMw}ElUe(CYna1F8=Q;8bK~c5RBfD8{9aqqC4f9!XT!Jw{FJwDl0cSd!FKLfqv-2qc79pd zK8|RB?4p#BQ}q``US#qNP%!Ce>bgFe+_Prk^Xl;z zPMIR`mng&|)a#DRd|Tq+f@ZbO;w>JVyjA^%2>u|n-dq=75HQR^>0)E%QV#$XI--X! zmR@9wrmtY;+`gc;axzuL7xXAyMPYEaWgWQ*x2!{f8j3oP%YyL|NT_v_#Xz`LL$Rd^ zKqv7TY&t?YVx$g>^Dc_zxKu-2v%|@*YVTS0M`X~GEBXR&U#YF!mD;|*`=hi^pqvJO zSMvpQcyB#tovzBR%06CPQ`G7G@jIFP<_7%YGpwF^b2PKugx73T?d6!)d2H1R3Uqo| zjd`6_@_LoxdFyJ6F>J!=`MKp%@#-#jL@WiueJ@|H3LsTLXX{vRRF*}eb8LRDD|AY^PcW>#!Ui%NX6JVx+{|J{|eM zoB!-4x4K@9a+h4FJo2LDU^s%Tw|hr^+qP6HCG3?LnT1*bA5@?391{tjKk& z+;u*_8kPGk%skp?G_z71qjM|yD-vKIE>omDyNtzx?bqojyepsbi208j>;qrhh3+4_ zh{s&ZdUnJ~Iq7MQT|#@}%fVm%Q~rk^4&nHH_FP`0U6^Hox#L5ZWBWa59a;dwy*M~0 z{!{)ldU)=v_9zo0{xVGTWrt`#FFvij@5V2fjRkyfZAp*Nm8iC$)tREsP>3fY8k{pu z?Ye7b(-Z=0h@j)DTkBck-^qHAm~_&zT{c8pE-i00*rmf!O^-Fub^VP3!%?v}Rw^`uH!c($};) zWx8~l_{!0n^I<7c<+)F>vKf1R6pbY}v&pQgi;C&B@<g3%N>ma z&i9VTst&_^maqx1)4{Y1QXC!Oqw-V$mv#_vJpVfa))pk@9dz@Yr3_>?jHoyO>WF>! z^v=pNHizAL8vV^Cw3@EZ6O3cPuN}U;w!gW=ji5h{r={@gm3cqD{YL>c?p`V2t32^i zgC3sd)eisro%T8J$+x@tyo?7)S5uq2?sMp^u^hEb?(89<^q}!r(3^X zr2H|K--I_2c8pfTW)|EyfAmkNc*mBzBr!9#2<*7_&}s|mlQLL)>Yn^t%q3g4$raIK z{?sf{KY6ruqH#IpMJ2_g;9kom_FqxbTr=?dl_>Se!u>q4Z&9f;mmY?B*)>1z4-`{t z?7_APl2V5W-Sr0}rBiha=i-igh?_JGlHW;@FTa?yGn)z5+Tdo;otr5~%EiMP`lCRL+`D2@AwhRV6|7kDXwB)$_W1r1mr_WXE41bn=*pMg?UW;wo zF&I$gS}4ODoim5s%8hWSHUkxTD7`fQHaqxhX<$C6G1uWulgcvRUi09{q$f;ll+h*L zh1;}@EhA~y%=~vVzQeQZ>cC<`wV$xYh$eVf6SU<;osh#^)B~o0Be*!(-BrjG80{-w zEQNniJ`pe=su?5s|Jr*Gpr*FBZ#0U6pooG<2NmgEkX}S7O7BIQ(tGbEs0c`tBUK0x z>AiPIKza$iw}eh;2_-=2-01QA=RGs`n|Z%G-<`ST+nKD{GdnA5t*89f)Art)Uwg=r zdGz8}LG0S9B0HE*YK`$cb~12(pLq&)N|rtjP~I(Rizq)2cYP4te!1f&Os2FIBVSqw7bnvn(46%JE`(Q1uP+m!mnSFP-9 zWhHSp62*MuJ8}uUmyh#{L{#}ld3(6WmSujJrED;v{If}?MR7F6@6leGMbFJX(M~I= zT-IrddH1T0ct6d!SdH@qA~tdIQt{43Fi{DRG@hYuS=rMhld8UXT2imBME!?zFP0ea zPVf^>z|)Tbe-Zt*l1Tt;C#_F)x3_d2_YA*}`<^c%Q@i*6hglSBV64TMtV8{h0%2zR z{p1(&A3JAf$1CQ!%5OuP?BKDcBK>{f2ztAR3j&(Mb~50Se(fGykp+>+5^Cd~Icf9R zOoCq_n5Wd_zy%>R;kfn1HGlPxCA!(5;%H;KZGO$h+-O=+q5!P%qkDYKKS$$bk?2r) zs@rIgOy*-zlwz$X?~%R?1^X>QZ{KEpc1>#A$+k$2^I*K1_jYODRK-o`-UsYLhGrcn zXtjBG#%U+ewJ(t*Z{>@ClG2y<%HL5RZ(olxSf=l>z^PMglBP!#tUM^lzVKtgjq(C5eU+Sx1j zNiQlU?o++T8(Yk}bFdrL1Rv^tp{bfb;PuVl)-l0{xX9@I`N$gU)%%5>dpv3>SY=$$ zqdC?nXKcBBv>N}-{Mt?7y(-H>Hso`nsgU8w_V37oyp;hhcaZZ%T#qb6?)rO2eM2rC zPA{*mcqn0K%aO71QA;PGU#UHmL$hJ~@MxCe?q8~uPv>V_5jG5l+)T!+$eG^COP8W) z-eSwOLnF6lP&-8r=6U1USomw5&(px9(ilJV5M{RG47}LhNN|y6cuSLN9aHgu{6pUj^vgNoJ(P$8{TF~+KA(l5< zbAhE5ZFkDXuX-bX&M=mWI;pzj=C_ZH z$ybJ2%1?kXi@d1Ih-q6TXo9-z{!1q)1VK~`q_>=fWhDf-NOjaXQZEKKS1(VoW&3wF zi7q|}h_F6ytBBQ?$z7i$LnN21B>Rw6vn@g z^M2YsD%SlNv9jMpJO1Q^eGdyU(eGs4AP}BSgeI_Eq=xYdTXhs#-`-RQ?cz&kpBsqP zNu{_r-Xp42@2AS_)2aNjW>@m?wzCE&WlC(i#rJju;V%`D-Yfrc3;>wk?CAQmga`OC zJwn~}Gn8f{M1Y?xN>unM$5$|Cg4w2o^J8T!hvHfq7uHs`h>^KFlQVHLbefsQ5U%;- zcq^kcpy7a6zH4qrDz8BwbYfi0s&#xK0zU(Zh|+#CE_%DyJHLvD4Uu=rVH9{vCFD6m z^1YKBwkJdyn4M^}X;^*`I42g&8>CI zb;>Uo8$ckR5CYn9uMiLw_MYE?lCG%b@hT`E-}q7y$78#OaiPe^(OA&_GSM*p6|$3U z#Jv`QGQC}^f0&_iJjHgIV=6K#eEN|_tooUp^^%g1_~}p7PQ}uAg!Wx&S~{$dDiC-y zQ1dcamm~S4K|fHf|6*X2{cYvZMk&k7k4xhcJ^1fyU|X4-`9M8+2>FcbPL3}LEEtb~ zyUd>}K@6x-krBi={F*=3aeYy)>%}m2o&+H48ZM-SbA(^8|FTtBB>`D7HA92d8lTRB z-uNeU-ip!ymR*3duSW?kiWJ0|jc0@oe%1Uu!{rw>vG(&Oh7!qt$$h1L&@($Om)|&* ze9Xa=R@4Z+SOTtyz`bL4+`Nni$Fk@IBLX04x#Q!d3Wf#ZQ%$h}FsOOZ{bW1}M<*+E zc{mP8PdjHTrPXSl&gDeAbQLSk#F>Q-NKCa)qbc>c60FUsS=2>ju-0u7|l2HvL;Jz22#=5JcBbF_8rsU(s zDN9815k_WVeb>tc%NH%;EY0PI(i~2BI!67D6k%^Dxlxz=6Kw_|xF^s2Fk@dQ_93m9y=z)lA8e>+161v@OM5t=9@#X)cb- z8k}E*sIn1$Op0a*p~|UTyF`c>umV`G<@4D?sL*!cB#3OqH{YlCw&ZMZLhqM};|3^F zn>DhAGNtltW?Ujr!`nnCiu3DNEJXK6^ak4ljc675^r6S=y$CFXmX2D#$uaQRwG#An zfP=G2Rq)G2KVuW2;baY}#-W>FOx&RcOZOmyR{w@&R9OOhsR{6WCB{p2GPz^mEFrrb z+som};BO;9mTUwTzqY$K)mS=|;F=1sEdA^O;P6U^4x+<#V=2Nc=o z;j|)&R(Ewz=uO%Ei)+JD!pr(fwzKKUNoKBYll`&+FTw}UmIPq)%Nn}|$hwYG69T|r zq|ncWejzqO61QCQ$o2OJ*!Kr&2=`I7gY=F|CdSQ}24i+K)!X;2WKNGUhc6{$<`#Bj!Crp#f14Kv&QO)hi3M8Gz$V+Q zU|ES!$;DyTvNiqB9#TQ4{jjq<;Ci;?T-|!6=1*9B#`9|`3=|h{7EOx6Ylc*%)940t zMLq1hiZ@b36H3RrSPgT*qz-$g<6xx^(Gk~EbejC@N7&vCcZJeKk>vpm5xKxh@Z(PZ z{z|!zWb`oO#5W9*u>HdU!BqEW6%PWk&sA@~`uaLiJAzIETDRIGilJ;s&mSDjFc`IO zjZPI^rZxMj@;Q-Xo=ckf`ZG;=>!Ie4XQ0v@h4EM5j}wY;52L!Ku&eDwR4q&Qj(ugg z;XeAX;!!9UHpIqhTR`Q*4Bm^GUw{Ju;bSQqzM znOTf{(-~!p&7qCukR)||(-;=Gm@Ab8fb85Rk0e{WV>}B*Zr(GBJZWIzicn=BFXWDu zB(2!hY>ELAQRz1f)QFkr56~xY`$7AbKfhS6e7?qa=R#;4Yji0c{|>UZ*d-fVo8!>b zyal$gqgzBT?~potD{-?UaD4Iu{37h~EUXk%2(}n&f)$AD%XUYi7VOsw$$<45Jn_X_ zC$_3Pcc)(= zJrPp}FBmc)Ko5YkOczpA<~&1-3#ECDx|j`XK1UYG2+ql|7?q_*pauk0cZ$9Ao0{~KbNr5r zls1GrMyu03&v>|fus+f?GkdYUK1$F-SZ>=`VT#wedCd&+rXm8>OlgaCM+pZjZ7f#W zG&m(x_``iCN6!XoxCLAAQxBX$r25aEO_PFl-BazIbb~Q&EjZ7@jJf%E|6<^<>2Gk|I)BKqH0NGE8T>Tj*fJ$!Wa`B00oS;T7038tstc|sT#D|g{?0Z zUmcO%KT(*r)y1YreYYNaMESlrMoh)UF4W>dpk|cy55_Ti052{t5l#YlME-lf{t(wr zUE@~byNJfPbGb#UXIPAVyC-Dgq$)M(4AQH7Z~;3bCFXyQiGyV$h&(DX4Is{qv)`2M zVrvWOBpk@|K_Q-R){Bp_4a6wVI5QN@Z_n~0O?GIn*yATYO_pBZxA%L(d1s~A{0q`7;(U6 zz;cB;^jJAFuzpE+^xA2v(6cIQIi+QKhlR_9r7K%p0#W)d9W7{wh3f7oy7_x?=a?Op z_R}kt;2rbkd^EapmHt&=@#~6@K7H;n#I~}Or?qo#*!p}#&m{}l!ig=6H3wl|eL>O1 z7IQ~verMs9tKQ+#I1xo4nj~8Ad*^VZxit#H_kzKSU3acMaPzw}JVIM=(cZ|bb~8sk z5-gS#b7lejUToSjR%}B<{}?p#-ET)y;p?k-o@mlP)vsv}H)@>b5Ru$e`Rd-!-_kA6p9_ z?i+g}q*U>xh9tqe{*v$1khQ@E)oW}%IU) ztG6T0*gA=0(N1gY&bNHaZ96yu6G`T1=sS|W4q02wO}&wNN3F}*HSfNymAYF%u_$O{ zZ?KcIajCz^#35&$R&?8atXFStWWdJ^={_SuQEQF6r$bU-=q026o)y_6Wv?wbca1Yh zn`iTLbn225djfw{CPQ7hCWqN)<^lJ%Op>`vZVqFp-s-*fVVA(l_oN-i9T&FKP$l(* zLU1$r{ywR1gi=(G!KgE6M8V=OWgEiI?i_(ROdp4d=nVr|YUYhBJ4?sL%9Ey-=}P%} z)kCu#T$pCwgVD%H^=F3`!HF1Li;{ zD-lb|O@nX`3C&o~DbAxk-!vWFW_FkS{dZ?7M_kw#V&-lV!#ZeceHVL$p5q3M(;$9J z6bTYoN~+2$7-q6(>#XuIqZx*_Gjw36D@D6VRxaL$4;=(c`TCCHoqn_}mth3p`WC)TQ#a_ZAasWFwP_7h&{fs%)VDYFC=My@8HPr zG)JqGJ*FOcbc9F2+#O3?Gq+_YGx#IGk)1Lk`y)^spWxIP|G8O-UV+MvW`*z1Txob}wU5 zqrSG=07J^7NyUCm<6SC)V}&qb%}kl{&114VKQDcqGyZc^PAA4kzpS zd8as`BLyYqlp=ZhZWI93t5!EnvB|yx9}1Blu-YYi*MbzcfQ}LFV+gd}8uS^9vU|4@ z8Kml0;G@-+<(MW5n{DVb1v;k;y`fHzPW_CEmm_?CRbI3(XUg=JE6#{H9`eZofK2N@j4S&5+e^!L%rmZad zQ*g0Q-5z>^lh|4Uu%IgaFSaP=X@7D_M#n?fOU^ZtbUBQ9>{d^z$`Wn9eD(k2_$5EF zGPU$1QgfskG?7*;Em%xEAdQ8V1|%0&?6{J+l|f5q{fvWKZ!EWnG?p5{U;WS9<5C>r zbVg;`Hz{uSKzOly+au{< zv69nd$A0t;3!~z&ZtqnU@5&c<{pzXV(FlXIBL3)fnuZ1|Y;}tWVJA`Wj!yvm6Uy&9 z)@VRmS}^-Dm}NJpAeL@AE+IG4Ug382veTNq6)K13379f{!{W56EFO7Sq06CQK}0^3 zdTi4`*!fLo$QSVQisWxgLF<=fe-J*G;vk=2`wLfz6i)YdC6b4h_QvT?mpAvB;vBEd z+P(kyQIuUF^i$p=_^*SG{gXn(lZW_vdXqYTIm+Hs+wa;+07q9^(Xr|9H0_=NWzcvZV0(tgRsLN&4*TmsydO$f<^>c`s`p9DsN-rFs$+ z*Hx=g*H6hZq&B`19ppabkiWvm;e&#C_zDJ_qSmK5AmS{;WWe=`Lsn>*cS2y~*_hza z@ML_7eTr#ZZ_nW4pWJ!ilITU$wvd}rW|SH6@~p5y9mxCn=r})#Md8Ag;oz>ais|r`}a;}sCVdOGipbA zTdMP~p6@yr+zA+1cqpP(8=M|wGL(YyQj5OjB%~)Fdd;Y=OUczG131uHn0KyElxoft zcV(|28+n^IVh5$YD|S#2+l03va>L=%x8q68f zJ=teeM7Mn!*3dJPpS)3e!xkyMb?xq-$BKMmF zNpkjAb5zqLPJBMIeD}}U*?~x>s7Jnu^Qp7QQ~t{c@ZL*R3;aAqShr|qBj@U(CE#LZPWtR0hK8H+~chL)U3wv$%||zApLo+lauI#$EO~ow5pIc zl1U}N07-YA0yUC=!zDUPDd`xyCa=l__|T)2F{#yi53daLa!N|FyLQ z&ENT(h(D1P+RyBez^2jKJC}8%*s~WBGOH`7?Tt-7NmK-CQi-X`gw1R7$V^R>P!kYj zdxpX+*j*Zo5*l^gOZ&WQuIXXSmN?|oKgQ#p=AYac3d$|S-Wunv&IIGC%Ag*sGw^21 z5V*u6>VJm&<#+Ek{uD|PJ{|=e)BdIs#UCn}eDFLC9T`?(fLcYBjqarHWN(2i=*-E4 zUtoZz1h(=FEIar2`6>oI&(^)%nkO#dx$27Lgde1XJMJZ)Rr~C2$b9cznGC4k&>QFb zocl<896f5X1F?Bpf*I|WL~jR#o>hsTmR5o^tUL){t<;H z>F6J}A#WReH@na*RTZ`6)s@t8H1ZyUJ`!idnLc?Iujh-r_#Li0;BDXX5`r4ynmyE{ z;tQ0nsxC}8&WBa=xgyiq!XlphUE1kDEfK4EzHGy6!wzwK`X6B*`tbzrm-&2PZq>pp zXo3al0thDD-HCADXSNNd^ruw;U2$$l46>@uyJZSBwhw*TtM44TkNH49vx#2qU1m?R zu7a^Vc=$F1mPdmXcgtUT@mhgoljV|rKqWxZ5a1_c)7#iCcz|5$k4>v9<7>yI|E%iT zFQK}8{70zf0l!Szt9yUz~KW-pzdH{QbsVMx^uXp$ zk!umX7Af5dR}NRlHANRXkMk}%hvoR>_(IC@T~7CW34IA0!bIQ9^x&YMEjgds#`?dm z_qrfS8TfiPxzf$qcCzHJO}FhtMG0|VBMBo$Wq$I249^Oc7{@2RL>Jyc!fkp7=AFZN z7@fypcOdn&a4-zFXu9j$x3D~IoUQ$gh(4cwb5gShvn>+?h|cuTqa*Ps2(dr)IyZ9_ zy}#5yiz4`?(Ia)>^d6axzO>amyvcEtS|#^z=q7>*(vsTL<|M-zTK&JYIie z|H&Q?b$w_weW20j9C-nw3S!tXY0BI(^H!`2>>25?rL?+-t0(Ktjx9>fh@bhX8;yuf z$R-lvYnc!ITO(hH{}x~NKb&pt1^1jzb_Q-3>HQWSp>XZ|-BgKz&~Tw))=NWA%Mx+V68~hh1E2hg;;FM+Rg{ z=2-Z@u!gx6Idm0-oAq;?-hBdffn~wMUKtx^Q&v9FYNBRfRf)Z)4B!pCC}{J=EvBqam=0V54HoeO0)Kbao%NsJz%$qQu692v# zh^BYU+qAZQXA;d!=!u`qowqS{!oPhTzkD!-lI(&3+$Ne8JwW7FQ##+gq8oIUxS;!= z@o{;&bKyTiAw~Uw;R6G~pNS)rme4uI;th?{Ev66P9rm>F4s+6RoTBd^ri6*`&e|{e z`BL_`A%KD;MM0-lGe4eXf&n+~WV|(#y16SG;R{KZ651m(3CbZj3b4-bpK!nJJ{#k* zq=`c!NJ|=H0jTGOpWwr~KtdqiKjV|&#&7B21`F4Z4*#_gGW<3KFg6 zj(=M~wrodUxoaTB+IO-Y>0UwE;aNs)3hpmqm=BRqHNBndsKHO+d8NTmj5hfcc<+yx z@c=pq7R3HD4P4~AiR{m%()i+->|}jdX@a_tuEYg7SnjO^Sk4x&)KY;lT1SDgR#1?* z&Yij7EZb)&?BW3+Ud)z#9qbi2*RBdb2-6&{YLR5A4>O=Svp4P^!{7E9G3nPH%scbG zA{8aMeE)|i%@1+8A=5t_>G@mS_+N0cgs8a<@F$}&ArT5ZyH)9ZArQq#K(x}^K6s_h zq``RLY(p1#+e&D(Q9tuR9HZaYEvBg(KK1R?mW2;`4B@QD`y&Z>@`zEH@5ObxTt%SQ zA~6}{=A*6ksFxK-w^p0XWS_YQobHXce&R8b0CO9IU z#m}!!Yr%N(c6d%{86LZ9u(4NOIvrbfSzJD2^19`1U2k%}6km+bnVoUI5;D%Ft%r(E zsBxJM$JX9iH8tkeVB$=zzAldig+^$7i-sGDk?-?;W5?`fp4r{h6U|nD81{A`o^kqS zB;2?2_ItkC-C3BB-gN{`hD~s5t}JVnXGS~rFh*5*Ux*dvg$kO~+#S%gUs$z1bUU7G zb}BBvWrh-N)RYLAscIu&Qh_GXIY8DXMuSVzKJgR=A1-g4hMOPJ(WEO)AUxB9`Ma5A zBV1*K_Kr5mu}}ICqqM@VHW3T3LYfHk3T;#33zWmB1JWagY-*}(${#38+r^^gz`82A zJcX5|iD!G)>&?za1eOjN`_54kbF=}a{zvmf0Kg^vFUp-)-##yvTZa2Lzy~-F&J} zjAZY5*aV9I!J?tfv9MaP-h&!vR(CT_3@bE`jk3xMi}$Md45H|L<}rH%`km#i&+)%^KiImXjf3rTN7{F|&-dmFD;)7cY429fgyE@tr6esZz!?C2c{f8IAN zGs5k?v(p_g!6D9{eE`t!!Kxm4IL-m1it zzhUCZm#EO-ixzF0!nnU*dz|_Gd955sMw&WABCb<+5RLO^0U6-EU!ZOT7tULT6*qn zDnHLumqX6@ZPSPTJ`1lu>sRjzPdCsot535tl!hFabX;5hr>4wczoP_D&=PGc|a~3XuNcoHjf75IJz~n=tqB&1B;wAw!e(6P-J4bGn2noaF0+IMoMJwnmaA3GrttKk9^zw}P1FocE)$`h% za$?Oovj)j3TQB*+pwbw$630dF3zuh6RWMMZI8hBcQ?svds%3Tojp%)Btn)|+S?#fl zIRWul6{?YJbScJ(oTg317USU1g47CXer)f3D`EdX2Hu&Wh zE@*z^-;_zZ{F)DHe6zb6BVUNNhn#l#>^%#PJ&P{LrRjY8c@@^^tB|r}?Xbuhf8d$G z;k<9T!I_Z2zVYs`7FpU@rYvIhL2$-Yl_mWa7eL!3>?U#~U9_+?09_1HJFs&61{NA)*V%&;V`H*yR(>2`Z z)mT_Vl(^U+{k-D59sQ+u`D^V4(Ub3<`~B50dKns$p#n>zsZJB5(|ij%*o3oxWMiAl zWC3N!KHb_g%Z#SD+Mti9%bc)mOv;5+Vx|} zB~1{Bz}cHK9|>SF)gCbk^Wbn5jf$fniz>WX?x`~*EO7otU3E?(aA&Vl%!eA9Y9O2) zfmyX`F2@GeyqgW?)Gm0xyiT27Ewp^6vwM7$LzY77ZiR^V)9xHoc@oAzIAU2+CHJYQ zUVXAf_?&onwk*e_iHVO_3xAXEkzh!zz)nwRXr3Qs`XS)MJmD|T=|8!>syU-mQ)7Nz z3O5EiU){^TOHwuxq5A7hrz#1qd{PA2b0>rI&kX0NO|xU5>pcsRb~G7E0-9v*!G#Q5 zd=Wpk#9ZIWcPL7;FnpbOLRJ6CQ&1}OWN`2*Jq9in>-##ZS6Ebe`O>xE{i1E=cddRu z4p#XI6QRhc$>w^`$hk@Nn!ljMX*{|;?B=871&h?_!gz-V9npRpgXjZK9pRqwx%L3l zXom$Ym2I1)HCr8Fs&`&4TMLmL(c`1bcWgiBg$Nmy6ZL2pAkCa&xeh#N8Jp_`WnsLY z7aqOe{5Uk8N11*u%n#Y&Q61&|`Y=C(eiNJ@6L1-QGI&92Jot@+HD=M;W8Y>0uI{n| zFaqnhXEL>p+fcvFS7)9SyfdJ8s>USNzPW^ZpEd@}v3e>J7vWK{I}RiFu8(`&zny!2 zta8r1W?Ay~W2oY9+LHg9wsG67i1_(|kQ(g%iJJ*DDXIp0_La+fLaI_jpjGMlmdVv6 zFZ8YOzzYu9Jj;|KIaXQNBlPFi68?U}lC(MHRZ~9|h{dfcql1+h{OU;c@M4PMmtSeU z8=NN^xe_^FkudmISX(w*XUpo<^e%T);emJpMe#JM&?Q-vMqX`?xb@F)+Pp0%wR|ZlBqidg%&Lx~xve5_tONFP-H1 zQ=jpt04CvvaupQoq18v0Cp&xgZYwCX92Jn2FE_J)mib_EvTJLLsi`*Vk65#lV%sSGN(aFSC{&s%V37g4j}v#7F7cAfybFeY!bK zN8_o2q7%&XRgU0-&SxT>X^Efe6+Ewrv0FChlXq)YVds532ew2nm;LC@+k2B5O!{Sa zB0z%5Jd3BN@m`F|^ZlaU$8(-rf7$=q6z(|IS(3&V_yme{tS1}z67HSWPDE;OHkW>T zXcI1Ysz^^KPHP_}c#1J*T{{uYhypf<{<1lm@&A>&vwiSOniKKV z(~BoT&WSFT-37xMW3y)J6wB#x$QkFbWNDWuyt<_QXtKL)W^dz88L@cHPKS6%ZD2|* zVXt~xxbhGKk_yrE-OGMHH-Li23}wB-_LtjUjBv*UqTPsfYm-gC7ctSN{d9TlvGym# zU0qHTn%k&vdZbtVi7W=edxRjeVi1U?^z+o3x{(eR8@l^NCnF5=j%xLb^Yhc5R@AuJ zighM~ogULChl93$9tUZfi-4{l2Y?S(9{(ld004|+#?)-L++wPM;`U&8u+q3dBzgWz zzXN-6%q~~rx*vFOP`iS7&L^S1Fs<~(z%-3^P5ko0qVee?ngg1;UJ)m%2P(&+Y188! zeY&O2BV8K@XqATu{1bmClII%#qTjfLw8%enk7AbrzfCQTo`*iZHc5U~M?Tq}+* zYZah|D@5MZVtTN4XzD7)helLiW#`C6L%2tGzQf-#v2;0&40@+mWWClt$bM*zUioQ{ zhSkpXdSA)4l2j`PJh%9?!Q36VYtQDg9C!`-ntWzG^DLv#DKqM_znU=)MsJOKv@dQW zXZW|x8LcCJ>M%h78xc@q99Hhmy-mVUbr<1z&0z=gfG)r*^*zdZjXh4*&lQ~`=y@=3 zF5W>$UVU9q`T#1@JIgH&cA|^C^lW#aNaHHIo3ka>5V+N0b-U>0`ssE1L5H395B&4v zXx@baK_0I7&O*w;LtY=>9)Mb|c`DR@;_;&#bRen9-l-(=sd=~6k$JXpS`;)qi;hO; z?o{6eN(Rmb(^{MwH-32gk&fIaeBJ0cm2*yc9eF3XqTjHHu5m#4bd4<|>hj2Kq9v3B zcd`b{`DR0Ho!NZZ02Ftp|BpBFPx)3Ct3NC|>0JSI3paquC0a)iS{G|I0AfP2tFTRH}p493Tmhx|$j0QoL@Xy;Q?udbPZftjnCV#}3noQSosq=!%xw!Va0$|F|2lBT8E9{Hc-8j!uq>FC3qJ$JbrRd?6}`2z<)<;4dS_5xJs z^&7@y%Rbw7IQJl837~_u&ra1RXDbZJHHU$+HH+qkDMU#CfGa|rweh>U5!ZH26JIph zQD8RUz`N@uD>EQ3osF*8Xr_%Ks6JyBgQ{R!R^^&b)L+Ker@b@K$p%7`wk*-#x~Pg8 z+{{uv_gQ6AT6!CWCTy}JHQK35AbtT*o!(m(MT_Bnlas0z9ZY5K;V;*+VK#<&NX2!a;nlsFm5m$vK5FCYnjby!&t6INpTX8%j0FS)=-&((UtI z9E%lBAY`!87qkQDZBsd{w8Y#G9D8=>ad&P5KH&13xc0z%mJP}2U*KF3G2InbdU@a& z9Gl81sRAL+^@1V2D!j#h`3|_VGG8%_K*H;u2i30LbuKk=uisGA;yeqtng@!C6t&I` zjc!#B6&2&FoK<8CvhH2zgXolJOTM_Y0HtDHQgl-%Lj`XFt| zW~G$cmf8APS;?ydM+81YuFAvU*jz8n_?fT=A4iJev6cu+H~ufm&*N&2{-*rT=z~B` z^T&rja2eScyx$qwab?9seL&~pH9Q0SlH}e*V;#c!0_miVd&f74-fG$ElM(h5eL4l) zEXK#nq*nCPzj1i;^Y@NO>Eb)~&vkS(D@hI6pZ0xeoRw<*crDt;g$s4I|pjel|g{fRP z5_sl^t#1T-r8Z+$&%itW(>{xyj91%`vQz)blSZ&^Gu2s@)h;5Cqu6RE&|0jr0X{6X z2RsZUnMaF3eM9hACR0cRwFDtbB`x@x{(AENK9h#iUsY=?UF6aWHjz;4&mMSj)b?x+ zm?>Ln_Jkz%JkJ9yRaRTH!B(ipl=r4*Di$qVi3KaWSZI1`-(h-x{bH)G;xK%$mYK$w zdGZ}UY=T2_KH1(o$q?^{@$~Mqdi)pA-<>}A{{9!h;qOj>&3Iy-SUbQN$FFXHJl$Wj zx~5k8|Lu4#77_k!}AxWIhc|Dwd}o*j65a` z)YR}J5BuG2*hWeEaL#IZOIvA9BR9hKt>em4@kU8oX(v7?Ayh86@hgOuX70$t`rt4f zO|gvEubAjK;O^P{ecj2Jnm%b`J1Kd6zDZbof9>7iSWpuxBCte+0dPgpK`_OR3)Yf5 zR1QX`c~_JzcdmZL#zMQm1GR?DBa%YXe%sUju4D1NzjO>eJ_K}zt)24J54?*Rb+SC2;*x3z|y4$E+w znRD&8W{&YW-~k?V{8ZzJeQ_)9C2On(4E=a>-Z*;{MSpSZ1Khv3zRJ+b-S#7dZ0% zuQ&n^@MB7x4uAe{ZvEdUdH>6~HT1u^_5UJnE#SDN`UeTim46}O3mr2uxpxobx|#K( z5I2|D`lH<&5ZO}nCV?j>_zOk{i#YVFDPPl&F#;8`l`vAbL l8;=U7+pPu5lbL`kKRo}N|M($N^1u1ZM@!ef^?l9wKLBocl0N_d literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_z.exr b/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_Exact_z.exr new file mode 100644 index 0000000000000000000000000000000000000000..becfe91f8ab2af07cb741c9964caee631256c7dc GIT binary patch literal 286010 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Ȱ}#92jAeSyG@ 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 literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_off.exr b/scripts/addons/cam/tests/test_data/waterline/temp_cam/waterline_Waterline_Internal_off.exr new file mode 100644 index 0000000000000000000000000000000000000000..c40b38b76fcae954746ac0c73aa8e23ec257b710 GIT binary patch literal 458922 zcmeFY1yEdFw=GH%+!8|YAi>=wcnHBI1cE~o+^um>NPrOB9fCWJHQtcm?ykY5aSb$b z8~M(==Rfb&ef8?zs&}gXu4+baskP^vW6Zhc+I!>7#_{Me0s?}enZAvUv6Tb-hn2a5 zBLW|M-&=&+Be$PN4_~SOvPb#9>_ITJwYCEqJ2;rz+We>Aw}U(}(s$HXH@7jebwMz& zb>%cicy>Gf!$;={0>bUr$lSrsO5g2&d7hQIjq!V+kuebA|N3=%6$CqTS7R$N2Rma! zM`e9Sb6W%xD_ea>1jM`Rd=_;u1R5LL+z&5qY~u(Yz?sto0rkO*Tlk+(Q8zboH2aq! zT;CbnIG8)S{iogGZ{Z%^57O~~Uy<&&MN#e%_HdDE^!sfG%zI?RzDMZOdklJck7Gjj z_`>iWr-JX1vg#h6F5Tk>=AGjRaU%Dq=Xj5QGw;!E<{pvB?l~xk*{~`u?sXqU^xl_4uCOM>__&0a|r?vQhj63#!bNAntyEp&l?!PT}O8@5W zzb$v4{>|NgTkh=s&E0=n?mol0i-#{a;cJ9x52fh!^bUJ=bhK&IJm&7UBDoHI#%WyZ z5Jd$#v8czArBbg9zkfzns->I|JOLyNK}LGWO6t%%f^x!Q}vVW z0(B^h(Bpm4%vQFc{_$>Bf=8(d*1DJdrf+7w78V$9Ja1e9LFIpZj3?kLuWLvveiv2A zBZN;D20<%069@?GJY^Dx-!0*$B~DVhwelb!$SYVy2rUsKAmq?iNt4-QAt0#WYEy^R zAR~Ov4`hjyg5M$dHTwUfVfH&c^y-je`Rb4wXR_R3c1M*l0ZB1p0a^f5R@j!`pJAwq zxGadoM`Sbuld}=z4x6K07T$x7MHd~|qrJG)WO5TB2+!)Gq z@R_tR*Dn1gF~zgH*a_*<6YQr^+y&INPKou8sMuYU*?mvyc!)2K4U>ZDRFzFJAsjDE zYvdKv!@5^$Ol)%vc2Zf}rYRvUb`Zw8Uk|*CB z1&c?eO5RcpqN~`#zH~1oNwRJvA@h4RQ9WIKoXUNt%g+%RLi=;oPx+X%bNpZGIQ{Ia zoy#`_){N3`R?2P#rYnd>cZs;y$k)63&_|_9DknJ~hs-o&+FDsTH!0|h>n6QU(K_3y zI7nC!CQ0M?loBptmJ-gfzrVL2Z0g$Ks^EMxsj=*(JQJ*~JQWO6o+9%$IkTIfmBg2P zQ9NU~F||UXm=0UuFTMB#9ffxPY+=f#+i1JNW9Ph=h9o-N>e>gZk>FPA+-Fn8>9WI~ z3aGxSu4w+IzcI9n@8!g{rnb?>7@~NtGiU+)ichjYrk_t)GpjjxhIve~Kyh3*|Ba}1 z)L88CZw~EhTZMX2fe)}NF5;cD%t&Q&4cc5xOrZ?#B(p&Pvvq|Ta3F2 z|EA0PPhT$cYFf!D%E&6nj+x<(RM{*cq7qRl4(j>Q^P?}evC!!t0gY%_P`jEg z$GmcQtSc6Oh73A=mWeyEOCl=Am~?Q2Wz6N+*RE7an&e{B4KbXttQKJt5G~lx`3fj= z-b-AvOHJsIroKxTb}4vzvfT&R;ax4l5#{}E@|)vYm#eHK+>FBvm$S*!(6Fgm^NzJ` z@1**He6Hui)mBY>7uQ_G`Lr#@)2F2h#5;CL&pG{PUL=twLEJiWQb-&OfQCT6ZLKCj z0X9ZaM$vML3_IPSY2ipG2^_x&q~XFLH>iv4B#mw@hv`UvpI}yfKs{|tA6&~;T?|h} z&-ACVWJL_krk%z6icnHvBFgj{`g!+ZOW#A;g? zP(QKWOK0sXFt7>dm(F*V=a54*`1J7q`SaN)C_lt0^^uf^b;Z(01*g<&JV=!QxL~vQ z?adD|!4!9`3dK3Ybv=8N^a7dOUGNDg@8~0saofo>^2_2QN6X9YGGTb-4+1_y=*3ul zj38uQbkXxf@4E80Fzv*ejoTmJ3as5BxA7+dzH4dq0#u9(?mwAbuWQ}I@;nQFm;Z5F zhYC|-6d}urucb6`a+QDTg>?EdajmDYVU-aFGv}SfyEHOSTCg)V%^P0VmVbhN$-6A% zT1yjrVI*9tul^P0Te13u*ToP>G7G<`ke!vN?h}MdA0<|V&u-~D2%qUR-M4>GjeuB0 z$F0+dKvw(^v#A3cSTLe-j}(YS(Az;-E5p*Vcw!jWJ)rVkT$L7ElPu(`+VZg4@{amc z_Mk=bjAesYVcm;-nf{$Ikw(>&&_>lUk^Y^08C~>+0%F^B=i(f>MRm>Po#C9Z4bn?S zONh?&@beRFx%Z>)wN*JZ^QooVf2BYS+MLhpcH&4yA<|QkL<`nUmy^eqOrb^{!<-18 zzyA}y_^&JPn!58RY!!qc^i2ItOY3@#$LFh>IDVOXE{NVl*^f1PnAf+AC^wg&W)d4bsy|#;vpMEH|4g>LFO` zuWNAW3pd_q3~~TmX1w<7XlF3+f3Q;cX*qhK(3iNpgE|? z)xN*&Nukt&p1Pyk_$&sBdPpPr2UoXoxwM;0xtkwFH6uZFJGp?fLV39?eT03-OdSM- zqqnsP2x}C)bA{t&6?5lb`Kqjsih)~$f}VffFJf0aqFEG4bMe{u-3)=U<%Xs)p!ZJo z4$1`?h20bNIdhtss*b7`ZWEQYW|i0%vZA1A-ndq7l&ipO0Jyj$&>Yu>T<>&RilzG- zCTw@PXS*JkQ!5(e<>)z;l@w@RTmk^o#R_cTj!f$`STJ3^JSMhI3UX1%PRo$Xw01cy z$v;$eq{*%lgnd*$S3DP>Id5N~p?lBbyk%#pS%ivR$0UTggBdaD1xS2 zxrvbx-aqX|LSSc$#z6RlodE=#ubkT^ew2E$#HDv*w^Ym{XEKIhUPxDkG=k zzT^iY3uX+CcUwJ*dn>ya!tg!WZS}Gtd|0f|S32zC=*YJRx%eLe75irDg@oG*N*nNyowE=1CT1_;>*ziN{adD z+DxZUUTQb)%O7!r`%7}0eHIgFW3)_Xv={d@C$fI@lnj(4rMLrub5C9 zThU8bNzAh6cD%G_``tHJeq(=TF>w62F%f#n;iWB};Ff!oFWF6( zGvTXxKB~|00hiFGV0Sw=HBGyZ*R~=ar}fBZR7|`(o+pNzt%c2^ua;eWA{84LuOd}q zlcNHI zxoSq9{OR10p?t%xuILq&AX|A6@M{{kE}7g=#SV+KQR)T8^;1@fkZQGM_08cNm%rvc zv-mgZVn2k%4{1KL~Yt4L~oogz{w$A%k%E<1?IGXocyaR&o>>REB{q!z&31-UKumnjNz}$l1Kd9>irRhh~Z^ zNm@G3OeR}Qu4&<9cHnBZHn?MFM%|rE(a(#&oQI1q=OjPm=1?=edh_@1PpR5a5pp03KgZff9@9Oq_uP1^*F@glp?3w zvtL4MmU-;L_tex@O3%Z`!>1l$=1md>^b_<`;jC>91+^5T5{o#4II9Z10+lapBjv{Vi%jqbD*{706_g4X zb-nZf=?F&vrjRBPHT~iPk|ZgOo2z8l9?j4ezpzRg9lz#BYaKnOA-yyj;J|KN@R< zC(9})Vr^0F6|b1{Bk2~MK-5{`*#=tacA1XOmsc+z&#PdnV1LjI(C|AMIUP!nNR&)y zb>wp7AZ~@yZO}BDm>Z7Cj>(Tm`~1endQyR0C-GP8{gY3UzCf|sU$#hdl$SqqAF)%3 z)&A7^Q|)Vm<2^Wn5dYyqWK(q}W1`pn;KDuyQx_v7Q=U?R%H14h#{9O(xA?0+z5n|3 zIsf0#$k2#j+YTskX1-^K`&A`0%JHF(Vaf>4s z14)i$JWGN}fpa;)S1kDh<}iEDpbzod=I$~=Z}N)?3vEPX2$xiPNM@}GL2{NFw(`o*21z0w~P zjeh4xN{RYtKMuvN_TP?=EXJ zYRy*Ht(%?Kk9c@H9F z2Erz}j;_f|P1i)tH3(EXDCdlJJ}MZkBhjt??)`c%p}(s?b(C25XC9L95_o6w&V$L` z*;cFaV@)pFbq5z^{zL5%w&wN_;#5Ppfr=|X;8y+#*ol!Dt@iL^7#kJvf+*B*?udl7 z`8+b5kl@~aZ#Uo#r~-{l~hlw|KiT<7$Y%NKWq=UHK z<&z5_p_dvarA--*a88 z4@6p6VAqrKf&J1i`0ci5)<>1Q+rT}dzm8;r;25<3H5K{S0P?CY@Qdiv`-VAyRQ1IKRw^#qJ}Fco920Rrr3pHXa0d7z4TgMdg%(o3ztqqN0;Qyl-o(4 zlrzBpp!Dc5*W(8Z*1z0Yfc}pKZ*FVSd%uTpp&Bu$A6_JCho8h6&4+HAmbqw}2D$xyB_^uj{bche~aRzjHHLy*l>;0ko54MEI%m z$@w<42%mvpG#}Th5KLK{(^j*%_s#a3Ju`e^{Cr4mjVkHwhbDJ zKILwHpktjoJPE7ehT>GgFZGnemAJHqTz|1KzzhA1f40ch1xuxT(_%6rQG*lD zK`*7qj2k5PWd_;LvhVCcoPiuaphh-&$B7bxcvEh_`Q7$M9z=dkb*p)Vn>Pt4T+v!;J9^2>z@r zuPwr-5U2IA_Y?I$1Lo=bDa6=h=(3%<=Ir!R4O*D;*~<|z#8Z3FbV06HNWi!$u-3}HzzJ<&T6)CnIYMcPNg3A_Wa|i zCOU)>a%!+mK`IFN5-MfPE69-rJc{0=xd$2wS@n;l5Pq2UA&;t zWYnflyYchZce&LY@2e-=DlPz!hIU+oP7X2&+rHjIL6axAt+Ss&{|}o1MW#Ke73SXM zGv%ma&f0rjO{Fu#VH1jK@74RSwJO356!dP3OEua|%D_zJ3XL`fz9kdB90~reN6mU^ z+GT@fP5EMhKf}~94<^H894tGzIJG3Di2PTxcq2XZdk@z^VG8z{X&o6 z^9|`k^CNJZ%m|(s|7Fvx(_9n5o21-LCqz6t&e=by(74E(UDDl4t|(n`*nj3*l*Rsn_E%7pIZu>ROsJXSO%rM>FJjtN7xunWC)!jWvqrAGUOF7moxxk{cv!cR$ z1?+0(Ls1mC!0&o}LH4?Kzpsa;ZCFvHdF*(O#fnC`f1XkmQ@6M%JCYTU5fhw}5*(8O zV2#W!D%QnRrJV0qrm; z_8R_JL**`t2<-I_NJ0VG(o~{)@rf>NV2JyTmm)>)TZVvK{co4ur-W1?l};{nXtp-8 z!$WeDz!cY|>qJbRsJxW8wMN^OZk1AW{sB-l*IP2v(%s!1EO)dlLmRS=Ha!-Tt=Afh zTbi_ApdYyeb=j0d4Fl3xa(8B2rg`@_B51J_uOX&ZzK5%6z(I=1B zi{2vke|RdS8Ik*mv|)X_!R0;w(WfORaH)&?)OLuLOf1}na_ak{j!-f1LS-aCy;^Z?pM|VN5NN0e$ zY_DY2*K%ZBmbmMHCllHUl1a2q1?6_2nZDz z4=5ffP@`#TgG+ep@*TU}$D`g@UXm6jLeYeY)qH-e6BKWZ`zIF=(IT@U`ui1g$MMv8PBo`M6LGi?Ih7gjXkV9r!JpjTl!+>dultowPvR8D%J$V+8s$Y^il?!l ztyg>-yPv0b$YA+8fK?aMV3iI#;a#rTtC`|HK1bYT%S{MEW|3?fL&L!g55^SU=2%q(ybi;4?BYd7hdcX|X{iPD9_Lf`jX@P8O4-&uJ zwX;Z$N%DJ?{P2)Ci?xD)Ory?rIW8Vm!-+OzGU%tRFOT^n3h;SdT-Ht*ak_@9kG>Z3 zrvYZOlZ9pdp)WQGflETFNAp^d_KgB98d)z|am9Gc{fde~1ToT6uswb`4xNFdqT;B^ zwQE@II^yH)tRR9W3qyv;GA!mub3@Hb*}B)ic9Va>YAtu6)HIe8N6MIc{FZxqlqc>u zmW5XO;aCTV%E~PojRKJe9+oOR5m7J|ZC=@&2xohO^^s4|>Z4Wkf_L#Vb9360?+VaY z)|$ZBHh-p^I26R!K|AC0Qw0I>JL431>nd5wxo41+u;jfnVE*%j=~DOkq$Jke0=#eD zj}U$hJc0LcaNK@;k5=|+^$AyFOMp84A7}>UAGRLpfD~UII^v0j&Pv)Jer^(0t`M27 z@OaCD=g`tyORhVYlX@LNujz3V;tp6g`?2VGE^L~{R&i#!!AKzJn2|I;lK?p%_4aEx zbJ#0(I-U2in6HN(Oi zC}z7SyY83h+s54zz}teg7S)emVTytmu`-VAEN*UpHNKB!rx<3D*pRel7m6;Wp4r?m znC^L4E)1x43ex?W-`<&3@Y#4*ODG-(xj8cS=wItm!PNdXc4X5SQFIj{O}DwlyDPQ0 zCR>{xYkJm@6!5Wofvgtm8(8(SCKnngUgQ7aX?Td@b8MC4VJCK;3ehG1+o<`#)9^p( zYl-EAo@kM}ja`>b1;vBwEYr#5d<(IE(cN!W*nR`i!2<;s=u#{n5p|X^G1jb1197}Q zWlBw+%Z%iaB(`o?JPqkXwqK#z+`1$nv!6li^H8T}(wgc{%;AuwRaat^;Wv6wvAKqL z5VNC?e|X-L>cWxy`q+>L7NMN-dbF){yVlb?zAa`4w>8OzxJ?N=Mz$L-I5OM3viye{ zemCnz8Jc@*pU*4Z6wg+Q-Q7B@>dcT1Y1MMp9i4;Lg~m9=f-Ei9A)+U6;l8fk*{Z9b4Vz3#5ZG{M@DHSu>_W~<*MnE@`<@0NUiYqvK!|5qYs}m; zG1or$R_#5~M;eD@`BY7sfXUP{o3LHW?~l7G_1A{0?$G-U_t zYyR=IBo#$*_*58>kbjX$11nQDE)W68%((o?Dl~2T&4)mQi@N3xFyV{0wRq0Ez z(dIMbjNPwe+9kPW3eUG_0W)={fNxU>U;~@tKZm9wiaWha9da~8f~1!K<~a{q?F3*a zwQN!IRJ}Cv1bgAL7LDQ;t5&tXU-5jw^{h5^UD{1cXmTW8XALfcZ189EWOq#+LNDtB z;wNK4_pBgQum(HuyQxLPtxb0{c=kRjNflB2JznO|0Owc51H5+Kr{G`Kic4fM1L6j1 zej7R_=t3Q}XGJ{dumtBhICj@MMUrs8f}iWK6mc&uF_66Vl{gJ-^Tx z6LSO43{_PHkx^8)+V3$VE_@a14DkWB^|HqY?Q3R+F-3Uq z-8T#TglFF|5gc>duh1t*9**HW8$FOnWL$lhIDUZhY=104a*dwHo_PbO_}H!xe>YqE z;2jOq^xt-qjIf!K#DH}M?urqYpcs$ZQPh(WJDvjLR^s_-Y0k#wa$~cqLsYi{+8aML zH6M@p3z#LdIFXt|viyZgo{29IZu zd4bkVk?E2``h*@1BkkHMB(a6@8F-e2CX34}Tjm6xi1Xuqrj~@$eW2BWtZ$E7LihH$ z-@so`|I=}M4+^f#fBFgS{WN^0I0oh3F-6`VWtdKf2aZ^!7<}(X`;>a(*rM|AMe1`y z_jc=8FK~c5AT{Bl%r3hBY+LXVnm?nn!EDf5L*T5Ku3oKVg{yCWO-^h6zrW|`G( z#)xxUg$x_(6^mn_xygbvkC<(^Y{vGa{xC;MmcwIWb0MrJqhMbV#ib4+K|N++_f}5F z4&%rJZIS~9qU&q{_mwu~cEy8b5k;`?lTjfoV)J!G$4GkrFw$6hk}TQ5<)*>j-PPtX zSIbP}jmv6P%I>LeQMEtb^2{ACt{Fd9#$`{;{_1?fUT zK#ji_^;S+(bxG*nV;`_xLTgzIkwswL2iix@sU?acvFVZ=(_M_3oDv4Bu=ir{upx;4 zt8md1E)Gm^s=nBeHHN-Y8*0x_4DC%BZO8pdDbZD(67K`1_vf3>xlWymj^5d(1?YM&_)0c>v7UQU6FX+MlU z>1YdkNlMqHG~;l3l0lIquNrBe8Lv&kHy6y4Orh5Ex+$p;XM%mlC_mCGmv7;dt+)6> z`(gCa?=5W-kQE58=(PrON@jEck?AvoRp`c={+=yuSdkD~v4vd2NHC1*m^p z80h#Nz66{&SdA1mjRG$W#IUkPCLD>0UN5m$;iEL66!OhsR%{qd`Y`IIe15}_s~lZ2 z@%mydCnii-H;rY%odw|YS?lsI1RPkW#ZX9&`5|=*x=x{aaW_lI#9r_xPYJ%Oz4)Rg zo3RT4FLj{gEuXmPdREPjIIS)xB{4T|xHCz_08vqa(Whx!2B;O2jz>ZXz!*iM-)?GY?^7fKe$b6o9m%oD2CnZ)(>&1X0D zi6i3>owISk0#NbK%9rIOsW77g+RbfCNOMRe*a0udsJl&Z zufFoAeJ!9c@wJxb0`tqi&=j3$@uLOK>I%Mqgj|bQndCVK%Vm}R>Z1-BdVfuAb>UfO zyoo~E(3H3(%=tb+M@l{Kew5;9E{}wn;;-aI01vjo(=mfD4x3wsSpi`&GD=v)QhIak z^V>^fo!}F#=&qS_O4X@u>SYTep0~$ykDQ0wg7m;;FS;g?cSS_kTh8L-bG~Qqj4c_M z*CTv!aDLP_48pId?0SJTow{(P-#_hf11R7pcTTJRpIW@o#(GICHF%9lNZK7N=hBtgoAAwlcO} za;@1q^_LTTzgPG|Vs}n@uON;POn$x-nut6 zC28iS>4t%ITSHXPO>>O(Q+04Twn}*LfKSi4<8raEsWAqRN?(hvFja4Pj6I}V`Bkb? zpOD+9MUsl|f>1YN&t&){YR@lY8vnK+ODWd#9oyn#QOiPzkMLf*c6w||urJu0IO^Mv zT09hUSg?VO72u$?r8rPL&p)Rrf=RrO-Fdm#KC(yfElb!--HVK|paDm@j$14R_a>dCC{M`{2=Rf}B^dr`0Y1%_N-w{`(lB_7_<+MlpZ_vl` zl!Ri55{k)-rIHWjCoad!BizKUc_@gMDW0K0E}X1h_A6$2tE;5T)FieZU;nqpU=VTIdkYrQtHwpp2GE92kOitS20A=SjS+gF;}Z2M3eB`o}I*%(X}>P^&S zkFwE@z}&@tcjzBKTSYRZKI+u?=~AmzR4z7xBfGwjQ*YA~L#kh=aXLo^KUHyL7uIod zn%TvOcX48I>l&08fy+!P-nHYumTGmru_F=f z(Aj_kn|VTpp^n-;f@5ZP$Tt6g-rmZzhh&y)IsCB^8fC$62@w2686Et5<$m5^@Y$J@ z{9unt-fLHD%O~$cm6OFj&y50f7n;vU=dKvmLwm~nuec3X-OyL467zGh!ij zJ7iC(vVjMT1kfB)TeVIMzNWemS4ugFnmSXlEC#(fDjU;1;{lKr-;eZ{j4{zngfwy^ zlU|J>G{0>6uG|5I&vHUXVB9KQQim^EyJ6-ASS~u=-J84p=f@ZL6{tEbkGGdFfziaS zCDz3_-73<980BSWe8H;y_@a%=oVHhyaUZ{u2fjF>mXjzrP5s?aXn9^C0U|h7!g}uY zl8Q8IE63K*t*YV6`Wo#90m7g6i2u2uZ`*qd>h4j$nxa2JFr?GQSRdgz<6D??lgV2f zM4Nh>{Ssc#dFS5PjE*T*VLG{lads&&MF>FPO@+xR9|j#-K1z2R5O4Y9iFFD@fhzZu zv3a|%`3W7DYM@Agktzn;21iH($tVkV-347K)Rk`9D(7X$rhc$g784 z9vRQ~&xYC9mZED8s<0GAN*r^%U;Nd@he6ew!`sU}JS8~D3lLQ=(+PI}6j_>6eYz_k>=obPG_wBu7>%~;#y6$Dez_#uqS~fI4s8rWI&1Q&4eD3A zdoepoYzV*75dUQfl_~A%tC2@L?AWqnt&Y?ms3sfJxn5}eX})W9cX$1nh53t+*}SBz zueBelBcLZ4VvL747rUziRXPMkkeb3J3KOa2C1d>b2wIiOn-?YOhv#(IeIBO7njIZO zD{BWvZKq5el3tZ4OhCLYNuFT~BqBi;@jcLD@maO4@oH0e0smW8Z4F8Z4l6r&p*|P7Lkov9b>lU4} z@}$}EpgT?Ddznnn^n8K#-x#!qMl_^4de zWkp`J4Pok8S65W-OX1LDLC^X23Ccn4R(c&aEkQ*p`?HXg>Z*k)_xG>FMI4Tv)D|f` zHLl+qSYkAPkqbSX5YdLoXS~UH)1Xu&U1ZjGG{&aZq;UdVEsO!Z3zCq1 zC;LqgqY0l8j~SO4mtOC{q>bbny-~JNcD`WMHm+$ke=Q$y8d~pPZ?fr8qE#|YLmun- zr>LdlLs%H((Zo{NUwp6Tj`8n7U^J3zqUNe^R|ibb8<7{G8R}mEeZyxPF)SVd02+62 zgX%`ik^0ZfXY^81)E|F@Vv}Z@aR@A_vJDM^+(H|1m;5&BeH#Vg28+h@Ms>&ZN4S+Q z9<)o9PbHCc8(Vu*q~(W~oy?yK3fk^8rbpzF<`P)Hupu_UFWVsHptOry=bZy#lGC>UZQ1_lIGq2>{v>lg_3pK;@NS)7aa-$#4FAs ziB>;dEZ=MPaEuk-pL+h_?=_``VuR$zY+#MV8|9wiYKM9CM6v>rZf|rcK00+%f;ik= zsR2c+@mSbl0&W72E?_a4c!9P-G}GF^yuEGK*LKm=Q@;3H7}~({+21K1Un@i17ENAV ze}$De&$U<>F3jn88|_dN{)Ul_BH5GZtB+pX!bgq6Gl&|1%iiIJ&ey{f$j81pJpWim zac3EW-#?Zi0>9`ypR(!X?} zwPew_#qG4-|5aN%u|HzcB%5P9#(8#pNpeA=P>J3yYlU<`7m6vlsK+QqVVn)~29kn` zM$mVRdSb*Y6AFS(Brz3iOB{RnTaiB(FjpSeQkZrB6LgZRY_-aHGw`6(zG;&RW;p+q zr;Qw)8khdSNtsEP@zpdl^|+&}pNa)#(mr-bRbcuP0xvp|x{@IrvJTtx3QNSP(vn5) zB(j$9PfdpyE-^yCLBbO}xQ+%srx?04qU-c^lBtajjjL^6U-bov*b}~kegO+;V-(-x4J>0XGu4!!q82B;=ZcHp*Np#+3b@)ERPYKX7idZsZ62YUj*eP2OU(rZ z6+B%?-lK0`|I303lkek6HX1P{d~Rj2-mpL7+HnN^)7Es7@y3Bi2zkvjx9qIyXeW80 z-3Z%VYy@YVa;dolS*-OgR>y)5%)OJpi&Ycu!(kPy{5Bgxlb6H2qXG-V6Ycd$Ii~RE z0a6o-nyek~m&b-fhJKI_FgrR!)lJZvXyg4_6K9z&duR1L;KD25O{a(1_g#MpGe96; zjhxLh>&5lOSj@3Tl2HI01Obm)1Dc)=xW~r zUPmI!cUZ@Zu)=(d@dfI6#)~!&(LtkTN&9QTrnu)6BIU0TU#9sB-YJpo-X(E`6kT19TjJ+e1T0VOjY2Aa5^!TZM2_3<4eKqD*YpoG z=RE5~f@km``Gf8T*@%l{Ynx(h>8$y{EmN6fPc~R}T*1N32Nn!DhJBH4hn5wH-9Ee+ zfjoGieH9McLv*^a6<(Z=ltjPzplLUDX&H0@or7sL0|x`ot8O%_4s=^&cx&~g%kl~R z5nC?&`dzQO*_ufk_PrT$mQOg*$)!Lx?j$~iKh+!;N>E$O1~DiF9&Gc*d9W?uA!XPe z*WQ|cvHT#)#H^;h7QCU)yDfO!YAowfxsrw13G7-dzM8}QO}v(ENrAnIR=n*-fW-l` zMMyJ|9~jN{X+Q036za=&SlGmlb?7O1%_%v}y1St;Lg62&ZPo#&Z#0!oVpr~3`7 znO5_CrkF+EM?8n-!7JJga_}Vajap$!W7K&6$RoH8a>o~sGRLOvNpkUbxADCTc0qkh zp!nYV^5cNM`AzVVUp3m4cqtkkF0tpq1dzLCvDSy>eGu}Rb2^*UgiIJr)E!ay>kP7W z$SGx~zY7)}$vh7=2`ARxWE#m`(Y!PY>AHlgq%Gy3c@w;RY6ihhyO9EY;g=F79rUUv zEcWdcPn;s2!phV~XX9S|C&0JW96|)NF!zA={A-X%Ai*b;mnX% zjgSWeOsdcrU#LuLgYmCkQk(j@Wkt9~(;nXL%W-xFAUc{~T;M0%!+=jJbi!L8w$}X# z0wXJNDweeyi8f!g9JQQ*??FxCY3Wk-1DPyCWHnw=_5M*vDqY-TcnEbxkaOyr`gV2YC4EG!>Q+Yg@t;R^ydPu?$ct+le-0`3J)b&0~ z6-*ya(67Jr?xF{8SlxW+8FC8Ng0UHnQTFz{u~4wVw;lV~n}`h>JZGZDJ47?Z1^R!& z<^29h1%s;~Av+;E5F7}`(e>;z%3F(sf6P za&Z#AjDLTCLV&)?f#or2L;I0sC4hun|76<7Ef~;$JN=Gg&pV6g2Jgt<3_}mSw-+c; ziAf%++~k9h1RsP{`rw4iKVI(If-%`hQO!*(j4KbzB+eEygw_tKwL1EP)#O;rfnZu* z%kvq%B%UTLy76OIzvQ=)3Vk{5q3)pq{Q~_@<&?lWV4c0VMm?<--IgmGq6=Ivf3h~= zu&Q?+@S?p!RBly=DekMVnoO{W4G@L#)B-GIGEW4Y*XSTxn(}OBxjXboCw*YGTGw*`XR`GRsUCQRU6K;K&f zg#dMF#ueOt+7-;-v_^wwNlIfvV?ywTP#4e^RIpOGQuqh-$EV`4pUB&=KdIu{>BaW2 z5a+uTNVbv&OY%}r9pBcTbDkLJ3YyD4F?3?Z-5SED#m02gbVB*c$ywAsC#^1SyuV}o zh7A(m_lC{v@Il|rC~)0S5FGEJ-zNZt=WTTdirthvobdK4Q;o_2BMM$J-xJh5ok%d; z{hHO^U*ZbJq-s}}6EGD5d-GaO)x#5EkmIhXGpP2!lUWm<%>3_4^K}qZX*Fmy7(X%| zYZ0naDpM*mYcXF~(A);!OY@5jkC!E2*psVs1Y>>K5tE*yPtpx2!xF5~`n0c?2E5V0(3iUQ0ZvDcv#NnN? zLcMs3pEJ?SthTbPMe%#mhEN~JY~Aa!5{Aa+;Zw-I+yes@ND*e`8TB-3TrO9 z5_o)lQ`;N0<5Ib#ApTMqfUzz+Hy4{{zxnIDrN4x?5jmpXw$lXU{ad9tBECRquQ0~v z3!>@V!N}@k1Bpi7VtMV|j*ZA*vyN;g&MIx>28}-T&9!6i&ALmaC|d_r<&vFjLUwGA zO8X%?l!50FF`#~O8-uxH_GY{9@>B;iv&^+49jeM2iO|=e9q>_XcQ&DGE#ss-TVm4^ zv_anO47WQULv#X2V=;MA6^vGGjod(cO@v@P*nhd{} zklgN!PDtyk@*WtUu$6(ZkLzk7AJa=A0x$Qc%h zbd2Dg%^%bD)Kmvr+Pl@9=hNTuAiCa{+@`u;zQx=$fbzSE1BJ#4^{i@}VE$rRA)pHA zc}pBNwZ-gQEYpeBBv$rb`M6nyeTLB=74fdknHhQSOCW@xmaTs=E zWDW5--12ohHlJ5v*&i+rv}7z?szhg_>CQbBaOK!|&M6#vHqO-f=tQCPS2KcJujTv> zsYmcY1p!1JGbuvGI5jti{n|-nC2ozEEOJ~~h1D*D&f3UA+C93< z+gfe)tfGS%U-f6^m|*nC#~EC!1m0CA3^+K>bbtfcz9i(X%c?AK76 z?W(g#x3^Pz%)}N3&lr@h1tW3TV&2q>8Rf~8(Hl4Q;(CBc>iMC->|Wpe3!7bKxeF1x zW+j2#)a71|-{sG7wA1|3xK>Xx7?4Z0XPm3Qz8W;p^~M8LFuC+qfMXw3&Q-hQ>5)>0 z4aM}?7*P>Q&h-p8T<#>UCz0(o7!&G49S)k0IPaNBXx|Gue`ey`}DzA#>lD8p) zdx+Y*nG0y~+Ibi5`AM39;_fKV8U|es85RJO?9J6M#gVm(jO1`M^W$0zrt^MsWC#W7 zh2Gtg1Ri{~yN>o5w?nyL5|hMCaRg!J46k0p{%F_RgLCUTjD%f#E>sB`lTjXXNCp3NS-re3 zHvMjd>)rTo_US_Y1qE?n7XMaQ3Indr(QL=*(cAN0Gl*uzscvn3NO}w9{yFhHqxw4W zE_mRre7(8LY-53k^qeYV#1Q-OmAg#^;m2W2dX({)^bq^0oam}9*w-iVwtf`I^`99_sNOm! z)+YB4t)Ds7P2hoci=6ikV0c+g2!WP_o&7UKq^7xziu6e_#f`Xyixe7E4J3e?1zmL< zv5!K@2(bM~bZ0zlp(7{8z9%nJj#+xgga*dv7l4K?d4X||sF~kE)AoWka%*TYz;Q6^ zWog!rGjOR*Z}WiPQm*0;f~vl-p)u*Ko&Ff$F~S`q|D?lzFfxqQ1g=N}9zC(GXPnR8 zV|lV`Lq)14Tkl#LEBDiLfG2ZboIpqCF|(m#lxkY%i=almX$ii!Z)YGIBd9V2x=%c6 z-(1%T&Mbr9$_tY!)@YwYCHCrS&%e07(xcH{p0#bsa-EX&piy4h+&3R;ml|?^^1=g@ zo!5J1f)n3lv*|%sE!zklc+5OQgg3HW*@fa{xNEC#0X|^C$DUMy+yqCYM_A1-8qXRO>do7nNoQ9n@f5K3HCJNEQ(K z`aL-TJsUpwz}N!PB*EwJ$*!o)0ZHNb1>C8S6``xn-aDGmMC`wc%_u*cMv$nSZn!V>yvQ z=VkB5Z5i*4cCEsOuu6~ZgdBg4YZ1#Bi>r7%_#4P8omalm;5q#%nN5f;;&Gn=ldC#C_^_KLua+?Hjmh4t^zFKb@t zYtXv>6|x*@QSircXr!~C&-G3ru%eBD*-Yg}^(^2APW>Hr$zd>W3kz>GxAh)u_FNp? zB8c&n8Yt%zir_#?f$g;0jI98+*bRO4C(eRn(XMQM>Am^@zw;HFMQ*&azTz*~6rU4K zb!E~9e{pZUFOg@j@IrOg7EQi5`syk=#45(qZ|Ny7idHywMP-A|>Q)15Q_4ZNNFU-w zzfc$MZlNA`nIv%@%@@Ek#J-i=M)5x}d=e&1D(i>u!;K~LfrvV8;JCuOJ~?-~`{o+I zJiTBQ|BI2h*xE*wPEBCl#>KXS)Fers2TGUy2*4+`q<-~!+LURY`TBd*&hio;6KXk5 z1LybNNJI0gBUZfU-d?+%9c?6JQkysNkoAk=6mH=)vG)@WW2ttaKXLT?)fP!jCx9aC zsln?WB6N&K4H!hLb}1xaIe;e8O}}01V3tP@KZ}9Cp3!K~MeP7EZ%bCWcK&Q?~+r ziHrZT8+J83l@ofY-rxovp0@oRhv|0}*X3{ER5fpzv}a56LFu_ zna&M|psY{Ja#+v+DUP=;qtSZba59bQGZ}VFHr$H|WY+|IADsge_5sQ=%g{6tus4L>}=Ney|Jax;Z=d!4Jo`m|nDlgVz5fk2_afttwA!sfEy8=Jj3^A%B+ z=iD*@Sd>IFg>FlgEKgc7q@+`ePbz2uoCev472Z|O-`+J0a#ASPCkB+wQAC!DiaEei zSnGmNXpRcpbaY$`FxrZ*HtKe*CgcfeL&hR{EKClnv`@YqQqoB0F4D1dvy@6mBi$g~EL{t%v~(^@Bi$ge^uoIe_w)b%=K0P0&M*voXBp0( zyv{k-=d)j~f7W+UQQ7j_1?8@tr=`|{&Dkj?e%HnIqT0>d$~e7r#uP%LWxjg?n#DTP zww4qYItFK-vU*fKQ&^T1t$by4&>~J;=uR+dyLT!}49UUo9on}liJnecE0I?|_kau^ z2}RofCSb-uSDYl78*F}y#NM+1fnwN6I5f7qn^{vwL%q%NcHIvve+f>1{Yfy2DzU5^ zd}9aCWwwQ0*d(U%b-b;M{JkYon7(H4D|ZXXzr6S*d9nYS_jlh2frjY=ZxXAeCJHGxW;MRW$D7q zYUetqLl~=Z50jBurq_W86WKi3}4=jehmyUTUq7wy>8E zwj1Z!rd>|vG)i@-rcY)*69sQnn$AwzP*yFAW5D83xnI51_*_XPDtQ0q7{8ll^tO!r z0lr*$+V=!)^aX}~uwS14tbe{yKR*VM+VawVp|QNE^1}W_Rr*M;_r`_bbnJ*_-IMfm z6m9D(@psfqYwje^0z_)^6sO$DzZU4L%cmDw-G`7&v)uz0uNFBuf?-Yo@BHrCdS)iX zu?h~!SK+C;^=MzSpr1XOYft#fZNMZ3{1Ul0O_8?U=k^L$Mx`bIy9R;roXp;4aZJrG zIb7Gqxa#>SGdfz;=>aaPN8ASIu5-6M56+Xu19^#@WfR9#;B)mL{bcY0^a0uDgN%wSt4I^@c)595D|4+2tc$}y~6dz&)Q-=^{R^zy3~~ z$^`peS)vpa_;E7P2>E>E&9i{{wS4;78A-SqQ3^|kw(F%+{U@zRhyu%K+{Nn#I@&4w zjbL)h!80I@!^S5AzmM8a`r8!D!gm@wrql*Mhrdveq0S0M2$=`=brGf3Mh_PM;=`u= z{<~(WSWY>zmznq#rsUr0%3TL-Cgv39IhT6`rY(e*2Z;qe>y1NC6Wu=$Ld2w!@La>v zorNEQMhA4w!i8tX#A&rmN$fY?2K`LgBd5$`vFwTrZC|&kpYK@)(Q2r%+x=+|OJlTO zsn7QVEM?}`BuHe}#vD`LFlcGe7;aVmcFb)Aa(DcQ^_t#wd39f>xR=U$V#YSbSu6(@ zhbiiR@*}5h4Up<(O@nK*-3;@8imbaDT7^S|`L^e@7Uk}5eXn7Q5@iUo_oRYCX z`+);O?ci*p#{`v-S`7Y$r)w1Sb7N`b4Z!I;I(DwZ{!7k^ow+3Z%5L1`lT72T#^$#J8K^qZP|Yo@uECv|qos@TD))p|7|=A5r=)dTJd2%7D@ z=fwNit(sbzpAf$KDfF8>>!gHA*f*^Q-_P(jf`#CB;^=6{y?JKVL!a3aCK-daRC{w$ zl9(dyFO<61jB|dfxj24|Eb?7`-B2+ZW(7RX>{z*b473WHtdLqz^U{R7&g|ymzK$Iz zSa5M(Ede)oj(^Qq+O6@@H0`G$;nm2scIClijKUQ3NA!0O3doAeu&Fk`&HMuMeJ5HN zWjj6lFj*5;S7D1#I1)gWnD&dkhwcye1Nzt~!;^xX07?i!a4~rDCy_zj7K6He+|0t^ zB_UPZCp45o@!iaI(An)z!T!_NC%svL7+AtXnhWQ|X%ClYA`z|6kqjBNt~ej8yuVXS zx7eNY%Q;pQTDj2Aw}h+%d^(a}8C?ZW3-XG9>t#`!d6F7ezROQ|jn)jF=5D!+dh1Ah z>mQuzw=kT11YCjWp3ilcMA`0C{?qFs)p5jceeiBJ;X*e-$J#SQ5~D|n3bGW_gUg4m zWX@bN)&s7i-=159gEK}R9BWbZ-)|@t>i&TL`GECJY>a>+_YUnus9SenEb|18LT>(& znoM^T{>vvk-O<@wBl3Z8i;u+2EBS+wyts*n!ZOx~evv{u+JYH@fzd9Eo)AfE&qSR) z+u<;3s?bpld0R7_GQEr9sKIXQjf+d?`)!@^-E?Dfpkn!Gg@va2t(Mhy4uX41A!Cqc zz3Tnp9g0grgHDz?zl2Pq!sTfPQSI2Kg`<62M`y!YQ=AVoa|U=l%GWxlGtUwkg}M1$ zwj{Y~h4k-%-#tB=zgeN6fo$==DQ-2UA=b+LBK5_gzyP7`b`zyd=B~P2Ft@oO0A;8! ze8_XbaWa(hrc>Dm#JDT;bfYYt#|)4e;AT)$>|n_gp?r#PXJ=RQ5*=*&m7ljqM{&hl z8(9(s`Ec2?nhedQA#W+d{;HF}CmJ|HMLis>q?)q~$0fU2Ux z*b8#bP`5htgc_O~%r_=M?{D}*b#B~jrHEntk$5#-Ve>1aj1ensxUmz4sEO!))H4=B z6VZkdJ)SqZM6-873YFNHTFZi*S6Fn@<=unt#KwGxW(Mw(jpu$%#-UyHR2uuBh!WtK zkY_%btx#o*hq2*R?&s{utBwRyQ3s9AU4HjJYo-2T0E_CLXV!Vrt@aYwjre(N&A##b zRMoBETu-lgfC~$gn=6dI#9mHbUd>Hj4iDNj-0fYR&J-O7`4v_+X^O5$~Rr$-n1 zu?8CIXYHpJ{W&zYI*(M+g@adRt(s0;&JvA|QX?OHF|m+!(59!++3#n4oJLDG=YlQT zWf76~wMDyY=pFBv_-MOMhb|rOmD6mxNyzwNE!htI|Jg*8@v85iPxP2Q{*awNasFEa zZud}&Iad{F(mUmW8H2FzT<5H2FfZ-pz#9$P`N;3=;7x9jwA-zULs2eZD0#NUjjxlK zGPB-b=%fkz8LbAJYTaIcH%*ZPJ5Ucl>d{m_ubK^~Wb511Jfzcv1rFzIIXHk!M&y6$ zGineNwGG)H5EwA;UDl{F3X(V(hP02bFq(5#cdn1yW5rB+nDnY3nQ|X$joTe+OrB@w z<9zsOc0Qr8U%|_E8kJFY%{lp@cwpSa`Vdt@hsAuVRObj60oYPXj5>nW%-eFAN2{B( z9V89a8x@L$xsAnfD2&!@+ATQFm#z847Jp6lWfJPs(8R~)(rFP{{1^kTYOx{4JzQ%u z+^L(!q!u_d%XyrpWNZ%BAxg7*zjvTg&!I{B6{!s1N29`q%;@QDs?S7n^ZN6Z z?4mxi1G;RTc4o@fw6wVjjpw-{9{N2z0?`xOC) z-Rj?k@pCTytPa|d58ASmFkNd#orj&9bT$@VCL$!`zgyt-%za>X+Wi^OMN4NPyZTPA zn((s4w5PyWoD^%^n}(h;ypK&2PJj6A2f)cjv0YXwMa@PL;MDMXo#MJsAW4E+#c(G+ z<1KG?3YnEg%b+@lGA5T>EHlA-SGwS-hp&uzuq<(^aoX3VqS#^uCxB}Whyf?MbMvPO zPo>**30S+uXZd-{t48_P4c&#&-P|r%wp@RPw!iexJ)e5B#fGffWas}@2MVC)3dyQZ z!X$rPXQE0U{`wpi_A|SM4^*-C6TRvx^>s#P=Q`tS3zHtA+r)KI^1lnV|44y;s&azt?<%8#%!syft(fX={_WLL@18Es zI@etc{l)5RM_`GN-NwapDqX?7cMeJq<5o!kkx{JmY$v_`TrB`Y)G@?+3@I$kO-`9E zr2gVHOo5U29X>#CZ~e2W&4&|)qTS|UB~ptQhkhR?204ZG4_(d%Im1N#;|)K0lWUep zcOMJB88MW^OnQ!N=FM*F8mQtVm59T%sZXb8RKd&Y9ogRa#9;OPto0##EM_LH_U{fj zX*Wt@_2EiCpwpmOp(E2QTxP{(l)-!#hV!QirT;1Sf1Ag@gG=;^Djh}jGL!g48Ja%U z#B@U+^Wh$IzrNmoI>If3hldwUho+99zq)(kVqhnOeR_avm26Y zA$EXjbZOqB06yp+uZA8p?q2VVgCLb=gL9>hX?t4+KNESpoQ<*XI~Qr!c(x`aS)wXOIJq^dn9E$RH>NaUdAYT)9CYVF!?i|$k*uQ8n$9@ z$QCen$1my2s6}|N0ckZ@boh?Lq)`dAt+-j_{z+0i5uWdJ!`zfIpIpEAL2?`ki-m;M zdaX02J>DIhNv{|9dO7bSP%q#nf@{5Iy>!7JO8wj5^x_%!0W^~Bi-K5K7P;SixI0&k zVwJ`=vd%I{b4g!TLl&0sfm)UQLOxv9U0|boS;fCgl~RdGnN``7n^3)2t@y}ps?9>O zC;#Cnl{|;q#vF>&%m3Q)=aT4DrrK@<@|i-Hr2XgN}J6QLo<0zb^zcc7%go zKMWG*CS0mfta#KvQENozNB*7g7Col?ak zSKmCNb;dcDX5OoD11b!XHqmeEv~$rGwDVz>M3!W;FK1q@mPn%}_%fXhs~35efO2XP zKJ;fQg|ZC}t=NfJlMeM}1e@7;2PV`lvb=$R1K||l4{B`4XA4;2N zW($yazR=1+V`e&S%QsY0E_P7;tq(Ppr1U3hpn;t^^ol;|w9%*XzAl&xI|yE@iKIb! z_{WHeA#GJZGtV_8ZnyfVe`dbIyV4XZq#mSH*qJW_M_!?q*ySg3WjQ`D1`)=~O#tLg zt;eQG%#1om{Eur0g7In>+!R+8R3iW-yUxQU>${b-?q0o}tzE<)h<8r(e|Z)NimMK- z5JAdXiJS9P*EK$>=1uvfSHcv5S^Z6$lMt26a{$;g^;*5+K9XxJjN0ri+Lc;ZEURSF zFY$0SbH=@{Jl)&v&1h!A14WLiK6qk$ds-7zZIkpdOg-y`hkesv1$V=e67{7ZFFiITZUGa|gL-bUu=y;&B$moteXy+9ZIw<@^xai+;XVzrCZ#wQ20|e598%UT@4RQk;qM0{jcCz;|8dw@Iswsd#l>eHW9G+P_nBI_lOdD#O8=O-r*(QfeF zmh@Km3e!mk3#WxbLVNy(v)_VIAkFIM;*YAmPgA+76GBJ<@Xg-hXM3>rVg_o(Mw({u z-iEqTx}EQ%?uL%*WP`x?XxYQjH?NCj*5xh@*+ECE#HI0`q-J2$Z^4Xs zBWdC$9huUv^IU&mRAbRRJ7dK^sC}uqbo{*W zxq{0AZ1ec}Wlma!bM^KXPSG<6zN9x##O6H5QLK;$tXT@;k01t&Dj8)F^PTLX1W}|u zKqy%Pb|WWR!nf?}TerNZu3Hrj-EQ|IZiDJ0%L72&cxpaj0aX~go|E^97qqcS$g`H> zT&3D{&M1!vq+1Ks5$-3u%J8Wqdyj9LfiY{lq;AW*z_4OImlP6T0_V9LME?LAQmxrd z@+vVf&xrqBWbbtCUGDvbxR>J?+%0UBhv@UrFGSRcQ#SYQE!`Wc207-p1x@YW$XIl@ z-cHrd@o)GX--ge`e|Z*Y8Rx;^%dQ8J9QH4AZ|C)?Mfm{hW-e2Gc`5e_;uKdA`5lcm z&U|v8J}1Mw{gaU{@~&upk^UB;4FS87YvKpKwFMK4tly$wN6ITfkDdUJ<7%{PHe4WG zaCS8Z(-%6Q?1$WUdgdf`nmrUpe zC!=(4b?kQQ8?~)B;q{@t(QYzDf|$LFwx4zm`y+8F+^l~wEPAMcJ0Z@JddUr5;2&#X+>cTNN>AH|Gck2p&dn1z;qul=nb1oMNAt5-m{LVp)YJUc1U zz&R;OB|a(IBI7tDcXt7ItJ#$z7K=J%O!tchubg+Lpu_;^%wuc85!TRB03@gI*A$P| zt;c@&L-0f=0Z71Y8MqBO{o9ah`4DQNwsNXzxZVoqcL4o!ZlNd4t{1>`Zn3^*hb$eh z`s(wZ?TApwMuSDvW_-)OPP;?aPkX+nh@+NCoOtiNwRFLUSDe@jGi?Knl1KFK+Ea`{e+Xx4j0>~9hLJDL2L5LSP zHyNPimNf|@zx5F6TMyy8@sQ?|0g>o}Z#o_a)#j;XOOb4!x$o4X1W%3~77Ssf(@-51 z_3bf3c2g(yq9SdaJ2S-3K!)e7<}M*7q3+iz8!Wpy7ar? zh$Bj?hwXOAoNoN#sOeuu(`{}zp}a|EFshrkGVk195vM$!H{{>D@X~(aydA#fzvYud zWI&Yam#j{Y%rW&U(G>Rj>$tJG^ZKWIKlHbEbu_!0JIgo@i)67*LKJB(&-FLpTh+ZF zss@*O$~kbAGCHh)GHunMZ~iw1cBQ!aw>~tt8r!;=om}DH%87DuaprEYD|u3cZ$skG zu7}es!l8F)V`&0fJyMe^_e<5mgXA-WM&5dV@iSe7!_Y*A;3Z~ixjT}MPcY%fP zY!_g7KA5g0MaKGO=v!luhIe;~!{ixXBivx}96^>YV5YdEF*&aHEFN?HKnP%FCjHFi zJPU+~d)igjV81@4PW=#3`VRkAFSfTqAA1vM=iax-1>$%6j&v%m>d_LzgTG@V1fz=3 zh!@7L=d#cQs56~EyRdxigGF3Qbl3o=8=Ea&!}mz?AKRQoiwuvSax#%QQNh{&t^XM| zlnk10Eo;7{Id+4dBRvgWVM%@o#2hZ*$aNom$fPS6>N4qn?Ywi|uA~QjuK4{XQT;2U z#jT7Pw=yc;%J}48t!q_2Z?IDj)1@+{`ogZqeudL4>O+PIzlQ?UjUA|Co6hVhv6bs6 z!VB?D5|Q$|dkIMEMnevK!DM!h4mn=21)rk>l}p!Sr?783x}=@;`h&|_-tZuY!gwuf zA8>yiMCW_l;MEjz$+o>3UH4~aW}qi~M`qv9iR#@S0626U(FSkB2QYj(+f!ZDNw-J5F;JG`D=b9FB1*lw(3y~uRwHIYUn*ka`kRh?@~ezy+{*v~ zlH3WYSNAVx=?d}XtI;XJnr4Ge)OA0}jjLo^jBx8E*_)YxHUy`Xpj_@3c@Q46iXtzT zv)v(I#Asin=VZ4!(`+{y)^l|U4fKnSQBUSe*UJRTM;8nYRD|yTMqefoS(1gNURANw z-wwK**`Q1QNn^58d|x%l(uXhLleM3LY`%C?uV zr4eSInGU30_#Y2&6I$(7P;?tAkR5K-%O`c)#DkXYwT36kpSIMr5P z8>gr3Y=i1X6RUScDproBuvtpk8n`a@V+51~{;`GH`O5n{L~7I+1iPmI>Oh|&%*s?_ zoWEE!0Moz)V%7J<`ru3xhLk5C&%cmcoCniK@*2(U&Y#&QsM_UJS+ve-JKEBdE9V;= z)6VZq9@=DkB>>!l7SRWBH9|dhmL_RVM2g=KCV^{8a~o?viH?FII$ zf#O7aQKea$ns;vVrRBCmu$_`jP)7~b_A-`JWWER#+=&uhCC`pyo{-`WpR10j6>e9~ zU96=to39t{;xEt}r#?ajB9_wR(Wi{gTjzcFD_7T7y`VKzGj@x4`tiUtSI6^v-SPU& zI=*kDDYkxTX-N)phm4A+B!bkCDQjw zvOudWN3u+dspD1O-0UEY28(Wk3d=Oj_$)`CNJp7gf#ioW$%2Vj9U^_BjIF#1FtyK$ zcs3uHxD_36Fztk4oSIoWU-Eocbb<^M8?EY%q`xqUzh&h5Q7xgIJ;!{0DNCl!GCj(u zDW2a=!#kd-BUb-&mP0p0o=f&|B_u|J)gD*+_4YESG_L)&b{U!71 zKotz%G11k*Ivc7s6v|K9MeA;*RSKH%YW7}LdCT|?0`1c*~(YFhg7`^m6I zA%?42iHIES1vu2F`f~9wC^>vsMkGy4ff1qP?O+n*qVah%0@g$ZL)r;KV9DmQi9Dl} zK>hr9V6ia;(sVogo?hyiC22zt!>|T*cr&HV)v*Clxm35Kjgl9gI!|HZh2wmMf~Ux8 ziAIe)243WE+1yMu>LE!!-#l7{d8P!h92FWX1-vs|E+@cUb?i-vtm6JfnIlggTMlI7 zg9Q?~Wa<4Ru~<97onnTJ4-flOz|UG4ip*2xCmrUF$@YbiZk2C|^FBsP!0$$a1KuWj zhMT6;!;;1Gl`9+~Kj4SyWhPkuAb5fqWBDOdniiXfiI|jL(C-}Dk+^0a*UfcbVpAPk z9xwIFJ-}lB!5FCBecGhgnX@Dl6R|1D?I=c((khD0GD>E zJbk$(pbWno{{shbr4il!NSE1P%o$5@bZz66>|-Y;^=}0qNzoTYXl164!{leu-}A_+ zD|lt6n#5Q=15vH6?rl9=B(aV)kIG1fbi(VGek7NVD|g6(HxR^%9&V;#=BY6Bd~v)D z(y7}{ly7MU(B?er?na6!h@?4<+v1Q*WqkULQ zVYtDA93Jl9Y2=M`-~5PPe9qr+*k3#%)B1NF{lg-!)~&x|MXpp$rv3tx&q;l&^UDe` zsC^bC#dCd7DP_}~WYTx>ETmL8so~hEDG*BKvlYpvqE4)lzhVYrYr1qA3BYIj0-Q6R z+7ELWWM>=g>sGy9I3dR8r80SR3Vy_=r-$Bybn))Y*DrmtZtppK@aLM3j*(a~1tAJ2 zl`ywNA;HP2%5PRgV12%f^w$*!7gS+qCV`P?l1hm0z*2VQ%MnPwKy zGZsQvOlY~p{L-2^4Wy9%n`Z2Y_pWTmN`*+8pTY(>k{38PuG~OM!0ZD`HE-7WV)3og z@tegL9dCW&vN?#h*$NGm3rP`X^~K<{bdMZv z&SIUrq;Xk6%?c6ao=gR5+pFn03Yl59Yb?eDvVgn8wTI^xEsAPb=Pn|{ekNKOvQ3YC zimd8g;v|c$>Sd`t64C4L_6@qU#~BP8vN?wR&wa;Y*Z3&s?LS@*7$tk3oh>>nJkWm< z@MATLgP~X`J+Cy;(IRoCa*4c~n`4it1(z{RkyEB`T%{LQCRVulljusr6$Xy1&ZANQ zIPIVUtMd-w+zvix?rN1#{jk%65I!O&xLln9qM5mXUNb7J9n{$LmgCWvX7w9j^_08p zrk(oo{j=I!C@y0F4rZk$Q=e*Wkkyi4Lt>6bZn%tL$;1FkOyPB$qs@GBkOrqlsyGWN zg%EnwpCqEweR>v8W{>zMEnIxMvnjk>jj3-;+Y`ATd3&YWMWk2#B@&UN0jx_KHMy^qW(g=bKO`a|8D4m1)3FtHalo3Fk^2vF_~oD z!?kok?cVCi?t)9F%=(sluI8OmKg|wVkuPowwLiH5E#WLpg6gYWvZDnpDmjL{}FGN=a^*}nP%w`5RaF&SRpPk zYKVhKWi&h>D0~@n?ncDIM977UZD&Fj(Coseh*fLkEtqck18$M1e!34e5wG_dDVvqk@9+L$>T5%- zQjrHK(`y=-Jnrs@VAQE@?qfNKQS#KO70n@B4NIw%iqp6)h{mK0%`LUPIbinmKJNc) zZhtK|sTOW-8ZKp%+1qlI>o{!{#vK0!kaw$qR?Y;5g*~MB()7_rjRVkVa0d9iWg!h> zFsC=_z*v>-oSV%`YTH@vRl!-CZR9)5)7O_3cv!tpG4c9XA*NczzJap&Ys=Go{2Uc5 zH7@6k48WvIP&G)d1&sBLPhZ$A&hB-}h#Fmwi)^ zBh&8Dp_mXgEKJA@Dgh{?W@aJyFlgLleM$n0FMESFxo;7E*bbUFT$)!Jy)^bN)X}}22Rr!a$r-?uWMo8 z6xXa8c=J+DH#5m10ZL_%AWLna6=g370fN6dCJLRqq}A6i?7-o#lBGHJ9!;zH8s;!i zyiO#Gg)n2h(9yP2N*kYrl&BnS$DdEDqMm_qeItbn$dK{`9NWu=9-AMxlk_#SD#L*Lk_mB=F*T- z?KF{bW$*@8jj7?Nhj+Ws!OXA(FXGV!R0P4{eTIG^R^o|DfHqa=_J_AcLPsXs3)E27ll+^r> zuSEiY-yp$>-axD1u7TD!#}xOF@2269_Zh(-v3p--Fyt8es!cz#g-9oJqwqS2-f=2q zsBz2+^o%|P{Lo@)?BYuUcv(zR9qtXfF`SrgcNutEx9@gFz-AML*Ic5?t=#k50#BeP zBQh=vCI`J7b18g>z{bSo-WO)gyoLrw-Go(T&p@Avcmr_Ip-TjOk4>&Tt{_%4P=1u; z9Di{r%me>oC%8LEt|*jH8cA-_Kfg(G;D$AUHtzhNBgy{>(Cw!7WHU_HOY4UC!Pko2 z@dXdiSud@ciEe7MxkU9NQn;qJ;26Dy5#X=DpoFcfAETqF4s+9VG z;%M!BL()U=OpW+V`aQi4&|+VnXkGbR(GrPa29+AU>@-+)EDV^fSMvcwAF%`OS7Ci- zl@TdNID*V9_7d7@)t>KkJ&|Qgmo)&dY>2zXX34lo=Du!{<$fJv2JUzo$i;`g3v!qz z^uP9NsU?J~c<#&ZxS#Zo=I-G1<0eWoQq}y5^(lCVD&>3K4F51C@gV=O zGaqz@eWY7ehTcJym%U!&)tUIXk)!kKNHxf?3O?R8=dmfFfTxN(6jo$^)N@GMK(M*+ zm!6Sw)9L?5PzvW-2@5TvCehbB#=R5>_+6U@6ucPl-N-&Uytcy(=v8KuhJ z*viX@4Xrf_j16@x8e;2vNm^puNG?-se0k*y28QQav~tylb%{PcJjSznQ*JOa+ACLG zaR~2QD$K5VPI_D(7BSmkGYBF|RbxocAh(7ePP@&pOggl(eErZ`WK7#yjtA^z>0xM-^UippYst}xzDg(0d0XdR5D+!X-r?Mmry5ZyjK2Ojv)jBpqmqCdU z+4u>mx8Nn18)iGv#rD21yKm_LURA-tk$S{EB30Hgy|q*p)|K9!$gpf)xQ15#D-X2F zZRm6+Udb{L@qS4iYVDkUZEheg%BJ9RelHH_Y5gRxL(4{x`}#>nZsx{1quYM107m@F z0qEJ5673xH!3U;4&^6_Mr$&AZtHguT)ke6P#qvLzSjh8MDj(4Eovnp(j=5qn(`CUD ztF`Z3AWDUmjgJMA#l}?!S+Sv`pkdhnn+N4l!`=QGoCBp&ETj(#p5&FhQDxj*9h~-d z+d9>Rq4IUUVSh{t5$vekQt(ldlDW>43 zxoYpHIjkWMapKyP@X(z@x;j4Mk=0 zz~b)3-a-LEz$ZKLL2Q!dR=G@%^G2%jtL5l!G~}Nu#DJmruiy(MjXO41VOfY2cmDnt zgltwR00ma7vEU4U+*zEeGJ7yvd6_c3KM}7Mc;87Vi43&xyy{oyPgg=t;`qI(HCUEC z_qj%z7_L)Bq-J^(k82M)iT@$Na@-sa|Bt3JQxW}%bu4r8d`lbI)?cL>uaS%;+jt%J zaJkaD3;G0+XmWUQYW10U!Cie_&FbQ;B)=iq&T<8FdU4u#t?uv9=-;}L=-&jfON`Zi z@&{BM?oRVG?DvuRkEq>iI=bM}NH2-A!(84Jssj|qRhLd9lUBA2s7zyK90Bp@@t%o% zWQD0>W{{i0ckEu4UXonRR6RejawwyMBeMqdsN$<_X7^#xdLi9@o-u1R+6FB{%m#nD=8Y@o zcT08g;UQR==~Wp!E7o|5E^7B(79@MX8$k>eE1%i*QXgj!aFRWbId0O@I1l~ z=+G&{Df(vrXk`<}DUwZzdsvP`MJl)Kf-=qhj^iVwV?_BS>>7E1z}Pvn)s^BBzox$Dm7L7QsA_*AJ!(aJ2Ous&1#julI0&ptBZ!|O^?dUGW5{!?y(IT z1F$yDpyKore`iYCIV~Lq1u=LWvo^7wRVW@!W&VR`V|R*@+%BnCrJ|Uf830kl=7}s_ z1>yc7hBf_f^GT}*dANrl(DcGn%h(twRf98I-M+kHc*A1rXNrOcNSY-ec{cc5D0&H` z+N=>J+`wp;&CtL%M>EVVV0WQp zOR-=jdVO$N$SV#&cG3V`K5xAz*thg@%;8PbksH@S0N|o9J5H~@POq_Zx=8Bz_Lmod zfdAb}k7AM|8!x!V)1PzF^#OBUDS7oL@-}BBN4%LylB4a7N1aZ$>{y&v8!qWrKT>ov zNLk)>wIS4*O}+Y*gErRN-B+5NWbe_n>FIm4{VpEsbia%T_#B`fp%I@A-~L(0%Aw7w zWkLrmX2z*FY_Ll;pQ#6Y5Y2yR#pdK};M#BKMIK@7__#3cm157h3Uhc3&2UrWmITzB zaPaB+W^66-f_fTwP(3WI7urDDeqi&dNFf{8;FJ73B-AM5OD}H;Q05C9y z2i;hGK>SN>{`f!fR*>}i!c|s1hlGp~9=JEhqTykG7R3@fG8$|}6JgUV{8cf^qh>QD z{etTap4Y&RbDr39E~okM$oz4+d6)Ggoj4tI#w^-5r*E6bEisn&kS;cEBn{Ev{F2gP z>4UOZZNdSx#k^NVq}foe>~_tQZN5+^Tud5m%+%tP1+X2-KD zqg?v>-KoR36#MHHGBstXrqBu%V@h@MP)ul2Snl&sACW87(_zpEvC;d}2g|AN%umwY zcX{_z;=iCh$&xgWBCYP%ezn3q;PKfDF5Z;&Wvp63YcHP%CuXs&JZ9n6J>!OtV4IKB6FYGq7YC!w6OZqFtuIY1ug8Yk{Cv41dI>J*G_gNX6b1B!YxbAz^CayBcW)T$#+$LRP1&9GB= z$JbgNNiI#-L=co<^Xo!%8t47pK9QPkUc+&~?qG+k@;Kv8ov1DU{>SrTsPgz_NjP;x zUSE^5%bz&=_b9=?1E~iZ*H6rC^k4iylQ`eqk~r;!VqLGh?D0DP&@(y5j2WWk1Gee9 z|1vor|9NJE{?^SOO*h$%e!3wq&5+nv79|tqgf~Jh<9%|{?2TA@9xtwVp4BT7fzUtc ztQP1+HBz0NT)64qHRBB%+ou@fhltk$hd0LJ>hd^u8EdFKWDG*iDLh$7)~PDC^51iZ zHE)Dc7$GGOtw!~tg_uYA=ktGjU!P76vGqE<$ehOBGmi(Un`>CA6=)7Zl#5bbZFjw5 zANC++aUCfKyt9Ys=7kQsL-Q(1M$;8M*k>8wA~{9}azX4^x{Vi=h|DG$TZU%BP2%TI zpRN;wX-8;}Ek}C0cT@)Rs(VBdC$oRs4Tz?TeyA6%)hN+vlFd@76e|+jihx|7Ht1ap zv;Hap@8!!QRhGrMQT((a>qteResg6B5yyw^P-=^O`m$2~;5urF@%22}gvdn2% z$CpT_JD2FK9?lC9S&{8$4zon+R9`q!-r7$p{+bkNI0p|Ue#J({NE!!Rwdw9&_S(2u21N!9P3KlWV@ za`15%<$f||xrWHs1gTkQSgM=Hb2L`2AM?RDnvpvSzBE~PmpqwA=?1wGLLNv-!OmPR z?uPeArCC?h?>;}A4?g$5c<%8A=^yGbGpZdym>NY9aQP;nQ(GeBG~|@qeCC#7)-72T z2n$s5?Yu4P>3X*>(1T}GPpHZR4Skn}X+b1v)$-(_ms@C}DQa_&gxHa;r#`cm{R;WXBMNt*Y50kH!2f4VzX>{84+w^E#)PK67oHQU695%;s zWZ`&uz9r|h*inKADaeA$1$5{_IIc8`R-C6ywD?;s5I-cGraICt9X5|ioTjSjpD98+ zoo`JgbZbissaspB-axc;)7uxMjAb0WMcZ;UeF!tY%igJV#be!9sZMRHGnYs%AYR9a zXUoWq2b?@o^3c@)mvVpaDa_FgArfFThU_v6^-^*v^Te+ zQ3^o!eHishM%Mj`lzVQ(c4M@Zxg!4qe7f={3YNqlss<-W_Mo?RihHnP%d=@(t=fQtcAI z=U66IhM2lvA)`R=@&LhMMglunLUnt;L0AGiVCuH0EP8&%H+a`E;>Ho?&wQSxKZq2) z)s*m!rX;Rzpri;gkZ7NH-{NV()`t?+))_+?cPXbJWZ<@NndmEXZbm)qy-?5AjtgPE zI-Pr8#NlA>3opyCo5&Xl(9EjcSVQ{Cm3PrzQ7pPX-ka)rWn~O2<8upJM*IW|Ckd-T z?zBW6C0$u9kCyto28ixBNOvkh@A^J&#GfQzB_F1Thfl{EiY^ZdN-X{>$qikKM)F3D zfnzs2-bk8T$rlCGDElUzEag(Ry4O?%7@E$vk?pl#t_2sSycdm9?9e`$qGQYWSiR_- zg_EU$DRs4!Es+W~F{l`b%ZOjA;I_t#!#?@>uQQJEgj-Ryz3PLUq`KoCW9u$SdWT6t z9^_Jv32E}&@n!H_rP!w@~w#z+Ssf>V8-fr_rs3 zs%|yJaI2xI8{10b57S)Hh~Uy#Q1l>!75CZ8ZH6f$QxkMAo@|S>zF9cGLX@Z3DS+wc z*v7fEN`T5h%h}|v2%WY>|1q-e;)n(Khx9CjVECsX`=mZ;}JN~EmhwMIr< zH+J4X1|5OXeIzwqY@x&PNFJL&j@ zB2;ftIMb{3-k0HvhIS|;ETsX}Rq_#W8g$Cl`lsY%->{Ve)t>oC{i(=-F}J2*UL1V% zH}1;92d~i|=#)oQH#_BOhZweItWBv%GUBtc@(#-;J_w;|8uEbpor|to#5*^5^~9-8EA9$3 z?7gT3h@$Myhu6rzUgEsX4!I5c8#>>MsDHhcf{H+dz>om z5C)O3X&##nk+HIq3;(8;kXrrAE%lt&DL`9^IfbWBPZRP0ETzTGbysWsd9y~1w3p&v z9{63fgXtQwg^Xr4s{9z}yQoDUKK&oLZq88!I{h0(H^ot+;olsw`JvrbyJWQ+eGP|2>};^$`G9LTMm+%DAIKdh$>#a` zo+h^7?D>bkm@74qeb~ajwB2y_cc2gBKkd58hkCb9Qo$1wI8ase!i2C;DZ z{e4B~@9F(sNNGdW?qx0)`i+L~y}6|N9Aqo%A#8!zr(hhOZlD}=&%bNCMPq*}v&5~; zx41we^ERuH4bvEQu3vWsTr~;DH;71q^QoxzjN!ey*Spb_$(9)?g)$HEeujWBu^4$%+xL3>dXt{)nw zg&AHXDE9Jl41-iip2k=r@;RBylp?Kn@NGUYbAFt_z-VZH4@L%qB@l&)W@gDna4C<# zW3VJ|udXQ(sks{c8`Kr-i`d za9d=7?)~Tt?}qsk=1O(m5xKl#ZIPMxMVT6GFSN1>W!pH^%FDIp8UGJkZygq8*R>Dd z=B)w-DIlP9NJxi(A|c&1!+>-Q-6bd`jexX(fHcEU!vMp8(kac*NaxT)$9G{o@AH1Y z_n&KyIp8>E@4f0=Yn^*tdUe&`f~6B|Ox03}h?8$#rz8o(S6k0|j;6Ejca~oO9S5w% z?MB2t^1r4xtLQXj#j?)7atEf+X>l*zK39@e9idc#)T~s<)zqrcMnrZv%kABc+Hb2U z>a~i^DH*a-bb`$KUyFIP8?hF560iVrmbdAzrOSM-z8;28LGtf)lH|`si5jC?6zFfS z>qePsbqq@TI4k5{WwAc4(o!s>oygVI&wbios2Sg(U~e1C->vAlPQ>4%R7!Kd!q`Oj zwX+cYQlcyZ)w&lf<=%-f(ODtWwi2dylf8aZbwoCjLR0Eyd{Xz0esm_fkJ<_`y0|!s zu%hlmXGZI%sm_SuqiFqaW{Vk)YMl_5KZ;<@-@o#mF^~{#?Ig_pctljXR)o1kzJcNV zeMd*9K>Tui&RB;)`iR&z%=dLlg|S-qDPVQ%0|l>LKN?a^;%xnt-NQap&7Dtd`ktLp zX89?|r|{xdkwh1#tfSvKotsIH7M0p18U?kHBhAPhXI|FxsyJy3NBZIZh97-vYAZfo zdOOH$F|pgj-C{E=AIia%U*Iw$24|dnw6pZk?~CsrsC{3u_)mc?_<3qM&~Z_p^sw$P zbfVk~D;yHdWU3QIAJy8RcfM*LH>auQ3S1n;{ru*KdfQOo0_9j2|7d&mLTWxjDap9M zG*!VWY+}z`-u4T8R%>nO>$MXWYhjxyw0t2x-saN^S*Y5G>rO=5jHntmM2SLETk9$M zd`Wws1y&{!b^F#xPEB*rA(N1qu7*Xa$#LL3Xs}SxDV4~(fs$nR7r&1d9dP`%k%Gjg ze9h9@acJhZe`OTDaYL9XalRYHG|aP^CY$CYq4Fk^C>E4I%E8c=t#1?m zD(k}`lL+xcK4u(VyTDr9NRFc((IXXWUgk1o+42V)<+7b6gli5;F9;2Ts#|{sv0&zc zhVqSqR^lYR?h_cR!3HQlO&DMQm$o);ji*Rqhn3HAk0XT3fLtp%!q&37k}3<*Aud@U z`dt_Ge0qctrrIZ`prgKgoqE+irU{|%yxO@6#n}eI4C)zirqS~cXE#ZqDXfow zQVz31(|4A%<>3ap8LDy1^g}-GJ(LgNZHzZkDn#%I@Hk<~Zc$2ECHK~S`9i?(ffUg! zg`FI6Xtc3f6n4T~f``&yjy+Rt}@C5Q>2bWp1KK-6N zE0@LwCrU>2&TY7QHn+OXOU_%zZhsr`^Or*oFPRi=gKT|sB8wPeiZNjJO~YG_wPH?) z(@e9*K;g;VoY9GNk)!OFeU-81{Gv%h*^PN`PC^7RglMI9p?lhT+jye&cSCaY0sY~c zei|lI&V-)LV-UQ*lHI$vdmQMNsIj~5wacHrkyv)|&cZGn<#-mz7iLF7u4Br}T;1Xl zrk=!asUQ7dDn!YHabXNr*6_9rHntF-q!h9|!PXt56uZ1bVB9|PHEz`^foJ{e*sIg= z?9W6Uh=|?tZW78Y(c{2c+BFS!9w$Y<0;5T0>+~%x-L*YJQCKLkSG!WAHi2BC_RIIX z8;1IEtF!vDh&lbyXI${(fC1|wZqFvd&-q;(w}rdKn*g5tKS-8N+LlRyuE-37E}<)U zIHxeap5Z*&w08tz-TisoJ+5H{xVg_}D`ex(yc>-s%AeX}!tXqaIeWES!ExK!Z-~w; zF4}B>DI7@1-#lsTy-jMSlh0wRqw?5H$Dgp3tNjPD7c7)ebZt*dH{Je@$gpA-D`t?p zTxp1{%z%09Rq3s1IWxOp|kSN z0it8LAmc*x!3>tfGFIBuH4N3l$E<65dfq(?CL3nPv{r@fZ;1CkGu>VB+U+XGBOseP z@<2Z?A9*iV%HD#I&XLFRDzvn>G}Gnr+r~glRgc3Z{%8Ilg#-& zq=MxCzEctX># zXawHIVsi4LR-xT<;AKZ<*$#EDxLvP_U@Aapb+tT$Qq}qq+D`Uiar)uzCqjK8iVg|| z{anl)6`$^y8q$`X16V`U{CO`UJO(^q;J7!9k`gyjfS*TfDQ3iY;sLY-3H# zb4}u;D?N%*448ec4u4fC>M6n7rSiNg{k;3muIUKLngGksTwR}xQrpo*O^Vc*nA9rh zHb-?%MCeGjTh>)!0@TqI-7me^O4;NUcHN~SUnCDC*DCI|c?bt@a~DP1HFpY_gD8kK zEd?1%SwfU}J&P!XF=cK2oLbhztDB#Sw0ihVM*>d{1BK}+m!Mi=>6P2oh#E`V;uSxX zxj+XZ;A9UED|~FSShGt4YB|y+-GwUI%q){UG>PRuI3EvL7|Rvd6t;?Cnm+ZWh^4@m zE)33siTAI?b+Y+iTYtCUvHF~EZDQH-MmDP%=+Q-38BbdB;_-*m zvi)`i^5Ooqab{(aYs1Yg7#@Z)<%Pt;ELtGY_Dc zgyOd2!oR%O-Vnr8AMh06hjus*>%ExHQZ>M0)3mk z;2HkvRvlojvmC-rR-oMsP6?vwFY_Xly95wCTYR1uu?g14QMtsptsmc{l4)vn>F zSC01oMA*jWXmf8;2TpQm!u8&KGNQrGPzJGEw+VFMD`9vTf?1tz@zI$huO*OZb z*2z|laZOil#JF}MN)eO(CmhAnDU@NhPJ<2F+}@4XuSa4(;{==aQMKPJn&ay00p_f* zsRaWaV2wPJH5y1Igp5Lw4C062wD2TPhp_0xzxk#r^F(R3-6vY!u(4&^=70aS_hJ|n z@897t*EC+<;b_g#7`La1vglNR-C5!1&(-sbS9co{ynjeggp-Pr21A`W1O&QRd? zIPF*Ms(bsJErJFJr5cz_krF~f0N_FLaNRN(xsN?N2_w*kff}`Eo8PVPKUWez&Y|7 zc@&t*Q!HON54!3o5Pu!UoAt(tU#WgsYTaJRVjoFVk=vNW!Gs z$UU!{TSP(#(=W>R6YgK=smFJ9G0DTdWatH_b=#me2dfvhJy{5dF!sRCv*{Cs7G%sT z7!13~5VdT(?SGO}7wo25h^}>Pj}Wf|m|SuM77W+C=(LjwJn6_%{OY6e@-fHiHcXjTz- zMW?BbsL{dVLK3~;zc;tVuFSb4lRW(N^Rv05oqkjzzC3DzwULLlL8LoJ11Y6cbF*S4&ie-q3sao&0h577RIFa$ zQTlqL=cOZ)KsL3VgJl06xP*C03NSmYAr5*AP5p$XZad?NRKgR<&qQ)A2v(!X>(}pMBX&+#J`j5WM$+Q; zhq>@ySiM~=-imC^Lm)$bc65)l`s`n`=je(8>nFl}j!J^1=x@P_svad9wf5;|3oYd% zA!ML&m#_u>aYff}7}t9njl-_EFvzU3EgQE*>|!3ydu-~J#F=VZXv7kRy#`|PPK%qq zW(%M-T<8ewNf%2?okr-H3B7z@oikyTWd85J^GSKbeU>ynY-!6R@CC?^f|lNeNqU)z z+OX02Pp?f2hC}u%JK|Tqf@M5Cq(HfApV}+;Lx=P4x?OEK;Vo1`Xlqnz=5`5BvT^sb zP1gKZ%Gad#3Xp@*O?WosbkPeoR_j$8@A|B93%o5hL*vLM)$s^`J-H5hR_5^om_J1Kaw`&ro5l)F~BT9w_T2Li;uFg!~%o=QYI_27QqV3!`>oTbW} zu!-^&>(F=abb2b5LL&hFPejk(yh-n`3riB7-%Y<*7g~RSKB}NHUNB`HVDNci4^_w< zpfNbWv(&T}_TO7c^s{a9lKDKTlOHndiqZRtu3RGzgky_qh#xvT;HC|N_cP7y2k+ow z4O6F17XavgDA!W!1@ykz2+=HQClPvypwt*Egi?qzC_6_d%AwA{B26|Sz!NuO+k6if z>ZN(@msQ|Hrlblf+87!6`dFBIg}&3`-5I7Uqsy#?118hL8I7#qp*S~S5O7J7U0{7|^h zven_rOH7q1MoYia*ji}e0eZ>a{#$7(K%$GN|9_=m6jmXRXcJY*AFM?r%A#vy43Pqo zwc{ndbjO47D7>53>yMpwEobTi-T2cYw(9DLVOR>1pHP$&wId*g3vz?;AL95h>{Uh;hjzhU1XG&1}Pait%ut%PB399eybb>U)J{!>?K}qqjGC2a* zaOR%U$xwk#@>LSJW<*9OO4#&p$M!{uZN8p-x;@&l&LPIw{+)cSWig07aFP*{5obw` zeb#?_WGh#0vgmYs7A!vylE z!u6;L=|)B`D(8Cq;VZ10-(1c2B1dlSy)zn|S@gbyx6fhIg!^vSrHeKpJE z!+l?h$)_@CP-Y>dK+WkZQAxB?`B*l6&0t}-qs89+3zH95vuA=;x?Q ze74hXTflCcs7~8j;+{}C5sG1Z@Q8V+8D})&A-xiR_g4I}l;>!U+c0->_rBs-=>+YJCP+HNe?8$*+Fib0cUjclB3{H_W|yKL5P zjr$AX7hkxs4|%|!DC$n-k$b4=u5!%z?58R$J#al?j_FsQ>+vmG?Z0s%r=X4*!!cZ6 z`LuC$BFH^qPDJ9AERK~=WyHE;PS;5ErqsAns2gXkzbkJsr)hgz$rfVL*1_8bsTJX6 zqg_B{F#c8uumf0s;+9fjgIHh|X)GwCIp(`I_h+CW(??mo6VX?lpp+!z9QqbrhTlmW zb{W}6wK}_0>b>WvpLNSohz_}KUAl{N+h!*()1(kDk~RM1%TOU5j6WV*Q}$GABe%G(F~0R+DfGd!NV-C?AAI2r0v#r zZnsVk4{33-m@emPm!q?jl~IaActc)1AG)qo2DcjOx81VAT)(0!ggxh52v zmLQa|-uwAlMBAsk**7QqOGI1Unoy^;Vgoy4WkzQxyj6|w9JeU`4hsA)D2sz^AO*;# z03OQO>l(#cuNPZG^zD&XzH5!LMo2W`L8a_pLuzNE!au2XX?@DvnA|y_eH29^mIvB*hSA!s#fCwaW zL455>0~0`2w-z7i%qG_9H_qly>#a%GQ6scgi74Euuf_nrjf1oVE_R=t3Dl~ia3(`f1Art9h zh)4Md-Mbp7d{_o8r#3D`acm#1ZhU`xxN2K=4LEy?Hgt`b`@PB&=qu>M_KCSOx)&Ea9Q^B~ zW;z(#*qjDpLVhLihv>=b9O6o2LL|t4x=phPu8;kEe^V{{rJ;J#`4e4I020mLxNe?; zlr0<tJY-c4ScZhL$MVQi~*qfrW2)xg%A#{3Yj@4`v$)%Q+==I zui{(&yv*{D2q@Gj$+rIhw4aqPWg+l5j^b~X=TW&8j^lRz{`X5{6eLMjm-roOsg3q5 zF^oy|w`*>#*>++RYkkUfYk;j~@`)~@SW$TV`Qs6KWk4l3FYdF(HWc>g7cYH6-E-KCf43@vYjn;Q4Zof;WZJ>Jvp#D|=Ze{vCcKl1#oR>W- zwwml>ib>5x+cCMyiKVK-PnWb!@pcu|*6Glv5|BA9gY{3f2*Ig^hUDZUKmd{Cb(NG$ zp>XVRAP|ntbO9kON9d1?6)PWB6KGNhoAyz&J2HAaLICX~3ugeFD#%xae}|FEOIJ^c zF^pv?$mAK+CxT&)HZx-WwCP*UYi1sSIY3CJS&wrI(CgglL@kaUUZ!%_*f)W~mgZ|@ zDGS1?I%k{X%J)dwmZL<)#?*Je6LOGAbt}GGa$vRDuWatoW(*Wm*pl-aX{o7Wwxd7R zOvdBRj_p9W%i!~`zp@2NDaOcIs#xOWqpAJ?xTFNb4kV?3QWcLJc zm5z zhIq+Iuvb?OPi6k}h1KWuns_C=yIaAPiU14h`y_qqq)b=2=Gs*78z@lOnT4XhUjdXq zUcDCMdd(jQ5Q^r#V;q54($23P0w;m-&_ZpeK?%EgIs3(_URJC~>K@8u!TFwWD#`U<5NG(_iWr=KS4$74&hNmN3J z{*_l>rpe=kqEorLRnXf%JQxoaal!2+6`e5d=UKG19_*Ldvuq@gtWC+Istf_}W7qm{ zL(99=CjhBPfz+3}IU_c_veT`2$C7{Q!GtFtYbJtV4=~gHJ zKMe$MPv|}Xc>u6ch+^~roKd_w5Dj*rfJKx)YHksrEpRF1z>3ApSN+^5gHry$J|}(j zBG*(WN$=uZdwlS+v+GK8`_A*4wfRa(CLmX9`~Q_Bzif|QTwx!DKJEpGlnZRXr(rq> zY5>zP9b)KpWnaZGw##Y76#boo8B6~JAW;HCFh9~O{+f~b>zh_t41I`fSy{iynNRx3 z=?}LbpPA?O(XK6tGrP%vX`wpvN?Qq6A^7@6tO5^b; zZ;B(`Z>^Xfo;JR&Cu96SxZ8QVxjG+%#br3P1?;BIs_!j6KgAu4Mh(gj%4v3Lbi}dc zh^OqIE{W&XCGI%xuvw*AqZRy}voweTpKY9t+%pzC-@_~`(^560NZs}K`+iN!`p8|!S)7x@(^vU>g zS9`jV&97vN!@N^Zc6-yO{Hp^&_m;kh_f7k<54-bHp0S9hX%_68m{oU_uV{vH<_veB z)An2?%1y!~Q*=JGEkzrA*xlYfxaEw;9as|}A|)*Cd%OaFM$_u^@F1<>qg9OUC(Dnp z++jr@zJ-NBGVu!`?9rZi2MOy!fvHvYDnmKV+40He4^F`J6L?B)LSee`N1H^`WV0j_ z%BA~QibeAIPR?G@o!#Zs)=HXbSI5p3fmj1Y$U%2KUa$-;Fo|kD_vLe;avKoRqVuP++~Epdp94q zq-6l|A#j3zpFBFyvF-N!#nr7peRh_lmTeR#f$)ydV@KHbuZ7q#)>2(4l_fQNv;ZdM zLy2t2RvzLG+Ir~f@GpedX~gP$gWi(&u}pA`v7Y-(ptkK8hgG5TqX3u7>#>z7g`pM1 zAZT#4UiUS7rtURd=u=|dCniK*t;n~WJ3Wlz5z7?vrSBiMY>*vG9&eHzS3E<1H2Az0 zcuKpfNoOGQmS=nN@L&$qJT4^?SRa09{*Y$6#5c&7YTI;vN!2&ZIk++m5rUMfP&4*keKI`Hd-)4&X_ z+DD_bKWN1XW+Q&3j#sPQh03HpOZiYV;iY;Bt>6NXF<@O=tZ2z@TjgAe5?rKywalSq z7F-1$y8#NU0?!i_x5IDxG~k?DeijU+0G!qHMBKE;P>PaCTmz&*Z`Al=_wfaM9?po8 z{$%|nC}Z=q72xNV3e?|Jhv)L#^cmwjihDF}N!G~$lfYh`C~iiE^Jqcz-FyaCY+Bu@ zM@2rHVnP;g+cBZj%F<3bug{*h0mEaDEbnt(HX@dR@r^Gyrv7TS1b{#T>zn|EpPl^k zZwEj<(HiYc*UZwz*Cg7;^R9h!jp`@$EU|1B99^>0(N9?g7Cw54+#27yIJ=G6wQM3P z{;pAY0r2;Fr}ZbEy25*XcZIeK#Q8HMCflecU!F&+2<&L%7AVYv3`0xtjStpj4(3PC zC=}2-xsaY*f)-0MSCKT+1SK(BOg|+Xya@dwOGK*XgFD{N!7c7U(N`0VdUWLd5oNeH zWSdgI^`z=IP4WeYe$;RIIQ+)afPAR`$Y+1n>SB)c)v52qn3H{LAB<_;2u9eg7Qr12 zwlF1g3#`JH?4->xCi=Hngm8}JSLkxN4JTefJe6s zEV=M0s|?mX4Ml1tFAZyT=)jW9SNTF26%3`Qf>^vI8#Vo88E+M5L59LLMP_+Pf5gU= zGhE2OuM+vAAXwn|H`#w_d@a@ARleZZJ0ShlU!MA-Y7S0Im{cjdc~60jvqY?nEA`mh zpKQ}U@Nd}G%Fh$_uH69ND`BE(x-D>mws@UY`@VpykmyW70(LDRtVks~v!^buKfBJDT4_DGM+sYYde2z7baF*L!3X9sv*{$wbFYeRTPQp1G@1I3=YKa^2A z$hkPS;5F3chTPh_NwhI~z%K%q30i9d4iAGiMJrBjd4e$~+nbCH<2CM2|Lm;$cQw(G zAmWb_3H&a}xAF1*@v;-+0=8HjZWrH?z$v&gqFjMnv2p5h2X)c%IB_;qB6ykq^k=I5 znO)wHIKT>AH*;7d*(NR&NN;1k;q_>$S^p!`vz-gMbE`6_WV_Ci!DqnJVWKwL%TJG| zhu7*wu*o}wNcn6%MEGGP#{@^?(*fR2`f4t{HS#Mi|Rl$P7rafHB! zT-+f5Uhz`xW%=(7-^RQt;8Q6-7l9w!5uHTgwCu#APE9gRukl&`^dSIx|D(-EmI)?2 z!oci3iA%M)3}U~OryP2O7)lsUANTHH@W#x%ZM^P%740V~F-Z&hnruRm=eoUB$wB>h z&Cp+oBd}(szia+0fYd(%aFcdk+bZ_=@)=*6+kCM8$dJaE$S&GCCUT|faUx0(`0hOCgFd&A|KVmTdyCs%CTZ%{p`HK&&K~!8fSW~*!YGoKk{uFU+PO26*S@hl$w<*BYIC6IE z3eMuJ5B7Xg?*s5N3pvqs=483K5a9R~{Q;echXC&RQtd}^nhBUx;CIHiQ)Tw=wyq1; z(4G35>;@oX`P2N7F553m!UV%kd4nl`r;hU!pg-}=_BYG)Bh=!ro8s(Zzw@eAlNexb zj`v_hzKJ%m#7HBRxRiB0uess*(vdm-ddR1hl*!i;h(Bb~b0d3WOAvV(&|mA6aG)nV#gR*K$uGUaz# zcV+5j+7s+7jQHkAGv%MoiJ(`xg^8rx%AtQj3JlphiYOerUXa3OK z$Ix!8;f%r?U1vvjRI%1LQmLc$l*LUJzVAQ?pAR)=|7G1`q97DT#3EP>=$FK7S z7SDyGKQ}kkt|CGaL@8C(1e~7$Ay^f!{)RYLc~Fctx|thrH-=35{&36zcJ-{HQ2th` zt08hu*HyNnWc`=ETq5Y-i|^#>4xJCu`eOuMM%4`cPv#dk=hl#GCGv-h>T5j!COoBBBt4hr_~R*mxyxC~YMoAU zZ*PU~-=EK{%EL_X%0SE5ooV3R@W6S8=+0jZ-|H6Ky^KpcYhHkvj^HGGaM{{+u4k{| zQ_84BzyC2+b%T<1H8#`Hz+ty%sCWawE1y+&=jt`MQSIY)4{q<>+4u)lCI07xMS&B> zATJ-i!GFAsGN48{8{lynpd^(z$tYrOUoiBZc>tM-e^C&4R)9O%L{Q#$wL`zUDfp3(QV@-%w zh7`B3-mU#xJD;q$74i01`{y_NSe@|{%Zjq(1d_9FdMvKE-p-E`mTn#$?(E=A%)Rb4 zzs7ZANd7bJoJ|1T9g;knHAvI>IWj6vJ z%#U)c-iPE^eN44tG$2}No1S~1$7Y@ulLgnJFyNrpQDYb=Si*$$6bwKQ3e4m&Z#mYz4%*6viK(l&#`_|&>y zO-@L3tMi6tC)Wf}nA}0cJoOj-Os6m~D8~R2mSl}9!#7@kdulVc=ePj9o)pn9l}z3u zl^oIUr*K4p)*Bb3xp;W;v{5em?t>6ggxUb_Qt+E7`c4Vh%_NQ|`HT@pyTr&|6(sSl z5y7@rZrfEGt5oeb(K+4`QNGzRMsewO2tR3uJTUCDmSKQXSb(9{C*zbv2WdZ-d-ax} znCurwMem$w_dK1ZM zHqj>4GflPd!W6@m2D;g_Wy>aK$Lf7N1)0Xa^I3m~Y!6GXq{v5ZltiQ)nKUKH zWfSkdt%ubVshl0;$&|0~9(tD+4I#EK5sxRvytXL~>=S(xIH@wp-oj9cy8l zbgLxI*^J2~X_vugG)rRk;lkAmQ>XmTl7g}YYOl`Or2RM))~hzc243?TP_&wt@jb@5 zog~hUZ#@0V^(gBOg17*El`ZZQo8NQFYy@7fZ?ulp!m7)i!gwLc)-g@i5Cpl|gP{U7 zVmrQ$z1?nZYa;am`hqx{UXB92mszbN359tH^q_X$>+Wv1`GOr^qs91G^=WBts_Ar zx`Wp-nAApDpj3`{!$d`HP`$fBZ}QdC7``z3lo)o22}om?+K@bRxY7)3^To-GRJ!ZZ}A#nLhgl)a#Z8lecMU@i7F;vYkFt0dSjkX~l0`FuUq{CSc*6-eF! zwS?h>uDf^2+i=(K%gTH^D~$i{7o)%EZMz%}kZ<$rzn8abWD0H_4W<#KrNiKlmu+ zffGJDNoL15NC%awnFf@rB!Dga!!?cSBF$7kvj@(sVK}Ua3~~w}AFUWCAI?s(tP|^I zSbs{g&C~$^IsiblH=ZPFFZ1H>b}&-!-jOwr?jjB^=j&1{AYQSEX4aJFiLh_6AzZbJ zp-q3M$u_^-I(Bqq#PnvFx3foJm_w!>^xivph@N3E5Jfuk*$h-LO#7riL&GwW#JrQZ z;`OD%88Yc8MK52+*cLiRDhRy7_)Y~)W za6xc)rZ-h#(@k=uN=ET5^Xv|MAU1N6~lV^(}Ms-+NX!q?#7=-E}RM zM9U3jKC+o3E(A66M0lU!mwRI5i}?*&`4npTWTyF*zS1>M@J6t_&LuY$OOH2MSwqgQ z_V~Kq5&!IDmIY0GS!v>wqqEbG!GfW3YYN zk-I^tbE{jjw6gl%x^bjU4}V@(UGnKcUSGP&+K_xWH%4D@0pM2_jCL~|2}hZ0vJ9(E zM_ofb4i@-L^aEb%JoTFl{t?g5Jzq0~t(d~3=PlP-snJlAz$}bT94hvC$8nD-=Z(KY z{#a@tQ>CyJN0q!Gul!Ux8TKb(aXQaAB&-RzXC`1ufllRG%(6|BKWt|Y1lbtH8YJa- z+|bwCb-wA_)5QekyzY^cQkzCURRAf$HEr7*2@?l-y-q5SK-4Q|$4EN_IAvA5bgg)m zTOw7a_&nl=8e4z9B;;d$?B)Xn4f#BIfgT>_p(52}ka-gEsZ@W}b%oMral#4_F}?j?UjBx;zIW2{)u@YBkDs?}lo>~#N0 zZ<$N&Y2(^Y*=*wieyN08Gt&?~9ooZvx(#!`$uN1neujF{DvnuuoL*|MlRE^2VNSJL zB>$SiGzfgB(_u3BsGCW>($pU&jSNA)-XM;2yU-Lm_ZO0|j zZroD>^`aJs`|2F~xCcRICN{I_Av`xr0e>-2(GEYS*35Hp43n?2r4HP7mB{H^Q49pJ z6V6SqEL8=_h)kqoSd|Q)R;>|x7_FrS`kwYbZ6_wS(=1BfRXHq|?EH!e)hwr8o2N8^ zS6QY01N03K{!}<_ysRbpQ^dT!t#;((h%4rlFAFahE#(_AzVPXJEXPA*P;L@uRHqbr zw4xYh*Usx4e0mNon*2d0ofNB&42#c82XB&Btk>wVtJ@#*6iWK0Lpu!=NLGL#J+N|X z>CxH2oCyVfnH#lGd8y>t+E)5S#p`_>s%+*pME$54-QE2t+55^jibqfAaosb&G zK)8WFzJEWXv=&=)?Mvy(DO#Z>Dub3c`I>n{jd0z0ig*i6Hl`s-4ZP`zhi$M!B|~R6 zeSxTD&0^|}I28=CkfKT(6xhbL-ck#)%vSSr`I2D#$>C;ajv&~V=H%f%5m(fkPI{O` z(#_NB_#)jgmc6VM3Ol#V#JkI)whuzeO@Zd)%tMmpxHpjeuUes;X1ub%Kp2P0`IOBh zX$eS`VYR1Zqw{Vr@XIsrfi|0+(PQ-2WUB>2m|i1qnCBrLLSi!5GXoKE-!05!^!R2H zu~V78jS;?_e>gndQ7WWH`-{S>$U5UsuVr$gHEGqk`Z}1)rO!L$48+6c(=SeiYbDRR zT2f{3%S?vOxmdR8uQ+29K&k5jFP9#r|R9; zMU$KLuE7dEh{NF(MNItCng&Z{C>}RMirc?um7GmyO~ZoYorT4mokJ&Q+Cb;5O630n zNXeSKbNSxghi;izzb;NXQ1i5s1UTnohA=ZdIZWX7>8N2>JOt3d41)rX$IAN%h*fHoZ&HC(QoS-233!~Z4WN5b zWm-D7GGhQ~Z2l7uvLN@*i+7UrbBTB!Xc$BNGc`+DBkYV()FIXF3v#wmZxk)}tQEa_ zf2C)K12mMEz}~A=VKh3(uELg(OKC^+vs?HIo4TSvi1QA-{?m8x}J3iH9Sd1)KJb8rD)3#IB2u8spGbD zhckvIVN6>?z>a~|vL!%pStG3T^(|{IUpW;G)ntPo9vbMW7w`0NWy=jzi2h4dl-^}> zXG17hUpK*0BU7LA#L{^#fWH*pcq7SJtb?}=)8$^EdF#?%e|MPM{+GQfB`9(hWe+q! zuA=v;M?9LIS)B=m&6klQ$sjtEEAwkh4^7kjtjk_&u~VKSh1r!LcBEV<>8oB}B_lp|DAT&e}#h=Ze92vvO6Z zNQ!$k=)G;cqE^V*jWV3*smq`@)#-JGY`r3wVv$~;`_&fVZdPqeA|j|dORIAEj>~GE z=xG^l?1omzQ_S_61*`4m@T)f7&$5u&lb*WX&*)mxtVh1tHT&8TH9iJcpe2%&%A-_S zHt#=Km6}*GU11nzoM7plWPg|L`as)_DvLQ>fsO+5G06fr4cG>V{s5ps9$W!U!r~}# z2VG3VxBor(npBs_I>@)Sog8*9rmdyLa`&PDRJniE(WYA}!QP<$b*@R+^T}KuEham` zwTFn^VEFV5WQGHnvcb2)2ryF}4E`@X$FLjP@bE08erZz`Uk<&#Yss+c<^nGkL>zt7 zKe(Ub-dN=C#s7JTnHuK?o4}dAOk3;e_Fx`6_f*U2i%xYdtv?rAH1_X;HD}lY$bh7{ zrgl@n+Xlmb*I*oCPX->>EZEzIYxT0?JD98Vj@hO>tgBqRk9{;lJxj8@M)niCM7c~d zOFeT6104nxyZr-dd0enCmu@&go1tPu6qOnkjq&&wpz2KqtZRNW_ly|LKEMji+ySAIl?!!;_-Czw?H)wpb<*b3L)sWfMjbKiI)2%g= zGmv46Bo$~2Kgxr=A@edZ&*@u}&BQuU_b zW!%=qDz?LQ3tZnzAY8B5#u^WvdJ(d3wk$1wKnY5&Ey(fnFuRdS3fqWgq%ch$8d}%g zt*b=s-K?uD)O!yQEOWmmg(CWvJ!&j$kScfv<7fK;VGSQGKNe~h8RZAYKMS^M$LKIg z3X(x@IYY^+KG1{j4;f8Qe8YHzAZO9nj(yqmKg#YGC=BuBZ34t^`Cq)UF9Y@|P2h1U zwog0}nN+pcCM1AaowchlJKr>Lp4*FxS2?PIj;K6$whfBZmbyg4xL{7Ry~>I;@+Qz& zGXasw-94@6b(!Pij5l+%%L2WEUEjZOE0qo?ck%NkQ01(s{N68LDp#zP^S0nWz4o!M1D^+1!|m?JK&8izhCjEOdNgX4_~eG1MINp;Vy zz0X%bKhyMFv^8&T+#Zu7KES~x?-D%qc6m8+cg=Z|+zeK0L{z^pzi&Ks<==yVn)i_V zO_x=#OJ2vz`*y#XO_#1u15@JKXVwm?7WZN4?;1dt@{St8FDmB>IoYcUL5!2kz1?4k zEz-xAcwOg2zl5Fe*1X>Bl0LW}yT`&QCCnSTjexY9 zdE02`bN3d$lrH4zh<^E`7+aQZf5R)FS{h*HZS%{<-yUu!eTrzK1UhW1rlu5K>VBd- zE{T4t4AaHCMk$on&?I(AO*?Xx)CRx{U{<_55)cLNN$BH9 zJ4+bzE&iRoq_S?GWkW_i;|=3wr%@yZuwLLM$`t>zkNT?&3g5j`-`v|JP73nW&D38% z0XlXu>}uUHPv>lwE0|lkwD$)%Dr!=UBH+J zk35uNyy6i#!|dpp2ak*|-q@y$GzkRhibVEXRzZsmbNR_Ec$n$Lv&M~H7z~OymRhM4eV*n+xnzj z1+OXgEobS>p{Z*lV;sKs>MBn>V-&qB-|M6}Nv3f)*l1@7HdMojIN}^}gfNN8+Nr?? ze&-=MDu78+NE~7%B$b)6N63D7^zcO3?!vBFUVzZ9nFvj|vh3xd%|v4`MX+^#^C7Zs zP$D{YRdh$Ybm#8j;)jW?=5W&6z- z5LciUUhWdE#>TL*Teg;+lB=1gpQ$zlbWTB&xC6jb?K-E@HI0=KSkI1XyKxqe9;Bq( zWeyFHPhK5JOUYe7VQ(+fz;@mWTqV&)U*jlZSH}`d7h}bU?RMR@67DaKjS|=~Io}DZ z?8J2WRP40`m6zEO7nqCnap-tMkN66k6mwJQ00|r){Atzv@BFPJ-?ZS~;^MLaIL(3t z>gEmpxb(TfvZvMOQ1}!ZaA!eJ)I|N>1x-D@?kvA1D@1=`33VLfc!S0u(6M0jJz6V- zC+&{2czV1M^)h0k7PJz~Ido^U72tt0tgbQ`787tE3#mp@(H0>f(RW+(>XfR~y1B#4 z33i&;*X)KJzZeA8-1^iZ#lj3brb>v;E?tzpnK>wPgk7j^T>eUSxqt zyye{7CsmvGvch55iI3Pq3_YWLU5bEFrrqknK@Og5X-bW@%~>RqQv%Pp(CbEVtm92( z_-Eb`+|A$+Ps2+SkQ>B9>^PSbaEI5sjRk7C!3ax-ChtZw%bb0_nZ%`yBdn-F?cM@- ztT3+GFwjcP?t6Vqym&feujpL9=IncPzM1cNS8J|b>2XZ@=7MINT;ut7$jMv+_4!JV zmR|Qzp^BNxp;wDf;=cDenvS{^**pdTy|wX(JZ}_QVCBs5gWTWS{J&6`S{U#$<00nr zg?rVYcC+Za?>Bnr7*eFYj|3Nc(l>kL1u_P%C;6U-kMr(0MVulQsubaj0DWc3lv3a% z+J32%MIa%d%I0#2w(KVH@>zlHqf=-X7+?E|hGB9fry?IFkzx3vpWr%_0v=GpkSClugG zL`2HTH?hT$eQST=)n(;mDaxLJs%bqnPPPf5lW3cX83=g`oH5M#XYo*5)M=5vABwT< z>p;I^C@*wDED|8E%u+7ix~$>?^r^zLg?rtZ0OgqHml^!W{rqk&u#5$xi}-( zqZ+7pm66`6{G}vCTKoougGQU{Zy#=fgGYpTz`fj2h6-f6pU--s09fP zal1Cwp-JG7-gNMG6A9$Bw%|3`rqVG_W&fo6(6o&c2V?Q&c{?HU8E#<%&73nN+h=&ZGTO5v2-2s#*uO$4j<_bk7u+cz0v~L;5r|Y$p&g(TUK2TiZeNq9q8e?I zw<;oMAU*I7?o^EBNc!x%>&?c{6N+F3v+W16&ExFdmae)Cu_L&1M1{wB$!%R>i?Eidev zoi<3O4kjtAs|CyP?g@0U**`|kI^mAT07Xv9k>wleU;tGIS(4(mh;T;O-LgG^zY(6< zXEW<1f>q1Q8I~20s|c>db14NT*9=Ws-KeMmXRMRc8>oN5-WOd#s4o7XD|M~E7<)oH zP;wX#UcM~504*>uJbjLk8f)*XUw9aRyg6D0Ra3;<6qNZ{yt0^q^r!}^+pQx8`Yv|o zyOwbSYk8ZkE_V*LqK+xofhi!D_vSC#l0anrMZ&u5M?MZFGpd%#f?qaxufcc+9-cI! zT&Z<^f6z}prbM=jnzSp9dQLFTg}1|nCGAt@p<+=_RmG80JQkxa)u5c8u}Qawz3wLG zD8B7u&?qKc&rr3FNwe;2v?EG419;tzo(+L^+hv8oNlJA-WL=O%IO2o@H9H?=8e>`G zn#S7JY60_)Qh9uFQ0M#BfTK+AEbNrn=vp*hU|p3sau#(1P+VpGK9c`cP{-S&T@4)O zZ@csjO%RBZ5-PY5C3@OU%D6)D{}J{UP*JY$+W6iE1|pIpAfQO6N=Yk9gMf4lY`Pl; z7|H^qMM_#a28L!}h7geM92)5u8ip9^dq>&l{JwM6_y4b@vsf_7+v5BJ{#^abTe@MQep^Q{iz%BQ{4pnJ@<}3F#@)OB3U~#JKSH{PW<)o$YwBJ*C z{Oj^qZ|%WhQrWGXPa}uATNT0<6{TCet;>@|5(mfnUPF0GKG@c~mZp?u5aZ6&UY5EA zukNXqP3M176LX@9?7o;)S(O{r*q3T1nBz)q6pDDNOOBQzF1V3d--=Wo8nS9J>T#+) zJDDwz;id2Ocy_j0ZoyE|h?OMz5OziB6re7KdUayM=YO+(OI_W|TuW=`p}gnApFmM- z+3F#h!Ep-WNj`p?Q^)?h?v&)LlLp_4j%J8GGkAIwPP zSb@PQ@Z(-(Dr$7BS-zoRLKehCu5zurS&04O{2R>MMm1%?Phx>%+Ccleqs7a#Q-BP+ zES?1B)?qW?(iL26&eS5C4)tptZXfAx91vTYA0>XURXrW%C2~&WSN0<6pA40cjN&s4m$D+#+FB*9zGlu#?>%J6rkk zO`UzpjRQ~AQRNP>pv!r8FHbMMt4*1b?->J^F}E|nWhS2bn2OI6b%(Mc-$L|>sM+v* zfMq3V^x!i;G1dNyp1~adA=04|fBVl?00p1RgT+>$$of zs~uy>53y=h0eEk8(}l`C`p)~65JAI z8|50djd`#>a*6^r!5|pfe{z)QMHwi12Tje{T5SEWcPmCXA-3z1j=Y__hZ;s_sr4&Z z0lwbK)L&&dI8S{aO7@sv9g4LB*&$%&YkTcHES1}pPVU7fs8NyU6D43W!#B|P8>GNC z{U4BSl;8z-r2>--Kg~o|l<}8;NKHsyjx*^?*zu0?lm7b=@wU7A+jgy&qTRwv_2akg zp8m02K)vN)RAL1BmcVW;-C=BhAwQ^GEmChS-dbn0&>sPsY_OM$k&87!WDhC%hEL5| zE!sEPhfxnV2Tww~11Hn#h$OT{^fPr!S~7K4{9LZ85_TC^)}2v*HfSq4$F9x5&-W!! z;5N9mswvt=+*AvM$yFWY-V{gxN2My~H9_QJ6=IA~l)xkukR&!MC1_pO$*lXjG%CDi z(Ti*Jd6xb7lBL?N8Uc}erc{>{oPY?=S8etNUa&q=x3rl;sxPi$8-B&Il2>+##73H?V!v_wnwM#bS z1t2)3&$FvOGY;;DIbYl<$#4KbLKsbbz^V1~|nY06w0bGokCG zyf;wNy8nJyyfBNu9gg&m!!_Zrt@iQ6f4}zirH~NwSDBt38=0WfZqdRADB?}GL&x2X zZZ1AE5XwJ$-5Ajq4PK{AC=HpCRD(q7(YMwtTP|8HIgl45?%Iw=QJKAEde&B0H)PyW z*r3+gOOkip+((WN@Vn>VphT1Kx_wDVOk1>^Sp1Hy?o_mNgZa5;%Hae0)`zIReXuPk zp)O=Ya?rg5Y>DIUhSOhcbaS&9x<`EUBHlQ#S#(I*m(0{GuwEkA9V8&KxD*V}5XEBg zg0)2eoY1!6@3Z*3wd%xyNTNQDq}Q*754 zOBQOc%HsS8u^j|(M!gw8&I-Bjg(C^8{gnYqms@FtOXY42uAZ#AFM8@38=sI#F{G-e zu*u%1R`;I~w9FRzpB4Xg<2OaU6K)oNCuFNK`)6;x&2g*PEq8&G8vSpnDs6U@q*0Yg z#YE$79v9~yy?m0+9z@iUTHnh{J||zNmizYu;|2PKZw);bx(p&-yg)TTv%-c^=8w4? zA=ZOI)M*6ovv(YxC)es9Zq4{uo~U+i6It9LmHls#2vhN7OuUrZNR*E7*tAnE)rGa> zN0ma^n9=!o%MR`{#4OdY@pNH>W=IX$E5m_bnC1d-D74>CgidM3N3=}7Kr{2@%J3(_ z`57m-Qn#{VSCjuOn^oM)#sJ>f*@=kFrTY~#ox`nTGmmoRa*eFJU8`oozF+2t9=r&} z`1}w_b?J$Cz9|s|FeLx8%e-^Z6hI zQGGgKo8Cm}3+Ndslt|3mqqOeILyHv@K3LV)m#c* z2MCUtz$eVdJeM|w4JyBeu}GvIeMO&d!)FdXs+k;$_XI?i zsvd3$n}mu}Rcmm|ZBYWYtM?5IQOEqGRIy?FS%6Q9`^GKYnV>Vo{OSgKoVRc-KG(Ow z1?&^>kyU9kr;q@iiy@^Lg?k>#;)UYip=ogtwWBbnFSg(eIob010&d`8kpLB@lti8` zylf)7G!%ZA>eGogpy_2^bj17U%rm@i>RkG+=p)1@41gRbW-V?#K4 z83;6<@(C#krH`eU>6#lZ9ZVd}FsW2!U!fg-LmKxRULcNcAnoJxh(eh{X-y7zXdP@C zXTOVdF;^W)yL%3|%{V-(Yela{&^GY+V;bO*|K>uxp?ps8f-U@;3-N}U`JF|CE(Loa zrKRY_tj{yrhL~@A;}CcCllCF!*+S_VHzH|GN6;M@2f%T7bdp9j~tHvjyECp{XzFgu#_|K;9LUMRJy{l)-mIk zO&wx>+Qqdb{f`rZCV&icV23WXkvPkUsa*T2I+?Ae7a@e(pyh?8B03|J{nl?pUgS!Bhw zfL#xF?SIP&AmpC0jy>zf5IHxs{GaTM9hsZGwIZny2CWp*v*@XYiCJ8d?t94h5!pOJ zAjKHwqkpU;>!N+}1xgI-iitsPJ}>uKy8-wx2%TE- zd6aT}0p+m&tu_#HB1AAwJ3(#0p~>%Z^;qsxfWW1o!e2saawK1ov`P7rrlp1bsex&C z7Z|qk`c$qtN2b2Ew7k@Ste4o{&VCLVl)vUO1T)|3i*082^#PNUCeRq0NRF>u zn^RnCuemz_q=QIYY|>uPjCujo$F{-MHm>m?{o~+rvTA#7^Q5Gw_eJ!hb=$a9Y%?du_?@l=Q!`TU2G;hU8K7x81C;pC5S>#&5sm zFu!SPvTDBd%ZkF16b^!Y<&0yCmF%S=^Ri*%k?Ys1@negelEUSLFl^chTU~M zgzUIpR&bL*bXTjICuA1&c||9-iat?jhwX_R&?J}?6+Eu<|w@#!p9S{ zWY;h6awt1&zZA#L8*;mXJ9^n+^lR6z;yP~o@FuOcY20_gG8kTWiD~;LQ|SUl z-a@y2%UxJtr(xu~DRk_N@Yn66%&}T!icuK9;aWm47OauAl4}tw?^R`!vnv=U=dYV6 zB^SfLmt&3cvdr44jFy*4(DQGnsBp<%sq(bW-Ybu7d#$Y<&`F7Kvas~1USlQ{(3Ji8 zZIpm*$-QJL_ecne@~A>Vg^qz|5d(E!f0L$ZUS(O!eE5*>nR;2w?gF8iJY#2b{wK`! z1P|RY5pAT%*mzaqu}N5k+CLjGsgQdc&}Z|i!NtG%+NzCO&$Q@|EQy{8Iqd*XX> z?$LC^MH{(1Eg0;wcZ!KK9#hW9Qb4D;AAWvXEm9w4H}d%GXYCD>FhcTa7g_ZJAp)($`sW*c+1 z%XTETtg1$&uZlFI-kDZgxqeo9e?>3Hx-yEVR(GA&TBk=m2#0MP z$ReR!r-d6-Ftn76<;#1jntuq}q$?E7?w4|A7Yf2PVts+dfC7)AbqrxIW}G7#h(w*4 zaCPVW84?PnwvyvQXAk@_WLOR7;047DJWIsL1WbRqWeta}1}-&61~Kv78R1-_f0n(&_1{Pjt}33XeZ#$H^A{^VosVLc~T3 zWFulh=#cSGp=_2%yn2c4dr7!+tS-_#_)WTRl4`ltC#hVw@5x0`aWGV12qGi3EW+nq zVveg-UAo;DM<1j)2=-DkNmVe!$h#&@>QlttVoc1O{ucS3VDhe;{D7BeRINz^?cmBQ zRFoSVdL$L=Tw8_I|LVN)qwdNO0}U2+1OD8SXcr0 zC$`t{ZQ&2!Bk?nDEFML^?32YHzB33lpE@c?Bh1C=E|$bv%dFa%dk$E5zq*wuX&7s1 z9tj&I4wRqLaxzc?T>&2fE)9{sP4qmwkav-d6r7v57;zWu{OWrUic6}MGq_CbRTFz? zy%3nUqabYJ;Ul@CEn4mk^;*UzkEr{5val!4fgEiDUF-ujIl3<^kAn-q>_XwbI|0M# z3>EvdDn{3`*YR04yMlWDW|lYk;-#`y91*wpxT=k4 zscSaYc6CYW(Ck9>bj0N+%Q+T<9=f2Z($CxOvV-~Y!C+Kry1ebA-a2q(Pud`KD3Z|Q znQRt5@9t_1h4|~SFH&@l696=Y{S7>1nr67T0)VE6mJ4`D3_QAh{g3_fO!k+eF3z7H zp3{p=hnu)!8j>&yo_f~V#)-@|`JnS<7TduPx%jn)3D(3E<+>8aA*fz4bSV=Q_5mG7 zcQMSpo#XcjW2n@oS#gU5)}iLSPgdBYQAvC+k1N`J=KxYd?0Dd`x9_^=bhnte#0V0o zQ1dolu~IwjU3RQ*dW1_Xb*(PJt-3d3`N_7ouM0ft&)&CIJu#sm9THr1i9zY$aGIiv za>}(tO)@11_eeJP@YZ%in58o;j#YL3) zGAQx*Ul}vbd$uFL1VJ@&y|TTs?vo@oH7VL}ABPADaZjr;t@e2VFSV@5FiiQX9TVkH zxdyw{h?O`q0ePIFAH+yE$ngyEBCZGO&S(!Xz5{ov`#qH_CP8WPh|b1X3qZmUOfp&e zU;E*Z9tlWSt)ESQELp1bM?m9tc#Ws@r+noAIsl++zdKRGyyI%lOuqwYcC*-rBSKX_ zAiyIl&gNs^S_sg;5U?^;^qppWXGfan(;b)b!OnWIx`MT_vB8kT&RU7kp>8fu=}jF6 z{If3mW3C4+bZm>OVF)QC*6KjLS%G{^M$}FbFas)lVarL z7#3~-mEcwxJ^PYY@Qty2b2Ql;havf_N>aWs@Rr~OOOfBp=j@inURp#>u`2X1st|o4tlmb zG#}E|-c(cn%^uf;Ler{|);b;4S7$al)RhP2N0gp?<_~|Cu>ooRM&8bU%hL3d&Tw$8CIx;IX`TA^~I{5u^n zamhZ#HldK~4xJjl%u7pb`hHf!`3gMDXLS}WWI%zaQv%?ER6uHAR|Pm*;9v*)#O)Q*5|pV~i5-%rA?s zm^TQtQ%0Ptv-|iK2A&{Yo-x@bN0>9M(NEeu5ZR)$(ei(tDI^@h(J0AL=r7pvnkVn0 z*toi&P)}R8nuIF+b383K&Y^pr7H&H%nk9KmN|m5HbO#Krcs?=A3$BUUr~dSlBh2LL z#>%j9QuWIdO;Jz7bA3LtT-m>&MGn<%HcxhRe&AMnwk?`D8B*mv4`7Vpuo;j%unr8oTQQM{n@nZP`Rhf z$+o&yB$O*PaM``vx55wiHmO?aCKGcnEGhMtPf?g2&#F{IJ$5l%lnVvSPpmDQ3K=1qj_^n$)_ovunq*?6ae|FU?jD31D z`IhyUhv6e9s~ALx=K}GC6GALGCEdj%PAA%V^GI+w-S0v%xz&uidpa`Hg0yKQJ~smL z;%$Z4B+-19!k9=06B#z+eC93)d3Awta7F zCgPHrBHv6kY8CzV=IP>kIdY0$>AV81_}mQ8GZI=M;eHuOHk582TERVo ztCMyTrxtXp8_re~&Pr+j%v;f0tS88IZFzm&m(QZwSTj!cJ48q*+d-du*|6GZWme0- z$=eT?XsJEJfB@J`fFJ%a7}8UmQ|(gw;71LA3NZIBR&_S@QBlA9Z@bJOuX~bEa!$q0 zddinJH%v^s)GYS&J+HCp_GfeJ&EBc(L%I2KnUM)Q*>v8r9qixGGzxLp6+|n^p-)X( zM@+1B$P5K;g7qj(PbO01*GAH=C>@D%qh8G7Y&9kxYVpi#j~T~qlE#~Y9Y9g{7xkAQ z_>vF-sp?>Y{ao`;yav_k+of9it~U~#5ET2e?)f6?&pWqxR3nIe0`4|F@HeA>I(49!nY zfiT_jm$~uv8yDs~cWnbZx%=AD!<|jZ*_0`13PqYCIr@8pyc)Ean*^CUx&ipOGM-#7M?Luv^dFyMo$QvqLC zVtf5WaLJS2E>64`(Zb#Pw0@uT&QIn$gCvPM;bR16$27v-gs2uv^g#x{8ag%WdOz4gRMl z9~FjmtHwaXeIE&Hb_sX;^mB2*C-lFSbX!mZ{4!u6kBex3Vuf zAvA(5EU%601n|~30TAU_Gv)4qJ!Rz^>55z`WWuU_3Y)Ofm9LBY)>+nqdkQh}QvSN2 zjtt)F?c{`-tHgW>cUT8Lq~Pnb@)ry--Gy&*v}rm31lbErmGvz_}H7PC!%*lO=|U{_SqScI;Qf`yQdFeQZX(& zo?r^n+trK!|~dEwNOqrrh0UG_Y-%Rk|ZY_+tlmgqt$YAP#T zLWfj686}sxvVYbCnFL?kzzeipW`{pDp%~ep4J#P~+!X30K>aX_p{wV&oX_4%gNgp= z{u@x0wR(YQAzQ#*4MS4nZf-FqDs@Xtn=3zt(xFP+{GbW#?RQb*chT#I za6)-GR-T9``qCLa(r8(sEX@nSPSeEQ&{ZBv1z^&-ar%#51WSnd0dk-UVBg~Wee6j9CtCb_sP%6XL{@9(j;7(xlE zB=pO|#YOodtq<>`imV0nEZ$3g4znsZ_1uE`>s%kh0qvQ61TcNjLM+>B@k;oWSy#H{ z7OCY!Dp(ytywj}8BV1~X^7w4Da{1j1w0P^yp;b)cMVLP)WQnj?Zm`$%)chopG+s5s zTx>vj((B2{(R2;>$QZE}*tlFFpR>_;*TwHf55zq}z<$b<2Yn|D?OjH1FYMOvf}>D( zXzEqw!EVPe0Aa5IH^Is>f-M4S`K$^YxWR(T^{Nu7FSh*Yi zN=0X--nW@-&QC`E3b)!%n`%cYn>$u^QWQ#H7NJgV)eED3V?WHVi+_3|E>b4DMyzOczdH_i z>uonq(6>(NKd?M(4QF$+5Ri>y#&9FuOxim-rJR|cD(CPb`8VxiMq?=I-Xy+ZTiUA} zQdAn;bua#`?HZC(Venih4nn7u5G!>}IHFVZSNOm!2~31js=%b^r53z4-Ntl6K)=UgJJEjJuo4D1mp* zChB~hL5p5{fjyLH-7T$diT~F&e_kK`-v@lP9ZVTYc9P_`g~v)Ye3E5rzwXZwnJ6-J zpNwx9C&4Br?17dQu*Jdfvr?>X)L#xE^5GiVU+jY4#N1bg>60Wh(pZ?%1@&M`Kg$lz z*)=S?F=0jsl|3p}Vx>OSze>>TR-cZgznF6EK(BTLvzItIP;KN>JPl&|s^@v!LOQ%| z3SJ%HrO6)hA63cu!Btbsb<`Dr+8s%=O;$C}A2WM<_k4cl9dO9T^B}agREZ7us2z_I z)K`dqpRJ4V3-F*3(4z$OyC!s|q~AOI3EaoCwOa?kzPl8Q!QpmiPayNF80+)9m>wlB zO+hB06K8`dG4};+DQ6IGq-?Vf>DKJ7W@u! z_n6<)DCF){tzUg@?V`TzsXrHU989-4t@N72k-wd@u&G=4*6*=-8wYCJivmJV>U z+^$r_f}*a`#2FxN0YoP*%-Zg;doLVRbf+R^d#%rYgd4PFeqPwBU;EXK<~kGmNb;V0 zn@-K@Q0vpyO71Ebi-r-C0&wIKa3b;10ZKf!I`_sJKJaPWl-Sq)<{|_pSTZ+15a5>) z`f#tk|4)HF=r1gjeE5DIRZUZ~A=VneJC^^}tnN+sDZ#BEm^-~LG@t~=7* z4C59{vZDs`)Nmcq#=$^(7Vpk}#<2+UB4aX~PtnOT8~UZt=bbsy(MQCh&N#C=Bh@z@C>A36)9A?{)jte>|bB8u&7%5>!hJr&cM(oTZ32QV1B4J>`8?F`;zx{ z>_c&NS6a5+@^WwNYXSRE4A$b4Hl&=5G?yX=vbcbJw;Nrm!OQoyLZW^`s%LnkdV)y@ zp1QIEvPvl$?P1d|inph~XPTHd&B{9TJt-u6n;DbNR#Vz+dAlgkE3e$PBu6ivT)e2W zyG(mzjHPjIBwaJbs<&Ltoi2#4SJkK-v#=Dzb9H@8_;AawwSue4V3ZFxyRH#u&#l=h z-`_Bp;!cmOf`YvXi&8Vxbt?09)LHblE05iQW!c8~X)DfqsOdi>cnCU<|MV~yf1Ltm z4~R9~?fXsWtaQ3Q!}t0N7Npc{XT)hlnGd%CVr$OROJeKu=&_3{#?NDbrjQnL&i#<5SII`R@t!=IT>z%8$i~FB-?X`WvS^ z#;02j>?|LJzj%by$;m5;-m$HNzV}jvX$WvP~r>=6Smt1jr-THm+M) zBPg@;XY`}fBMUrz9 zR^Ik@pW4R^o0NJB2zf_#BsWR!k<5w3`_<<~%m z_E{&#+&-K$0)2*wgLRTt6mcmHAnO=)c%|l-($%qggo8u&JhEEdD%B!9YRA))oLMIT zcd|#-<2i?xxM8IVQhOKJ)i6I32AeHfwp%zCC=Uy(NUn{^@GtW`iX%yt@uk*iHGHhX zrOk8-IZeD()U3cHpDmh-Q_OxL;J1{aLu?_T(($$@$j?t5q91HtQuprMOdfHvmKpGyx+ zS^oxz6Ij10sV;v*qBX?%e5~bhRX3p8aoWl13mO<^>jXiYcD+!<>MuZK`XmdnIAr73?=beTD{g|G<50$jX zKCCt;eGX0TIDk*!?Cl~eG>YB&R|CM1hCl_-K>ei#X08AlAO|$yaH)ZuHRf7!E>Jq~ zPfZgMP3I1_UP)q=^wZvxoVM8OQyx&n(zIHaW?THRWs6DtmDNVJUY7cmajr@M%2>^V zq9*Z(TpA576*hHlob$jQn{v)#`y&KhSN3FwO4_6(Fk9Etic_^eTfYm+<*!;Z;4xoE zR;>-lXM)Vo!)DQ7OGHR1$1DF*OoOedR?6g{Rc}sBk~+68n>v?@;2QAYa+B_uj`rv{ zveITZgsnG=(CoIkXv(qdBbldOn_^Iu>%5lX6T%5^?-L`7yOot`GgY+Vwjz~GpDEvp zTY&>-lyVIl0rKsswTcALhr9}4y0v=8kZ*+b)I-Ye_50od`oZx`GAuO~5nvVENSVkC z2>Y)TMA(XyH+%d;wETkfK{>{T|U+!~#gdsPXXQq-1IU%M|dkHOaMahe$qnSWgb99w^qk~ju za|d&6(y3&=EYcB221Th44i8XH>3hH8usWv5$i?RPoeWM}9Nw!G=vg8W3AZIIzMGyr ze$Re<_K<8X@KSUhK=i+e0MS|g6&>JOya0mZ{1)PK^)~F~S)bn|DF@j!`9B*LUY4!z zi`KchH%0F1-1rs<7b*hDR8?%pS|jCKCcRuXITqTM=j?;jL%jqK#Qca1xi&S)K+c#m zfO8Ra%^B0LXzWY?c%RTm*N|^XFRP>!dmc{Wa#90sRGbU0y(#iD)=Hu?Lsw))gY(w* zA!h7q5V0hzC37k4^_4NKFJ~z%h!7D{6kBh;e&V$Cn%xn#;6mZNG@0t9%=C)iXSPsE zkUy#d0Hz~Jg2WYfN1fXIlWW~Z(Ig_LwpR;)1j>H{bslypI5FPlcLBl0F9m-E2>u-q zypH&f;64UZ3n1gAhFHJzB@2v6#N>Dxy1}dSX~@0#Hj224uY6IfQwe7;#I?8`a8Xvu zS;34UEVW5Tx7|a&l_^wXo%T;kC>YF)nMixH#pB;>Q3{#GIff*ahDuwHO%GmVO?3ZIPnNnkJHjiq-hf4z!SW{eC9uujVF$Kd` ztM7xUAOC=!dc{q{gNm4Iqd8;Q6AoN)WYwa_lVik=C{2BV@ba5u&Rq>AQLIq!Q9 zdS^axz3T2g2P{v%Hh-yr<@fp@kv-9C>s}wlfLNIL|NeF>aIbq1CQ?rZfgAPWCmP#W ztK9n@cF_`L)4STrU7HK|N&LY3gD>}`%UHR6Z6-sfPTse4bH&Um>nlCr z;`DnCwG*?ZI>n5|#FdlDA(m!Td(=A;&i)yBRV_MpYNebb-ZVvh7Kup7F*=GfGVG7n zuhMJ&>OT;zHx#5##lEm`rxD;Q!C{>%$_vJ)eBSQXXIoYNm?lfIwZ6M8-s`GI?mJ#* zZ58jqhY+_M(0tDPsxl^hU4A5U9iYlbgAbQgAQbW-g*ixC88X0!_S`?`J;Hat^(p-| z*vdu%&URdF%)7egzsqT0xq^t(VwPdAE`fvC+6wjtp$wtcV4HFVf_3kaEkssZldyq{lShymN=!vSVoU$q7}@WNFFEjvaKWi*8uuE3WR-vk0Ptq*t2 z&|%Hsv6w>HQLG0+26&}_djxVYk~ZUrYN?Ok)~!)bH|^NV>ovXYh#FGke;ccB2yq*h z8r)W5u|0#(I@&B-Qk|PEu=pmdY zFW`VPz4RfmCvln8`iLvOsG=}-^4=@qs>3$b-#Kep!=0Sl$ymO`^xT)mFmhJc9R)!1 zb;nOd_BPqg*vxyO&-X}b0gwUcqn@IGG?(toNu5^ts{7*%J@&)-|bpzkI)n%x+Ww7mSF%Mqkt)o zxw%o9X$2FQfpD&(X6TP&Rp6gGfCEj2i4tY3N&AuKQWlWARu1{}Xz^ zqyD*HyLpGpaw2l(5LH2ZCU8pVaQ2O>RWMNd9r%puQt~HXY~EdSehN&Uu)dc%YXWpe z+SUx5!iP>S@knoeVmbFuhR+b&&Pd_Smb97CgXdnO$M)ht8y5G$UUV~7vZqUim#&js z7WBqb{P_CjNSpFxn7-1hdZObV0HKd`9ecj+ok|B1%D|r-3j|P7(oTJ8({PMdl?v^2 z^+mz3hYVsM@PZ2}we8qXkO=sR{{)43%X2*Oc=vV({Iy*NJTU}nk6tF}PO`Ht*mzY( zeAY7jbL#mnnLT;|q?V!p>LBm(5Z#NE3}-Npvp-!UG&f3&m^c>pTDPOYg`u3(r@j$O zmfs5jyyTE*4R#qI7Vq^hoFAfluQ|W0rMXlL=NF9O-344c;5qJ>(PcTKd-*PuZj;Fcfb*KMKxEG5` zw_($YrU(X+hKHm1*QamC1=W5FMM?QFXeCg)F`%(bfYaG&T7ksC`CTDRa zT0c)dP5>E3H)ki@?&a)^RFT{voFqc<=niI@`z;Snxs~_LOxAkvV8%MKGP;yQBe|5& zN0<(QXOT~B0ZVOAbeFZ~@lDKd*lUoy9&dN&%H_A|346=$3yh&*5=6Q;VHj& z#bb%EqpsnfW1Uo4#6mhgF|lo`;OVZ92AbMyTt04Ek8J3rZUj5MA# z=-)E$=kO;3lHSixcq9jK*>}phJK!9hY^bm0tSEb~ z^$u=VO|77xm|Odhk_y0S$GV33=Jwi@y(>=nO=jjHN`BAgOT6KdJxM1nTY09r**rB; zqgf!tQjKS>+mG;9+?$)+Dcz67_l9q4r;d_a-(_*bDqlua)C;)Zqc1RE;llUn+m}^~ zv<(YfK7a+L!9#kNQKprJB==COp1>IkP>F{`{?)~m zY8?L;*cMRer!Ud256MoD$3;S9YE?qOI>c?lv#-T(zP)03-z3gpP;o?7$HS(Am@ARnPT*|m1p1xin5Eerg^ z9$>IEkfdSCN@G(0lINe~N^RKnZ&{OUNopC)O3pHqq#m1dewL;`N631J>X*~gNFVd} z9n>HP@ulp{3cL?#zOMo~!xEZlm71d=i>E%C=%`hxw>&H{A$2rbO&3;-wcsY;qLD7W zUM-QOrqM&m7zPS5gLz1|?~~=a|2HB+ZcJId0)C)$P4)S?S-|77u+k$m#M2LP@Ke&xrXfru1&xp@N7mK>`prh9eOs(XuQa@ z@fwPVE;X0^nNuIJ&8`n zg8^!TDFRMe`7Y%TCWV$>-BU_uZ24!Ti6&%Nt&D;wBUO3Kqg1|cIw!_wD}^Mr^QSQ`WSi7bv0Q|4}D>|_7QFv-`!swwf3 zMTb|3OHD}Tkzu85zIu&hrtbF?L=Y?nXy3qs5C-3YD5wGQg@EBB86h<;C0-qtml7ka z9ek7B{Bp1qEo7f4-{)AC*5S;QwFRmiE#OSfohqXpy{7MduYZbd%lsp->lE~vX0H8vJ)AUj&}CO$dC|HfiZDGsP#!e0 zmkmQ!Cj`()t+uW)S~A13*euu=+Ljb3g5rYJZ>sN&xbT-CDoX{OhW9iH)jvlEgX9)F zmsm~NbMlyMm{)$T$x!(x255hZm-@EQUQwz5T4Q_;^-na<9oh?ywPDe{aZNW1rulh$ zI6phl-&2IqIxfVyylpulK#P#o?07UTgxvx+uJJk7)I&H%mUTgvqOfLR&eylQNvAt0 z%J5TyMJ{KJ8LbMlz}()nSmFACIK7O6=&^UOk!$7LSBxNKFZ902Lse(%rfsQ1N+l6^j62YHQFa3ysjZ{N}bFsJTr^*nLuSK4=yek^%hZ+40Yd(Qo z?8d2>#g7Je_U3%^f0R*ocLEhyHrA_|Ik%6qQZ~WH@D6Ik8F+~{u?F{ZxySJ@o9&H_ z-_AO-EC0IY`H_o1%I5fkB{iFpzLB}`t!LNum6%AKpmh++NAfyXhBZF0QTtX_`ckuf z&_o}-XLp5rH@M}r@_UMVr$ z$-vH)lkvY*AB-toa51~Ru&c-jOIoRZ!fbNlj)RSD!}ZlsM}301E1A1y1q6WSYMu{FAKk0|{IZ0$;qZF*3sC*x-1 zSdW$go7Mb|_!8V;N{Ru`+gXbc(Gy{Sl_P)8vvO`%&nQj)Oq-{bucG%c#B&GzbIpcD z^K(^oaI7Ji0NBCkf**>xr|~TKY_|fj6xj~M(%i}oV1)SrN(l^K+(0ltYkvrj%do1FCvfnowoK4{SE2D;^Q){Bmm$*nTl_}^BwbyVPzLTD`aTh6 zRPOr7!llIY>xjh6Z0XIC+vQl7?d+joK5hzPL8i$yz3R^~3BF zal$DdhV3FqxFpK%Qe}OOOeM2CzY`B7JU&Gb1;-@P&%YTsRNLftc#RA5Eb zrI(s%M$$O=CD)Gjp$eFj3btk9K$rim+fa=yx`66+*^ZVnMXvp?cJ;fJuDe5FARfDg zp13eR$ib8F=xz%;9*PJw*7aG?3N)~@Hx@VKZ6Tr`BgubCB-))6t^1T98Rg&UUxe(E zsef~EI3Tg!3OQS;o|Tw-x|Bj3Tt$B_+a)6^Om%!?&Wc3h!*ufftMWzWJp?J#=kjUc zDDY6u^wmP-#KyCrZP#El7lnmgT(@Aj>%nKfo;bS&N}k~`_uV^{UF!C~NOComD|U#} znQSy~@1R-D69aLht!p5K+v<8ZGV^x$v_COg?TqLVu-NPlCj|7?H(AELs6aIJIiL>t zoyLyhLf|5Mrbq+*fMEHSVY9*1hrOqE(!S#e8c$8GCmVTI9`D@3ugG*xda+mrk9j(e z{GHT2n{92?{1tCO&M$<`Re%Ng>Xx3-=UqFaJ_8yB044DI=9OxdP#eDLH}f{!dy(>e zMg0S8t+t=Wy zFVqCco%^E0Bv(m;{3@Fk*Jqc4#rY$K8bNQ3n@#mI*o3w<;>Ki|R?sUc*vGYfy}u%) zKb;d?Shalf<7SDY2*1GR|#oov0;letxU0hmU7o$#IwCN6^b#ngO zMs|&ycqN>Dq`W3O;eyrcp3(<0(}FMy=Ac+1x1|?yQKRQ(*#R1(XI$fD3w;+w8>%FV z>#bsOHLK=9eiABC{fi_FeAK;fCtPKCH!d$mtmkmG>#%^#U+Q@QUL9 zcL6L!5}nlkI-KpWs9=Tnv&x9`H1f11pjY~hk-65#3olujz-VdGZrJC~9U3EH?}b8m ztb3=848kBpdDz^E`U}*ynYJ(ia#;6d4{TumZe^poo z^>)||PJD<5vFRdJ8^h=v9#5_AwoE9o2Qv4dvMOl{s)GsGGh==-#8Se~pF818e-D$o z;p3%6d8IibW=VCJx1}XEyH|)VSwcf%x-?vy2@$uotowpBacBDtEnO@y!q%uW9++L!Xgg0}K}+_9a-KofN%V ztCMx(zDDW0ecv(l7rq-?t^wesV2H!D)@q%sL}sEE3J6nT@2Xa%<+o@z z>#~<0%77(7bWX`Bq01h~47%iE1guphv|Cm|%4rf1xm3)tt<$az*u_NC+ENF4*NI<&#F2kwccr6s?QB8-MlQ{05v0*azP$NJN^ywoi`DEahKT-~ zM0euzfr*0qqz;m`gcRGmu9gS{gE|WIwL5Q%^=ZgtnaAoPD-ZRZr0Qn9FqP?j$IpB$ zQRep1or7W0>aY@`K*nllttGa7**^&liZE?DIX4nR5bL*OGE2?IUdve9>oZZU^`knZ!j8;V*6r#$<+P+%~t>K9Q=m@rdHepFjG>nR>qw&)#LN~PqaD!nL;xmB&SY+2AVF3(H{{O=t!>Mm zGYGYN^g~Jpq$75tl8~Sx@v&>%$-YGogKxK@qNI#s{MAlOn5a-#Za6uAKpcYH(l>J> zCjG?*7H>2xqrD4+O@AF*`Vas3O4M6S8#>0t`x^WXMDlbtes3$P z;{C1D<%pg5o;U8w?l8?~BEgTAk1GSRrw~0W>(H_ZUwYGTk;ozS{&v0ggr;`A<Bkv zqvQnO*cz>CYqT2Jm8gJdV4KM-d96vC+*WXJ*mu|wF0YFNnMp+Lze#mw7PjC{(=#_}d&UEb?FSxyY+MD2 zApjzj#6NR`xb5Mf%hEnTiFYY@%`AEeNio65PHlXcnmcHNy&(R<0sZ;Y zF~PbSIkKzO&vfsEK|BJa31dBNOO0aQWIU7&mI`$Hp%WMEUT7AfmFisfk9MR+xOuU3 zK%z}HJV>UjM9m7`yquTtC;aR=1pm;r!Ap&fG@=3PHz~lq=DNBFZFlIRQJl)q7z#Y zj9*xYnF)U^7R*__WUwE*08HQK^T2sOrVdvK9-Q@xEiA4<@0oYz?s{`7W3`S#(_^rn zc!w~#*CHt6;eHRx=YF=@ajj#CwdmOG?qNCkfrw62q1$|}p<=DBDnofW=$>3JW%rQ0 zoVsMP@_S+klSZJ(#X=ZyXe4=x64jm#Yj;1h)=f^=NLpjvS3T6~a&S8e@AKI|j;~$= z?a5zB?(fm#yT4HRx2@~nvP(Rp<2OTHpgD^9K2p@aGOpHj?wqx8RQ@% z1)8sjJNcV!+2US_r%Vk#JPQ1^ug}pX_P$68t3{{_`To`JvBQo8OM1|y9Zg6iFSo{% zsqYUY23-b+05GN@0wOn#loN#Xy`zkFagtW&6G-fj!gUTJNhnrh^!XjlO&xuV;79c0 z({tZ%hD2)5Ic4%{6e^8 zYANKhrVIWML8jMjX9`;OmV^Dt%HDs{`Q1z zHX*D_-U9mTvqU)zuJr-~;z<>4x{zW#%f;*W;#@&>^i9SCox!=wld2;_O10t^!_Bdg zEE3kINGh3>iIK3f2jxdju13I20YZ_?Cv$^u7l=)lg?{HL4;|wwgsoTtg1jI&)Aof0 zz^1E@*?G()vk8>P?(@p3^U($aS-VqovBn_pn^MA?H9V3aeOBSfjTKjyY~F$l4 zd0Rf(=0@_mZMlze>zQ<4CnU)ELaJRp#G0f#_$gGrutB{gHHr*-zanfHGI`0nUwO$J zGMThvel|*o2#hW&j}11@mLhW()V;Ub60DJ))Bovi?i-#jz&Dy3`55JGC6SMy3b24Q zXT4_i9YY?%6?OREiVFP@l7ufGRi>@yC4c}u`~Gi$rr1@par&YK2$bi#V*&DYSpdkF zqNW7Tu(Wfkd8C)?tvKt@Y1?(2lc34agSWSx6} zwmD+iX92m?Z`FBHi@NhVOrnTlr$7 zf1bAJ6v<06Eg?fBuj5lqj=b7Ep@twPLuAXb>O?PcosFuuC*6@Gri{^YLkH#Wc1LhD zJC<{^xT<^~03w%*OiBsu;vxwIq;B7zfJEp~1N8d#-nV~k%WzA1tSxdQgo5omRp(JJ z+6veq!mI)_BY5T3Y#<3UTM4dq-0g{uWpm!|ie1W&?MBNNVjnsOamZ`a*$o7j?Cn(C zkx?0-V>p;c>F`~?e}ODuH+d4kZVk!+rvf1<1ZM^jB~=4N3lsi^Mf#K# z7~;$&=k!IUxl$9i3CjQtXtM)41=xn3z&c!!cHhjNJ5hdjH%x}=7{Xovj?%AvDBUq|e?=CTnwqGVke7`Dzn-U6jQ1wwHu# z{bWnV%za7Xs+wikc|Vdi|0xE~UTSz^KkL9V<0>|@FQ{O($DAO36@2Bf*Bt zm2Ti5_Zld?OEG*XXA+LRKYUh+PIp4aMFN@rgxQYCf+`LdK5rn$XL=|~iRKRw3&A_b z0Np&?7NRmOLC6JsczYNS(*`I3QDf`^wa32%v&EB;>dt6}8k5r8BqC+Hb~U+ypke&- zLuW?^B=*y*y`Km^K89Z1+L>z)UWX%J5y5=gfs)rwb4b~8>fVHkaw<=LJa{IFt5;8h zURy~!yQiqTtzxF*ud?Vnvrj$y%sZ{yH&eq~r^^DO07?hEWIO*$s~@S_q^W?I{yD%hmWF?Vs^`bg3jKWwZXD#KW~BO^ z$c5MBi6|UZ$#27_{3|0Jk7-x4Gp6F}E){C?tOU*{3pK($5az`W>Cypdv57Vx%B%#m z3DfirF;my7d|o-iH}mBL*ku8MRrw+TVM7F@K12Jde*^ltHl^~|%Pg1!ABo~MV0ksw z#dC%Yo+1f%frDWgGUi27Mp7AVoY7MYrbuvQ)gy4U+!rv$TkU}Cj7KSPC&<9T&gg=< z6-c_RLSK7q{)*Y;EoMR+3nEtS_LstjZVpVO-`Ufk%*C-#$uf_a`g99MGN&Do zZlDNZwpg*NvGon0iD##Dayjl6FQqC-oe2wKhXHQJz(?TJ1AqlQ0 zJsw+9rb8Q8dZEJyLhkSW=Qb5=>T!rjC3d*3*s)eJ=H8q5+xQk|lM2*c8}7?RYc1P(&i+;I%cpT7<9q-0noIeB0$O2kAGVkuzKE>Wrtr@q!~w1quI{F;@bN1 z+~(G-MQ0Zt$)if}F#=@ivTTc2^o31H6D|LOuw>7|{-{h6OeFsZ%xHEtMgSh(4+rDr{n-c6`d#11@#nsRXjMZ#l%>Tw z(5~r*O61k15U$~=6GJj)eye2k>uv~d^+A8V1{aB(L2Xt=C3JjCfSKkS&j zS>N7>*XwHY-A7`T?w1@vci%x>oc3vfeSO{lsEfYWElCP8w&L3G*%ac7nXdxIM($`qPe z;q#kDEEAPCI%->%&K7XT>RnxFgyfI?v#XVb*ASorEd-AUEBPGxP| zGQVS%<1sATb;&PrBxH&TpH8;xg?$#PGaqS`vO5~GZ6=CMQRT$R zB`7^P6SCc*+>#o6cerL(y597J*4lzOCP>d`cZ9)a;M1wilPQ{Cto{a*(PEzm5N%ek zfbp!qq&p^$-t@pVeq4e|1dv$ni5BaJ2c2(d#EE9Q ztRB62kstZx+jG#WedAbHSMC6i-+jqFm}0Bd1=1$uoqtuhH2J?$O{<(n2(HLY3Q)e( zycMwHX07Jn9euS4Fe&3l9F^CuXaDF8Q*wS>Cb7wfYjTO3=r}(chV7{Xm$(|$=x;is z2aL>Y7R*1grr(Ne%$@NP(7obWB*h6Xkrid%x9vXj(Akn&99tsd!Z0$IQM>0l4c@g| z&GWB5V#!;WZeL-mF{eDg@bpT2@w^b{{05lx{B@%5FEu{;Q6Kh4$W5Ji(Q^bo`{&j_ z|NP|ryIn(M*mdTogOL1y{6M?PK>V5#m;6AybA)_Ee0+Q*ZKRSp7dJPz6YwU!T`5Az z5%`y0-dw0%CBif!sSEfIDiRFxLX|rwwdfPE6_2OlDr>Evl-7-ujoQqealws)YRa%) z;AchGx|`(Hdfs!b>pUKRuZFVe(X?(`q4L;wkg|OjVNhiesyNr;y@5ZWp{t!QSalvJ z%^HlFU))!x4{1@B=??A{z3}%;1Yb1Z;IIu~jDwsCx+;J4%Mx{0a9(A0Je1B8Vac5Q z%Jv^vcmsDp`%Z5o|4Ul>%U zJWP0azT_TA9Y}3Zb1{sPua~bksEd3W^|aLFB6N%KV;{f0WX`QVr2Q+a>%Ja4VPwVy za2rycyU}(hBYP5F80SqOdjc=^Qxt)h*ZhgZWo^LB^Suk-8No}!pINvm{2ud77I-P= zX?vA~DS{r;yH-1+cq!~2h~2=3tmw583&ofdSNqMWuM3s->WEga>*$r_o#RFu3Nt(V&zpW`=>u;3=*+1d@@;Lwo)GTvjCBUt912(>AZ%0=KMHh2YfNtoPwqTyEavI z{BS!e(rneyKd4&Dib;~0O7N0L1=D|Za)GjIrcaAsEtl#IKtRdpoCEZr$nqFn2>k$L zShvkJw+lQ!qROs_I=b2*Ko-}~Lh9uet1zazBF( z>&i_GTRK9q!p_ixNkXwJ@)NvUBZrCWR~r4B(}LPo5?hF~*w?6-B(P^kb!aol#p8%G z*LvfJi|h!M=iUn16Sjat*1q5Ae6f^+9L6*U4PsAL+V#df?S14G;}A(^9d4Ye+NkXK z7IE<;2IoqO()KOe~p5})%-E|%|zc#Gc< zC<|O9`SkUQeGWaJJ=hZV;F>!48no46HpP`|0Cig#yV=-up(g67sgp8Ua`5~tU!LV%q0Q=A`}?gn1?&`o`E(wHA<)V7&7m#@D4 zd=c)8?HhF0f<0j~h32rJVa4Ash<=7cf^(qo2H*3MoBYI4_XGio%YzQOHN{ed13}S3 ziG-EA5JFCcx%C?32+Hma?t7X$N@|qJZtml(Ww(y5bIJA%NZ6i zNqo8du|=JT$(&KqUIwR&Q2b10eANpM>cb7!H+P^Q<6ROvi5s2;dhWk&Hwm~M0}DwE zPUq2$@c-+x;u@&EvUeaXe0?_RCNaQksTJ4XmKl87{3D@T$K~>D<~1_;EV3TG73Z~< zQ)eZSzdcq{f{;hK*yz@|?buRp4S0wxTz)5RfOgmO?Bkr97kzk;^D~w$Q=EP zjfAuq-rk$?eIK0~tHuHD-YdwcaUavxNOh;~RDx&;qPrTw_f1IP=r~Hav7;E0q&I0g zY3ODxSm9z!SK;C@+%!qUZ}AZFC=uAM70^xUqu11amR-SHrPCD182>Sr*T$nDKpc19 zG=iZf3nlWd zb?s!>HGgLUuPpi+JCTPra*s;&%!2EB>M^izGm@-tSPrc!GWN4)0g;jS-Z_lCW|pmt@c$N;oe8a0m8ke%&BMwt*^9`x{f2?qTAhMA2p$FY>|ii3t`R$! z6F@{SyGfhYvMY25j}&T^FZfcI+&&YZNABiyIN5#on@TdkKbZKqgIP-w2(r2+KpoqL zk4A5BK0-0s{X|$Z7vk{kgqG$i-!w?vy6Tt|3nAzB(};@7oKz!~*cwmwv$)ZdV%(35Vq_BujK#u&N~T( zNr~RPd*P+JL5aLo+5@^FW`kbWbb86cWaosdgy|qjqUWCC*+#Al=*D5iam3=YK#(v} zezRjgX7nh2+SczS2O(lFp){}aZ$v;R?Z!3v^jt~ zZt)&SPY@1-LgyZYVw@vEZ!rcWARYKSgqDFVEXHKXB1BXzfiH52J<9jHXjJ6ZNMTkb zEe8PfWGlU6ey&$bPI|80XTZ3TP->D!PQMYm`BkJyN3zxcU%P;PI=0-V6&eUy^CUpk zx=R{sCj(FSqZt1O3j952{W1s+O#;)~q04mcSR=ET7}c7XBnRz#Dqaz#Lf4R-@Zern zK^*&a8a^vI`UMJw4EhD}Ap8q#`h^=X>=l)2%gn#-5do~oo4+ga@4_*%plSauTw$_nrW$e|3voxS@Q@ke~UvrBgfsFiF!P(j?&Q=Ec zeTb9Rl5QCvJqGO$dUc}s5DGm}H6*S5V#gjW-@lmC21F73l%q!iZp~;-e5of^tG(|W zF)9Tpum#_1>>*yO_7-j>hq_rpw+gpUjZz6^cPG1LQXqYCokz=$l?7|_O*pY&8J5E4K+ zPu;c(;jV}6r^*-C@t?KhZ%6J?3zuijwVz%P|CK%pZxQZz0IoJTiuU{!IO#r1RV#mq z>*?RiWCWAl4QXoKV{ZS(*|XTvjLH8^(o~BSsQWwrku~>_)~xjN#FZAY&NwP^1FZ8( zw39SFuLf8JZc!bc@t5lYQDctCu$VRLP6*IW8`D_*biBZ9xvKi}M&`@Tc+-Q(BSol+ z=b~`+{oiY1&(0||ju`qU5QIVkQgJV;EO@d;xExdnkL~=_jXE;N%_Kmv^=|Z6i#feM z>Uq30mxIg#RM(esn`=79gYCOOkeo#U4&jOY-vWfI!Es9OZ-C(cg^cGc=$gL)a`GoY zwv#tNQc6wtJa7s3kWmZ*k5p}YpG|l0qFhIKb&%)7?iM&!vC9XfDsdKPwfz zl@;6GSNG{!K?hA68e$gr-7|awMul4=hpKhc20Y?733CH&NAPWw;lXw)ZXx`nO=z_G zVncT{opS)et*G~)mwld_t(#=P&Eh1tbDVc$d=s31Q=1bETu|SBs<+NTN>#Yg%>vLZ z)n6V3tnZ@w$M+ei|M)%%u>Q&8KOsW!pUddy*{ul)_vTVw{91tVBp177m$F;K^}sq$ zl4{lRDv#vTFJKO;ch=_PS z>$>k<)k)Gq>s=X(Y*xyPwm$7-w2_Kk5Xb@;V2+>Y%4}~vNlX@xR&@UhtXWLKZ|7MZfye#OcKTe*U{@2ODjgC{G zKd~VHS4(W?OiQnjUY>D0x>0EWDK!oHaQrg8CTRq8V<$_<`Nku2c=lxFXTBTvyWq~- zNNW~``H^ewz=JOW_sphfR7Btq(QI`UhqA}q!H;ch3uGeKR!I64DEj(RK9I}sSar8> zeSWO;sE$&(rWWSKgBrLp-J+@GpTGFDW90$(I3yn<76Uq*_z_emj<()i;Rd9NIGr6y z^)H7`9abh};vp3W!C_=dEp?GW3=2Dwe=P|(^fuF9gZut9_+!A}zl1knaNWO`*_$;= zc>wgmJ%hRkgc-YIR!46f58W!WDXA)Jaos?d&5ne-%#Nhu&W`l5H!z+Jb`rVNFz`j6Z_#c^wGatx=g| zJN3h>zB|)g9|FYft{V`yoj3qGi=e8?5Zz?2QKHc>_1JOoM4oMHuImMXxX*Pzrpo>S zyx8&IUu#tP82=Ac!~a4x090>8{@mxHuda_d$k#nPAEE1^QnIpq2o+`9$cmj&D9cWQ zU(J8IDyTg%OKG_ZCW8lBkkfFDBHgYAntL*p7%OZU?K=xKe5D=4bu>tlFtyJd#G z*QpbVB}$lmBak;~`jlwvjpg_^t|9B!<|z-?qwTZ`#s+iLA+gI;U#vCrC;GG0Oyajm zh<9Fh%my=t&{tA&hC|L|x*I#}&YM+=9*M=XAl$rA;0$HQ*D!qzXIm)=OUKsPmY}**V)4;@}Z5uvjy6#R z*Xf>-G4?s*H+eDfv0lx6D!Q5a00WVavo~kYyTt`xQ1kH7FbRpU31$yuf>di#nvdE) zhr9ruyhNqNXASKP3+y_wMpO9)&>y#6Yo}INm#6dzs6VZOvkwj3d*@~a4jz%ZV6YF))e2sj+atG3yKj;B?g}B z7ibc&+{xhT#r%7^jFY<6!TVtoH|xz9+yw=K=RN3Vb|)PYV!Ry7V-`Uhb+~O)ipkUM zcI-}GuHWA8+bG&$1RU%1J0W;iA&X=t6It%(J%bq35mKJCxVkVyYe?gab<+6WdG@cH zv}y}ReNrPKv7Mp*{z=WCmVd|5kKHOt*{o)aSv=X`aG8yom1l0GR8>5|g_Z6#@;Q?b z9Y=4?XiiIUeW|4DOJ#T*J&VI(ht>{z$w_-Ujx~#Sq_eQ;FS)^&tw{c0zq>GJXq#Z5 zB2Ou0mNv`ys{B@&(18cC?a2yFq)bpnbKwxRSQmF|>8rE?No1E8?@#fN?*7fxfMHCY zb4!V&9+Z<}t$sFWVT0~_i+{fl27-mne+wwW5kATlid!=oePg!uy2Ijb=IbW;dL_y^ z%^YtTU?j&7dyOda9+=_8|upQKfEnUP{<*t(&VZC0o|rGNLl zxqH9Qo)HbxdEmp4%$QQW6DE?;j6r!q>%$z}*G)o6wxM3S)-U7V`y*shth_B$Jl^I= z22)Gh4y@`wCXL+DvJm^0IkT+s^lQ{wqZyXfIEXL$Rr@R`w!GBU`c5%)4SJVh`0W73 zM@F;b^(*igT_{iL{_Y1|e@O1D5VIonWOoHak2r{|!fc*RiYHIr(}ZW~HI*BeG{{Rg z?!*oQ@LLB0kM7yK|#s`>)yrP?CKYW*whMC^2)@A!@%>pxDy@LKhC?ECEHx zBi6*h0*D%p%+>CAP~;LcU2R%NVqDn3Th00o<44u28rK7-Iuc=>k;^LRLj6pNQ}0IK zRC#1Qf3Aho*I*Gx?+6nbLdoJ9Y<+lF#k-PowymLmHhw_zoy8}D#6py)bdW!vb&^?m zI}5q<2yWUI^xeJSnhK?k#vFXwyZQosyOEgFx0S^)H1M#8t3aLnVIQ+?I?$%E*x}yg&9Njsin<* z3{J0k`6RNqdy~57iscHdS>+18f0r+KJ6Z8gtg)D$_nYkC4_)KP^=FY5>T5|vL`iGv zMibWm@Ee*R^+>;J--Rnee$pa!0;ylM_eh%`y-OUner=SvMo6@kWTxJ}n1iRFU-DzS zUkSG`Z;pGuK}Ua}FxzsVP#0%_gDtqb-3h(+Ipm4PQM)at^9xoN4qJIF4T=XU>q8Dq z%Y`9{#Wh10Y2Q($bSc5ks|O^cm3~OQtxij599=$f0WuzCrjm|d%MU(Yq z*lAIb^q3G=G^|h%l16QYZ|FBI#|?9zeq^Mm+J*xUOxiOH$uLtc8g#1LnR<|%gX?kL zpKmoA?>cfOU2u6nIeH>r;H=SRHeTAUPAHKu>g$$^b!EgrA5T7{Vqv@#9};q6*8rVM z9xBvNqZ1U&=v5z!B$ntk`HOf{Gyk{G-?ko3m`^@byMdLD1Hd}d#%1+beZA|%7JfmF z={3=FRWO}EOq9>JJB%`ndSz2I*gV;U)Hd=BgPXUS!H|?TF$eNOcaf|s42ar0MxVlqU z>#TlDP}16PPVm@>=D8h{;G zXbF05XLa4tn2NRK7SSsQF22b+%nI}5CJZ}ueVH|g5TWJd{WktXFPN<`Z%qS~U;BjKpxiWX((k1{h~f<8=V_L^zxP>mNJD%FyE*KKr<)nKJ|G#c%p z&Fvn4{ZRlrXc3o5hx^X!&`V zh4fH7I=*_iwNSr;0Z0cdf9HT~Pq+Jl`s<%E8USsN3A=#uZnftOeZ|IpTR!;UNI3B% zf$sq`I4bWH_Rv3=UNiI@LESlJu)UQM#pzRoI>U>z z^R6)Q)Bq69<)8$?LPMmtqiDyLz$USzSYvj4uiS&8?5!brJY%O>L}#k=OV-xG9GDzk zFKC3-%DcE4%NtX3qr14~rGU5AXtCNV0R@I0pT<3-P8s9-wqt$k#C^a^Lu}izhiZY` zIEz66(!X%53Y4~Hrk*46^~v`ctwmeCeoG0JW()TaQ6hs)Q!Q0Dy7X}H5{v*WZ$JFZ z){zNMN&bGfYi`-WJjr`eR^uG+EtHMb&bx~=(`O8ixuxLbed79`PZ59O4# zxhmerRdQgW*O%ls|H&XvMxP$;7e7oQtnAEG+uv0Fy-lZ%l~W2nnn`fg6wr5L%r*H* z)#noVQpy*X^dn;-wd70mx*slSAT{vPr7v%qSy<>t9|oW4lhRLeW8fom?;lE(u-GNm zt)x5kpkR;aX{@%>*2 zc|_NTmh04I*=X6Oc<($KF`=5OidAkB( z`h5QN+LYUU`9)n$VfZ+5E-ai@Lz|D(>Uo_9*(y0Di>>&1f0j$gkqZ*pgLuz1sx4IV zea)2p>?4cZ>Xb7!SN!NN;?ri@U?H$c@g-P;--D{3<+u4vHFO&N9t7*od|v!UAZv_M z;7)1V_Celtw1J%Ro*pctW@E5#J*KR)l$!5*y+$1?OKs2BdVKl3vc9#$?ia|kpz0C@ zirK91X}8rqxRQeA{QAFX=8~67q8IAOC|Uv)kUBR2B0br z``0b{0d@W#iB!rZSh@SDqc6HrxkYwl2_|&YwiuW_tR-Kscl>ipD;f2re17p>3YAV) zdg=CkqoLwKf3%*ax7rG^{n+zw_ZV<cy=L$gl;${3!fu>sg7*)N za3~35B%C58eA!-oEG>S{Mw{)sIb1l337@2@VkPGRk#KvJ^b;vtboajBR+SQ^Et>Tm zUHQh32lX)_KCg{YkSsiZTzlY{1@iY~g=Tj|k*1QpRYj4ava*AnMLzVlgS|)hl{gDf zi8-gv;+)N_UW?11b}d}v0|-n%!7E2H(jmo9CdDCAGRI3-ABMQ+-)-@Lx$y@41b&Ba zpHC5K@;1JAw$Mp*zTs-{sX|C1&-pn}E>@%%xNKZ7<=;}^Gq*ep_p4kAW?b`pTr{t~ zx9swfv69m6oF~-;>l}+9HHGio?(2+Ez?`3OqfF<=dG;>Bw1*9TeO)2z%CiMwqqZVu%p~Yz3RRL1<%!Ze&{M( zK`OZfa`r$hCnoNV37lVj54toP?M1d04HmA1YZq&Jk5+FalMttny`CSn97vNVBITWk zJ&HbFmNu^C2aoBTK%7D1l5&pJvobNV%1EK@v}w zIPQZI=9D%K4~^1I4UdqFIGX-WR)b?FDaktDBFQ?Vv-(7&p(JKCoVgM31!=;D-*1(w z648}Qjl|P3gq#@VyME1)fQmKKAtdj&Hk^D+F>Gr(N{g66(e0sr&0A$ z@o;?X7)e$7MSXtquNkHH@$jj8DF#UkDI%`X%Q`{hL!2X4nP?c{;@c2Z$3^QK;J+Bsm3w*B=s|jpGORG)jp$ zpfCvcLg;x?-KVF!W0%?C1uxvbJ-5OV2g;#gT-hthffsHE(XHdd%jwE4LIF1|)JQ^vqI zKTod-3cBJOMI`RX*rGE6j8G7dCxGf$eO|tCo&^91rSfR;ql6F18fpH?9t*z7xl-R^ zK7bdW65DI$3ym5kEbr)ko8UawsCJ32&Ve;BxBteDv2K)55^)*iEES9KW3viw=9KRG zl^-i~sg{&8eBb`(;*|Nn1*7Z}R~z0qV&ROte)JVfdPD2Z9W$jiTB?xokN~;|04Twx z@X()lZZOq{U*)bBp3+#{N~&i#l8_X?+LNf8>i6ciA5H`uWV2(gTd$D%&UE!sEG3nU z?HsR_4{P-qq|jHpE@ZTU8F<1_zD$a$dA#VP!xAcxu}8X+()&j!oz=BOf+jDu-kW#+ zY;DU)OELE9R%)G#R>#?RLD_|^K8;|f!xa)`GAB`Ip}O}r%SRROIO%Bj*1zVXCXUK? z-Z~x+T-GG=?_^++D-%c>awS*qaoNJR{CmX(s=+UJ;?smq4-J@A1&Hh?NGZyk$jfm~ zfEE-m3t&t4r(P>C>5bEw@pg4g!RK#*=N>wu1s)q6b@yh{WoyjX)pB|%7^h>?3nARIQku?~)RiJw5i5w1|O>w_4myKkm8SEy+PHG?rG~d6V{C z{Wkaepd=<+awTDBR9Te&!Blne@I{8%e6a-K;rK>(!$O=$#RgZdJzDX;nFH38=@geG z#T)AKjVfXQDJTicH{2}Z(e=cI9_(mhA!5((oUUTs+N z8}8eeJkTA+^eJ^Kp;=k{_C*SPn>d#3$s4OdD)&K%@qMe&DW&Gvu@g(OQa3Zjjo~b# z(fK60i%KTS^X||j6M5LvYB&y`>CZ&uz!ktBg}!KEobuiMIN(LuhtUS%^xHeL5#_1LeAt$HY~wOT<_o9PWvuN zSX#Bvm^lrsr{~cJ4Kb6!T5d%OMFHDjU9snbt)gM{B9-$8_#+e_+hO}HoeWOb8R)v3 z`75pqHoGL1)`&b-v*WB-tW^aC=lXqhVEQVOAO=VDhMw$Srcimyc6ga+`7bU!*-J*0 zovtnM+=Pjbl@I2ex+4ouL++)VUkqHP7|=pD8@7m*nh2449FOcZDF}6JI8i6VSHeiq z1B+yo6)@sw<0V=&kW(>icmO`~#GEPg?Jin^am7BSbaC{-#adKL*(vfavK zO|Yc(7dOLmh2GA~tpepPlA*U9RnaSW6D}c%0b5lLbA_N-rcYZ{j2{8131J0*AmY9r zUmv0MWgp?Y`A62#6VkF{u~x^WWTx(CS@C=ybk+KyOJwgOa!#{X1C^WkNh85#@USvC zeqUq#CG>TQvrgm=zbt93WfA%@siIqe!n)FG#sBRE>s`O0;5psH^4&Mm zg}(f3Uq>5kChzLO<|yZ9KfEa`rF$M1naG!G*HyhIZ8HZ7APlVJVe!lO ziJ{&RqK;b~V^152F%4>F7)pkJ=A1Tll+|gCCh#p*iXW`>*cEK2Ou0KsE2V25eZf|0 ziR1?#u!r-5m!J~k>R$FDr~O&twfpofxSWvrT{cb3pjx z{V(rX=aTTn4|>MmwWaUn)cHrVciZde&yrDEXp7wtIH06AM}2=Gfd2$5z5PY#9rmfX zqV?0(lp)>h6R*RyQayt@NkVftyU@)7GX^39PhTst=)-hYDp1>4MHW>IaP|8PYZ1g( z!|g|ixHLQWBzJC=+ro=y>|TFeki*n=YS)1G)Kn3oZ)}!Wju2qbaP+%+GVP0l)8?r zCPIUg3|IQh{n}}>ERmU6y$-vy+OvfXj;FF=cv*1nRq7wtT+BUT-P_Ul_KOn1;RI6`X{1*B#FHQhD7zNs2AYnuM?05Bi1Nt0@t? zWTAUF;p;xx==cb5g#Y9eM!ril61_k7{^fLVB}>JIF%{703bUy50;)~5 zpGS*uka|s_pkLY%Rxu@17tns7%Iv&0$Wd@{N=P}#zp!6@Qu|b+4B^PS+kq$l`LjG; z$I%PNWuQS$%SCsuNN|Abqa2%WV+RHTa`xDvGU?xHS8AfCo_9_jFt-UvgCkEs8axGZ z3!(ua{d`AYJND$Y4POW8N|Jt>Z}KM-y4)DWR22(<$VVHw?`F8tEcsgRBBZyPvgXeo zI5O3a%GadqTIY46{`Z>Iul|x)e81WsH6Sf}W;(9*^J3{@SnomS7MI6!lj`F9+{1|g zWz=HJ=kP?RsN?X(oL_KU&xre5(!We+c{;tPpsAJS53s=(FA5YZSNdU^3(_~-HZ_PFngIw z3hB)lgIS@x%{TYOmo!*d^&G!Ql@m+iq1pt|&jnwwiO}{lPn7+vTJMQEsjU|2<@avA zJdMHhte@D37zqMvB33Nue^Yd|Mf)2We{d|sh z9vb}H`arjO5>hW!?N+DmpfGzTt$I`Da&l5&5RY1iMVGzTi7fiUmzT%)&rSp~&w&Zr zi=#ws_B|-w><2e~FoT&jS7;Dl!2&mdB#X7M%Y7h0qENUu<|-4-YkAXo?E z^A2B<|K9uFySJu_Dw-*(x2&~yukLdirM_}`U%~bqw4tWFTcy;V!Z$*~zGgKuzQ0s@ zH;R`KbkXNiaJlxH3H>xEfYAo1DGk;@jAmy@LHg*VLY#^%x`oamOycNHL6_5B&RKJ9+@HZx^5IVg~X zxzPN$b9cn!xRajsiFHFz@*s1flhuC7q1RcHx52Y(Bd^o#uzjTBE|rsbV4<)IY_#CG zKMQ;Lw;uo`1&J+Q9{0Zuftn}5^?~Yv8&{0ffcg1cVyEdC^CFb1oVG~E%|6qZY z$KEi!IKkOswI#ZlKr}_RWRG;vj}S*h3GsNI&dQw8wNEzXopR6%BlFR4tf<(YVlAEML%2`W1Y$<$NfCI+%q7s&8jfxg*lxV@K71+u~n%G22J44`(wFq~j z>UfUK#V@x*HcG?Cb4ejL<7YEuQePtjfje8?0Azsn9b7x&Tt)nxj`i|QU=;yh%DWz4 z$((2Ah^f3CvLoez`OfPjsb9F z_X!`qT)mSc=A7$%{JRBLn%sF-c`|Tan`Q4r?!!=T40!^ zry~{&#KRIq|DduJxtY2jvz$b{6lXp17OavRZu8wvsmVP@#?sdiRp#SQs!WxsPBA(@ zd|iEX9n>uf?!&tEs3aHtLh=8Sj^;C!?DQl>J~wKDk=zj{EGaY1&ja%N#kZ_f!0#J1 zMLz4H)#T2y7WJ9hPSw7&TOLVr?g=FVej+)t6t$`h7hJ}7qiJHtj0ICWG6aU^11Au; zt&jV=Pa`IJvxk`lIbXMN&(aTil0&q&AO)@p!+!+lRBc|%WpFil_0g}H^;FfZOo2U( zfAXW3XS2+d5;mB#vFWhOiMZc?km&6M1Oo}9Z>G`HZSCHcVvmnANo4?1L5l8mLXIx9 z9=22ooU2~8+i+qbl3Q?v)_GR~(BE0gCLJ8@&7D)XV>`2|Sv4OeR4~;sJqGq5x;Po) zEVtBo{{B; z^b!>#YieeVlb#1BS|_gYtl?En175|G!d0En-n+|&jh(xLiX@%JUgC#al2U!rW7)_& z0q-=+jUrvrO*!w=vM!hX#)sN^oyNPWtqGC^%)>cY%eiO9ys9_p7~xctRnx;#j;_uz z{)dZAn<@7}82Yn1nOfPjlZunQgr-zjQd{eG6T7r`a`h6+=IIHqlJ%l4&xVuU?vKTL zt2FK5SC`u{X{u#B_a`>%>T<%BAXT#g;d?nc-s{`d2T2F#L%LqC>x^TDQ^BHckBxT;m(wVzp6P6> zwKs($g(GQAC4BpSAPYqcMcsxEsmCMpRXtTb%!@hC?{-Tbt}V0`wMXb(8tqLz4Lny0 zUEcahE~ln|oVh4DC>xwXh8~8uyAY_CrjnM@90`Q0i>s^A4PAulp9UxPOe$p9x$blD zFnIWIPAr$voEK!1{k3uSQ+`_2t@LUA(jwNw!sVdPgY@N~|9$q>#1ItfMRH9yw4ZS! z%AJqb=#Fx2KCBwr=zgl*e4(CnJ2HPM3(T^HaFM=I{dAtvnsCuZ{k*JE-=pc$8P=Z( z>Tn4<#GN(eMlO^T8zO9w0Ddnp0WRF+4 zUbbFF+jhty8T@dYYL7?1`1A^2kQ|dOxHp4NS8SNC2MkwkUuorOn+yo8addIqZ!ew)=iQZ= zjV||#=lYzjx^-*v4aCKn*G@pkv3;n`H1QHc8CgH$aCK!~O}>!p((Lm3iM^1iv3qq> zq#2xAF>yS;<7^(DeSCl!o8D-jGuh2lvBy$(Uv9#@;?;A`Cv-s9o4d;7_)W%cYvU7w zqbhiK%X)@_pS-AEs`u_#kqo7Fx9bFhf#T$7LncY~rp*f`_h3StvH{GGZI#dS9_uA8 zp{ENBvXG^Tb@}WDQ~Tbnxj80H=SD0<2tbEm^9=IofB+1+zF@o8*DP}1L#>Ozpg~#0No(H*KJwk+ARLkb4BelCCY3&Lyj{(F%{RK9b*+BrWPoW<955GZ%1ru*UbMTzLa z48)Vu#nbCbH7>+G9CU9hWZwC3Bllr0M0nqEEX4aGwb#o;7|GM$;4Q*sA!G!g%fvES z@CVQTe?#E_hrbWK+^(4X+>UZSYSaE|>bk=i5k5`Zr{Szexm)viv=Iqu^)AE};o5n|k*VX#YZDy&)-ni8BK~jO;>l)#6r_GB$q$1R; zRgJiT05A>QZ{|^ZrN=b;n|a{=SQ!4mu`-(WF7(oGl&b-_cVWNy06bvJfiz1oJZ2Fp zJ}OJ~k5V!1fzc^}poO{%!GU5_it)T^;nnsxvfgd93gol#LS8FIf5wYR4MDfMbH1xN zzl-#AeyhNDF4F{l)}<5(^ONQWB1s(QHnTM*w@~I=2vL(QwRvY=lWRBv;A(_F6TxDM z1OLmDOmKZp25=kY_$-8vN%f^x7EYeR>(*l~AWpz;j2QNLl<8Q-g{WDclw z2Lf zS}?i~X!HWve!5)ssZJCX$aY%Fn029l2|3fE1h+%`67}`LVqN`^c9R`_-8#ft9C9-t zv!${Kg`Z!ycfn5Lux0mZ_xEBKDqR#|*U<<_g86GHunS$`f9`z6`a4_xdBqZE^*aDx z{#dq!xKWPz+l=S(+G1EcW^h)|+?K^}ztxAoqj=db!SNt8r5_B577=JWx^1mMmn$~h zXVfe*K92=Ylpin%EOrl+Z_xkD=rbX|+#;rX>y#~W$51}qO;E8`x_+-wtNCq*S##fM zC0m<*-Y*T_Sx!;Wm=PZNUGP?mK?oiLzRw&zUi`?$-KI68N-!%PMW(&zK2H@XMTx}HCK6; zipvXqmfic-Sy$Kte3=l4OxI;O&&QR((vGmv#bfi53{940c;ESZmgalzKyI_5iI6nA zjKz>mGZnK=VY-g~%U1#!MBeRxys6!+W$_5m1b7Gdr@tYZjvjAe^_WfUdQ5QdoUjG+ z^WZsn9bwwcbDDu9Fn^-1QtYN`hz=C7AMAaHKgb|ZcGS>1x-;c!VPyf{V3e95=i0@Y z$lPVvZPb{Tg|_rwt^FKNZwzurn>LCi6Q9&I0xkPGIDx>9ldwv{Y3)n5W;sMJoFj)X zX$UK*r5nYDGajQ= zH2XSmpNTeAZTKsF3a?RWzED?=!68b}CbdAz(P?W`FRRNWx?xI)ZK&DLAXY5kBhB^i-nKC(F-WnfaOGr+#vkU8Z#^YN)ENDst;p z0dnMx#qw^3^|$Zvyai?a`PBZXxY}v(4R!jVl+~e2>w*CW$d49{|9@ z7_aL+G&)51iS$PI+Uf3>6>BVf_cL!nWWp}Xo8MgdfaG0B`!c&hbZO!bFX*>#4~0UvNJoNS=> zG^z5gLd)B}rlV!2vBp5N!#E2z?osEtU@_I4Qk(FoN=yOjLX%AIxV>-_DeIQ-)nzn^g7_-lXpOgF&n*3gh@DE2&QQxOH7LYMG~BY`V*i{)F1(j!T6I3aDB#) zWc>)cJhtCtsK;NJqs4#GUP%fT7f95lCxLyIT_*CQiDq1&8*C*)c8avb+vq&mS(y23 zu?()O=Ic!?`H_)tO?*yvezy}@!bAjnM>^%MR~VMH>H+nGyCb<)zO_4*JSF;6@+M2& zA%6d!+-7y22c&MVI<0UgOk+zRDL^v;kRax*(eosig8s|m(yZ~2aI@)J6d)G9zhhyE z(r1kN=yxt)s!V^Us>dYt$lKNM8+=Yc!l1pn+q7F!J4*02y`*=E-PoH+QS+X^S@)KT zK*5NAap%LlSoQhU#MxL|nvUCf#;i_U-qtj!w!5;@oQoTr^~T(Q`1g!%@ai7Xwan7_r~Qb&$d#J`M^`-r?A_t!ivr$Vy$cR$^#t$V$|W zq?fOi33a3OOTfSXCT?QtYNsO|ynCAO;+d&NFa|N2B-4lmuAXJfTBjcG=f%gXlcg7H zffA?6^DalI?v=ilgORD6WNDsRmi1W|2n1&-p_0#{+>I9IYE$r;M-h@{&g?|S?%@!B zLikQw*2p>I#s&Q;IASc$u#0M!6l5+{$TUzWwSz`-oaA6YR5KGF8zNaCM!WZn?7}F1 z$~~33ObC)q2jz4hYT~S;6NFHf0r&3h66M+r9?0j&)Ai(sm@S*Df9K76(^Onqm%Y+Z zxs%TYDM)=@)@Ph6$Oir8R?W#*ma4CaibEW`w-cE;LqKXCMZuS=Jz9u-M=`665LPVE zYc0Vr3cPx0++$JSEvQXI2j=T9U$BgHK_^x`isa`YE8(P3+$-&9q3-Om6{#(srlyj{ zv$M}fHmaCBNn~wwjb)NUOn#MQwn4GYCYm2!^Q=PW>0aWz%GuC!2#lNQ(^W06nx9}F-*b`Sq3VHYh zHB;?pcTHbipZ>~w*?13qe-n3jeS19W-Lw;h=VsiANfvlk?;K~`i5rvtiulabT^ZqS&nPn^nG)I zl1>e>kToogFu(HH04I6#Qxp}xZKCltYuG>AJiKx5?O0qmF;JQ-a*)@xmN32CXyb;S zoQkIDdM$*LzttlsvSicG-lg~%C9Xt^_~lbq zv1JKJ(b4`IWMxs{e?o-_F8<*shH(4|Ajya$$dQL#A&nN3 zzqetL%ffS)#@l4%$?o>|yxN)gT&+W`Q{GFLcJw@-L*U_^Uw~O3tyNIc(Gh954dtxG zND5f)^V`84R94onp#Jk;%yhrvB!5-Vv70O6ljoGuhPl%lE#>$9>T|JV`&o*0QYRf!rSxaS9k-^?)l4M5&BYOIe zFssj9C~P4MKOySCNrK$3t9>(0_-;t%&{C+u24pQEzgXm;G$+WcbGtvOnc8$%J(6}}B`DosNv2GFarG(-9-yq34B52Zdo?@jf1GKX|q0>7bw z>O>IvHzON>j?0%1z30ywiD#k4+qCI&v{uyXP+K?dQ!Mm7_vRS>`5vN6Q>E3z&Njj5 z$861u2H;1^GKP|v|EG~x@hVn&64}(^62&*!BGHmgoRsk+{O0R&*#qE%Ofk5F=B>MInU0K3_D`qw?v=|RTuM0Ul-||a6Q}|6i ztre22PP-Qv^)P`ox&pU8hga&ZnLPys;wf(8Kge3Wr4;KO$8 z;&b!%EJykVhi>Kq>sC<)b~Y*dvIJ6sZj+s7{d6=*fraCC-6kU9iB7S3VpaO}(~O*5 z)DyU-j(&m0y-+H7w{SLTQ7qZb9P_H9GQHj6FtpzJ22)+O zVWcpieRoe(Y~^h3?v<{U(RIkvm>k|&PDoR%I&6)ist05zgWg8;O(2oCVgu+4~8knZ#R;_G&YIsN5LhwrxwVO~4Sv%0g#XUwL{j_ChA z$W5!`aR>JY?tbVrdd)qx_A0n=5}8CB9|v<<)N~_smHt~ z#Jk4CyumR&;!v`VA!-MxA@q`?gR?_{X7D;gR`kh-u8DfNy1pN{OqG_dBV>0SMn)Fz ztI9AagwPb+m7ANtB2H_Vjce^UP}Jf#89|)sQww7xpX$QKr&OzYx>ajfOYqpF%lZ@W zeUk|G=V`uyR!J&)>DZDC?GQHDafTzvnHicd; z(zGhML5$T2`if|Qp+v|83-JmOJ*v3Rq9kiH>cE$7ABwraiIycu{$G4f>HI#o`x^(C z(=)uDl^RTS{2*o8Zx%P2+#7O3sTp2g6(eusaF?EkwqN_Y-lEg5q_{8Me$}irCg|AT z^X@wu8p>T+OzE^%-e_UBiA4xa5@yOfE=`5Thy>-AlVgN<3)9%k{2{o&b*6C#jjQ7) zURsQ>x=T%R*beF&oD!6@cY%`)S%jDM?EC{zV=3Gt`@?N$knJ#t z{bhS>I0x`ECj#<6kWPM@%wKCZntfxcTKOL8=wg?;8?Iz+rO@HVe_<*3g&4*&FB0l1 zwN0>4c5}1MFZ55SYqE8Qc*BXMGM{D%&d2QFbH%_>QZ6M^S9knrX(0+Db{W@CMv?uL z@{BX|es(g{$km!4BXi7NJ)v(q;H(6Db=ev*v<$Xl+FYtq?_r%b`u;!*a?L_|X}Ulw z=d`g35}fj1qV7k|I)IxMY#QsRW2PENCM&Nuv|DAWdQz782EKlq{Ura&{qPG;X=6Gq zw?9%?*vDqu#ExNJ2I<-}qzn%9{{D0!NC6TGSsr@>SHjWEPLlI#B5;C+v%upl|H2&X z>K3%FBCwFzJ8lxgO}RCF5LMQ%D53gdTE|W%2axwNJ@ve$rL>~tX%(kZofUxajZrDg zQ$-liJAJRfDB#?Wkag&mq!!nTQmpyREFaOY62ZItjw=axUW|3xatp(VE04FpXjq3_ z8P&tTa3saFKTy>>{Zsksw?(-Rfs@sDVk?|`Qj|L3g0hr4q+}7UR)VDtgTnWR(A~T_ z0qSfldNC`78;DG;)y$d@>ITBS>hGw1ecb+Gg5y#6pTDwRvRw9S{v1I^KLEWn)^?Md zI~}+ZCT&O;ET<&Q`%A-+XdrMNChw&}4p|=ASgH#UYj4RB>6-%d=?3i+6aqxtyG;^Z z#0FTCXlp0T!Yp$eUkBnP(3ZTIWz3fwWa_0Jh#v`ZSx)JxLh?I~4hz6ebNev4-Ru?pCB~S$^8z^umxa842h%!)xp$x_!0Oqa zOG>H>DQr97M;;y?_2hRK9G!}4GYIW7-lEdxZai8`n+~~Jw@}m=`~+wVhSEo@X&ujZ z&Qclk)5f8&)zp``Q##NVM0imzzGt%axpTm#lEZMSzP(y$G!WyHkIjPw%f)V8TN5*D zyfl_MKT^vr1^M&k=vq`-O?J@@c6&^uim861^JBQk8kKs|Z(5sNctipksxVDiBZtDhoGj{yE#ieeZU$79uC4c@>%K#7XuRfqK*&0Ng7}@WKf)J4h9$MOYTwCs~N;)=UZ`b$HpWWNu zPih{8X+K6)Ko$Zh+f|Oi#sM}(vNs3hj~Mm%D5t9SiNv~{pW%~9jU1hym#A+-dGC~a zd4f1J2d5&zT#;h7$D~~M6KdoR>855@+5F>T+FhjYPk1eht8k0Eog4Wo`SZiGlI-4S z(w8lO9A~Pis7rVnqRcon(XBRfW`jO7N`e(m7;P3htP>p}x~DJK3*QIbW+slEckAm? zj2ctx{gaN<`%S6$)!JUm)C#Pb< z!?0xH@kvuv?PEAE?D`-x?jX#@h)8|~fA}4xj&KTqPd3&FSM3w*2-odtb|ym7+uqr5 z!TSxy87n30yt`#qY!=&5e%j*?sZU(sY54V+dLK#l-zcg9QGC>6e@D?SSAY1vKG^-+ zVnYt!{-L+&y}KU$ub|ACxm?>iKbO7C$5Lun=SK|d9R<5}k-ZXtZFQ+8jWXW62g)g0 z2isggiG=ZP(h36d*zJ)lVz6ayE>A|JB{hT=Gu$%wKf(#TCB$s1*fZZDzo;yxKRyZj$Dh(;DCIISXj*n=*Lw z!U8fq8d6-&R2l?~&r}4?V*rqL%V44kzQKLzETv9j9f!FM5 zMmsE$OmoKEZB?Ng5H$fxZjwx!LlSwCOul6h1pv;B4HOKHj=seT#kD@8kM(dYaxTSW4-?Ak-5aTbnfdl)Yut9-%P5a4SZYVCx z(HF}u6w|xov)D=N$764u{LTvhGt59y<#&d>ta+&Aw&u*!)IwDLYccX9hCf~=hqhV6DD+32jSI)d@? z@ejHDJga6l=8;eu+~NvD&3f{qC8JvphV10N2ZA@vaCRM}evW8nq1?sM=&f;B{9ghT zELYpW5ZP&z$v%D-4`6t4W9`VWRW{st$?C~`$gZQbl%0-gO~khF$EjY2InbC(d|?&U z{!hucyyx5>pu-eM(-z%>E<8;id)K#0W7z1%J>n87cfy`pQdw40)lZp}Pa7GOPY)6K zPJj2znyC~02Jj~jUnWtww{@xD4n3{JA)z%}M$!wYJ1*i1##$A%|# zsbd*OZy?UKvz@O z)idS+BD6+Rkna78C}g;5iiglIw2P{$EANt@xcG7!XB5vL2k6OX)^<1&cq+&Nf#$WQ z6a)H>X8Gw9C)f zd(P1g`ip)97LJ-n?c$QGD(ph3as?Ip51(oC@AGNMA(bn7u6z}h4i@nRb8Nqy(N5(& zAesvX`}W6bj=cKFm2_emlx$gjErv*6>pZ5JzJtDE*4=+OheN>*CgxnN)kt>b9o!%c zbyv;HFcFAw-G@eeY!E{OBJ2_Wu_1BAcIB);ZBWQ@{-)lUUMvmS(|dWjpNT!r6E^9+ z&7;S(WQ6=1vldj39n}4wXoCR}AFeBDh9Bago#xu3%Cl>53moy0axsnZcjE205(@d3 zo)4$qKeoWZBfxUDYn#Bl-QSrI&)oIj1@28px#gsp8=84LAs%19vQZ)luodjFT0`SZ zte?wTGD2FOkU0|~U7ny}AcmG(#z^-*Y=47yfh1XJhx7ZscnTv4qHN925qdT$hoBQQ zG%Jakxm-2m++nZb|E9bp8B!^hS5XTjuGmYu0Yb=q^RJK^YIm?JMbuG@+ljR~W|`^rvn*0B}@hMzoiWWV+3)T3IXcaz0QWZ)}6?plcBX!68l8<(#pBm z7bl$|6Q3Jc+ZxOWdHcF6CxQm)bE{WY6Y1Em5%&q*5|c-AYf!O(w?B_;Oi8#@3uR;o z6wjw%Ns|Nhc&GCVc#ShwX@+EvH&ry4O#+%$Ow;T(h!6^wZVS0VOD`g_bjTpH_f;`U+M5x^21CLeP1i0a z$^U>lYeC2GkBl*C?L0 zZ_4IPw0@3~^O>Zq(@t5VVvZG*D=gL^lNhR)Fmq>s8PLm8gHjh?CDQ2t7H*$j8D$#J>>mw zSds6>J^b?D3Rh8=&%4B%CI8hb6i*&0`McYwwaB+N5$(uY{K3(p`Jgnr<*mq_?sK+M z4{@=epQk=>{(hxQ70}}#baYAk0+5rHRDJATKBP{8l0%QaN693Gg74?3iB@S0!i|)@ z6KQcZu%fR%kiU>9pT^nU#YGjCsim5TN`fhGGqHELZJ0F37p$+`)xU+gud4hT%lm%- zm{uiyY!ILhXt!8&PIfhp8>jK|y4r5iJ(CC$Fuf=-HHquzeK+!7H2hd>;ktmc9LA4J z9-y;=1D+3&t#wTtID$sJjnRuoMv(tgRR0T{|HdyL+q*YuQ0Qo!>QCUXcFBohIlKo^ z$9U!x3wsW`GJ&$@sh?E7# z%z#uL{;S!dje0qp#u59#{`w6taSId{I&F-f&|h}vGg=&WB1C~rMtj`)^;bv?>I=^P zpM5_*D*dZO{8k(L%kUGXjbWi1GWG497$vNXaklX{*@>@arStH^tGBAf2WxDr%*LyF z+8PHcBKDE|tQ3qu@2l_$DnH294c7GWGV&C~ICpSUPtZ)}DhC@+8`nO_)E@^MB!#P> zC8}!4^K2cme+5&u}wt*IUPPu1rVJjJ{o@reKILrtgJMrfXFE7Kn5bH(bne|RHVg7!kqb_*>^vU?)Y z<+Q|30=pxkCfm(N^GTi76gzOVNqk0rF0 zD(V0hO03iTk5&1omJTdhQ$*C|?F=Ylh-E!ib&o?eiIgRMxr1A_ZZkH0Ry5GqO-02` zOG!R$cI=l-{c5Uj+wJ!sZPFNg;7e9!2(8agO4a@}7s^>Fx^3Wfvrk`qEKv*)_fY$u zUyXOQ19F;_t=~wloV#wHi;O4O*|)}%T+W34qZ|j+*!9lQPe!t?zNdKWSU1<0`W5T= zQT-D{18kHNC8xNxM7nY96RJj$NwD`kP@Ro-V+WM}kUhrOHY; z!WHvorM=hMY%|sO`>byLx{ME6Bv4KNY#=}Gbps3w?1n$ehQMw(Asi`4C09#0S$O?? z)s4^-VkP`<-TZ%1E3iB@ml6HC%dt>KGBJ+(l^C|I+)Dz(L~VhRVULLmOq&_8_w(bJ z1X-}LYGg%Oq-@MkC|Q)wPhA6=#2Co&^%p(~uAr~Lc9pPgON$|S`Fyf%2RGU$1*_#0 z0U6tpGheB}3A^vN$OzpNLH|uxdO=-L6)jSkGU-mAn)e2>Quc>qXEI+%Yl!Y^N!Qa?XG%$w%9t_c|u$S69CclchF^XQLN~W@*k+WNChYveUnyPKeJEC5dz* zE$eH~?>>EL6&hyw3f7aaRZc&6OhVVcTu78#{r032!(fz2?i-nS34WS-243QR2aDgS z(SM{TBM{~jjH_)P_v)%jdcD^*ae1hRA?CRG-Davr^MZMES98X7veBcfh0xOvV zz+EbmV6$+?M5$t{P%hS$#HSVg{Nj+r{_4cE027Z7VzxG-t;Z%F?lGyMkL#U|32w^l z3D?lyjnn0qj}6rGf$h87{sHau`G=W@L!gpZ1O5O;uK?;=NP|uc!^+nxF-DOIeU24LebRMNMvZLKGTai8ls$cpqcV^r%5CbNhl`yP=px8dt4`H%qUqx0 z=AJKTF>4TX*tMCw*?YpiG1QrHd4S$-%UcO+X0&SZ4GRqK+`n`;mA{R>OC_=hpCJSb zNUTpqGw~ZLH(oZl#i*a%_I5gnpB>&7EOKmK?~zd6hs@51fU9XQ#`$T45|7k!O!q2S z4O2r76)h$k*zY!qOrXeZ^d~?2fmchH=8t5y9?wNqNc9%gMvwN&ftTbTTo2cN8Pt>C z;1=GWlpu6@%HCwPrCNa?ohY6+#v)5Dx|GP2SKbfm<%?Kpxt9lv ztwy9%9(oQx2gB1!w@|oq++NMZADoSL5v2;-be~?XXE9u~?N-c(6&a3t-LZ6?ZOv%K z8Co1ChnEO7@-1#du+T`+Ngqy1MEr3JgZ6^=yuF*I4%U7}(MwrAxQ2@gR;`Q$DJCf< z0p>ApDh@F4R}}Tt^vx9w<{;X^o&dlb*1^gNNZ}I=W`*NOEV9a za2BvP@yFr{KE9KVHZBG3{244?9i&~1fG}ET+zl%9}`KtlB%>Es%Qo5$f>E6U=3`95PFJzenOwybEm8=P5w#=5KpV#y6CM2Q_1u`3;pM*NX(q zbSuyEO*>=n-obRk$smS<6rs&VO^A?=&CGG4Nm*dZmzx7d(7%>?!UA^0IS8A#M z$a+-xW(_4limO|zTMPK>S?*Vt2!kb6(e7S3@7)olCrZG3#ED12c(8HqdkM#h>o(b| z!v|~4@)bP4`qLugj!blP=ERAUk>7W=J&n$%?ijE!$5_ah=(E zcEL7HPi#Ub)O}OLzPU+xSp-6<(yEMj(Tk|;2$Bs941GA67@KdK@SA|KI%?q|%QR<~YqFFHb?zPk=mzA0&64G3 znoCEe$#TbgLI(QS)EryY)d6J%kokFQY1q-=O zAn{=9sQW4x0D@fK*fC_8c_2#-Un_z{{T$E+-twd^S$>T|<&RmC{)&srA6`28ls~vM z>ghm+XIgkQq*UqYiHbpU;~>=nu}&*hNmUJAX>7wlawH*X+m>E zAO%t;a>sL>Z0LrHr;FXZ(crvWg-~?;RP{^zU4o#=wpQsC2$*)46q&SMb3;k(EZuPB@JKN#PjxL4o;2`-iBP=8n=(?tLZ&mXla(^n{Cv zMP%S|qa@IHwsT7kA*D9i%-d~u)c^81@zY12uK+$z`0MkpeVor9LDd0(iv2IBRvNvc zm^>f$3(~q!Ivm6A_&7FBJj)e}Rr#^8@`i(<_^&wXXr8;Gkm3G_tDMG)3EX`LI(7|wv{u>mnExVIf?lh8JA&I$~t4tjcIvuAjT)_?$F zY>?XHWFg_fl%}?u3Z1^IC;=`r=xfVe*WF`w+dpG7i>s_}yfHYPry3CVw~poI#BTRUN({(f%D&mdXqA1C zC-+aOVTkTA@Oy(t>X&rqe6A=BN;4LE6*o#O{-{;3XNceQ?|*VeoD)tIu@32$?<3t4 z|I!JnmAb9Ri?7GIpKV}sum8aJDkeLtKxnGbNBe}1dewBXnR<2oV_h{>diE|=^B)>S zOhCs1Vmo;Rgz@D699(K_q(PUeZHFf=Odw8IY)lPrIe5iY?3?vde{QP?JvkpF}irCST}F}&`) z0IP8P&2ZOLdsI$5$y%LxF##j|k*NNXPR*49lWhl-lZjqRtz%6K7SnN(R`AQ7uE@!NMXG(yQuUH!y=V38WQQg0SuJ;G_UDmZ+ACyZ z4-2N1QHo|R5)a~jHEtKmGM&9Kj{9?Gj^we9R^ppmwtsyOpHL|L@2s(aStI?KHCDIQ z-MhzGy8*Li_&w_YLIhgXUJQnHwD|(PUw&x;-W+yoG%NbW>TKf-``{V{x6a7Rc5Dzj zzXOYngj^ezHJH#%O2`7!QRUm41$CFaDhub&jz6(B==&5#Ob|NTnr|FZv7isHaP^Mv zyaR;k-BQ+|IEDJuQR}6$XU{^NWj;@D9WsAjBijOM2Pg23V@TYoV?(?4il=X_MdiVl zYnRfGUWFoa*{tPJuyE>U$swvN+i0PO$w86D`hO~H1Hc)ikLFPz0_HvWZ5~YIN3HE^ zhc^zP7y#yQeq*j3y;c}SsutnmmU;d9l4delnMNhm<1jo!skq>xG}_{nW`zp3f69&B zeE}zpD<0C$SZ}-Xk=qnQcv-Pe1ILt;cqs*Gnt%sAgfqT`Me5827k-E3K0Hu95SF}I z_2TY_hs^EHy-u4YSA1Ku-V$?n!_mDt$B*;Lk24l5E`geA+5AZ@+AC0wM3;9Xf(&;_ zE+0T_I;q*aP+^r+EvHt_&DAsyrRsw_n-TnYhU7rN#xK9S2*Ac-uYb(}EVX&|*PM47 zJ1CDx>-On8+xYucIG}8mA5sAf$&HOQ+Ltx$`PzCq#;ZKi@dAE>aofzbp=#}fU_oOu zb_tBcJG6dfV9L3Twa?I}hUn?%COIP$wN$1#sNnB!Be;2mSaXks>H1XF|B|9-lIk$x zoOpvx9pDPg{Ss==vZkd2lJ=BN$~wjZJ)!*dFecc(kltNt;L zf=V5c#f(J+p3#o9Pya*+;YV%uzpSVZBp~VE1pLxRh4imUU;mp)Uv3<@a<3`T^E}En zBP7+t)+V5hv}(VMRg2Vqo&BU<(e;D0_Y;jkQQ{#>Mc5z;-YnPa_>6B&n7OO=%z84d}koMqM4>o$! zgOZXH^fxczjEWz8`uzGhHR{n4b|1X|%fvI`AGJSf#Vu6dINUv8{q_ed2a(z@Cpp2l z8%VU14fLWpTAq^gKe1@$lY(U0CrLsV!~~LAvs_x2pnfTA@sMmy=bXG{|0vrYLvYjL zN`$844gc;kWkbd5Y2rE6_?+u{i9(sa%6fxJzmbnZ%x+fRCHh<>bNWBrm+z258%FNU zhu0(YC3S>AJ%>y^);K`mAcvB3e)E`6Vwb>+@h$1jNfLeFrk-4(=E<&ck2N zB~<%l8Mf=V5vjD%zWvTk3{1AT7msBd_!KYypM$8`S{_zShvIM7am-I7vIMcQHhZ^{ znO3%q%O1hD!Y;lCzV9bujQEs^1mbNpxjG z?jp7*o?c%Kqj;Y3u_;WIsw9q!WP{Dj->0J;$j<_2(G9|kM(lw35QKgOZ5O?p4l zQ3*}evJz{`zjVUExf!7biM64D#tjDT?^Ej zx*|U(pu{n-!89e-!M4zsTWn1}b8U@+xtfN0liR60NK4JA#%EDl0ShzI9_Q=kCntb`>?gX(mm$MT!+fyTJr2#(9h#RU=L742Srl zc9t;F!{jVQyd%=Ss6<@rpWZ?g$i>HWeEgi*|DS_)Po-)PAPtpDV6gHo@+ z7FrY8DNTc?G}RoCHy*=utZtDG`ChJ)M4(nEg_(TjI2W9Z)VXs=Z}-)Y7eHBs^-gs_ zf~P1(0*&}FZ_jS~VKfis$%|OuGnW@HEI)e-lA+vxH3@a@=K^Q!`?jayES=R?z{}KH1{T^HEYVd!_Y}H+$S`g_J%C_{d5XVLaBq_6pdF&a-l^%mitYXW zc;?=uO^do$Uynr{unN=6TaaWni!B<;#Q0)P=eS17LyppVF{TDxN}*%F9tX(34^vDe zp$twy^5M(WNn0FAOzx~6sHfe&_)+6|F<^@Ovd~b1P!Ir`{a;KyV2w&V zEY}6*!6#8PE}oV(WGCV_X*$`=6DHNZ(JOaS7o&{JoY_}9x-*?zzzhO}Ewo93@-5_0 z-eQ6n43jvG&mrb6TBWk|!PZ)FyCTc6Gd!E)iLO8+fZRcU_TJyDD(8}z!=zqa1#gxY zZ-V6i;p;7++FrVM;Zp%xq&O5Qw75gj;w@U-36|jQ?ogn3ae_WXs*6TGXqk()BnA2XSfyQz3b z02XlYidN&zjNdcVoh;1QKG1keVdNGh9)+xVSVIG}@EDT6eiB+Jw59&kAe<#0btXw& zD_+y_hxo;{JTFA^a2Hf4)aUckui~B3+M3e43cr^=eL{tx-9t?Xul)6exN4+xvV(1& zcxFqXeaJM3_xAc~}-^=EHsRtvFA0=4DR?O77 zSRy#)O7jrUCNK}ssL0PQ(#+8@QJp_HPhF=L+%I*$tL!)|b9$)mJValkwCBk5`NDFV zqv%qu&vj32*pcRRJ-|cNOJy%&KkPL9ECu(eC-^>=1pOc!mGnFn=h;pCT^un6DJluZ zUEEDP&a<;rRMLZRV)Xl1PcZJ&^OV!{{V-UBmx_n#dO&ZJzz%53i*^Ms=fKl9*x0gtogoQ&whL2B15{s3zpSTxy2;R!rNI6n98p%q~1ZYhri4a+b zL%wL(fcJ2tf5n&0Eas_O#%@Kn_EU-m6@;v6L~kz@+UUmbbpdr;uL1mKEyayKSZ z`eRcjH*)VPzywAhMXv_12~@$NIYv~!BHdWLHuaNXSed%EQB)vwcOwD z%8FhEH-sxSmKj8LEN`(Mt)d%@0uX>oji7@UoyExiC_w&$fghW+P5{ij03%y99|!6{ zZ3EBs$A^(tLD7#*9NrfZNQwL5LU&XA2dCaBJT-f-XOO72`9ohY${)b935{$iX#X}5C4e>Y&A}HkvK5>ftEA&kBwPK~k(^ONE^IFf9BIx7R(j~Sl z%vI&Vyy;UAB7)Mn;p|j^{SQv=C{mo_#Q~$&oCFJVgj~C~`DgT9NC9OG%#)2~`<%8< z#+n%DS~}Hh7N~iq3dVkZfB!b7zt4Hh+HdB4l5=-0bwH{Wo620mBoRl_QjvXIBXa|b zs{F_7Plh?4ilSs77OtVziU_g!OVYNSA>t|`tuGzx{4)={V&=*9eW9sDRiZ6?R;l#Fj=fHzQG z@eQgl4hjf>#L&h5+T5RGC43!mgEHufN8dvBmEa1}CjeL&YA~#a5^6e?#E16)U%prh z9}!OSaw=6Q0y)MB#eFhV@Nl!~gS=ss7kra=gcoh5GgIVfH?ecuG=Z?emfmcK3&dvZ z3H$>61)2n{FGf&k~nwnj=`p>!3Y}DH<>EslX~FJ6FI=s zUBD?%m9Z&1>CF@+$^a8k`1`~;g7lt-gY@YqX;7_Xp5ov>leWTFsApo3v;4Mqp4vE> zTPki!G~--ExF81jHRacMfTgCns)YuQkA791_9Jq+$h;zfUKp6VLjfte8C-_E?Gl2UMx0_Ft?c4 zu~aD+(V|g6a?x}rt(sT}#yj|0<8;zVFMW&rb$_#twAnXvjLitL@o)^xd zO6& zJy1Lp-xg_-f9tsRJ7iWSHDY2%v{(0MnY?e~2+=sAQ)thBzbL#Y;qp7hl&M=yL)=)M!0#vynYms>hz z7QMEcXa$KO%p{!bUNH}1za@MG7-bXzYQd8cuzU}<&?Sc1l^(q#}VfYsegzlMr73wJc|O) z2IFE^^<>FFZE&5XiN{rL?A3FnaGM7FRGJahCQ*GIw#4xq1JG7t+S;7iTWi+GT-hiZ zZ|f`-A193z*L;-JgdHZv101XcF6g9(YW$5V6mFjA4mRP>R5i+6hb3C66fKNrDVYoz zeeL8Uh<0!8zbY2OdCeKrt(pwux}|pfp=FJ3Qjso!l8!_~7?FN}Y%*{b-J`HniOP`zl`uQ&Acqky_bc96N>(YuO$*E>6Na*UIonn)v6ZVvS=Hu_pZxBMbcnY`pxc#>dJaddt0^Wi!RKE84OdPQh z@s*-*qxjrT@npSUa19p6lx>i;u$C6S8WKeNuWi^)uou6IM3@EB?|$^7uk%`Fu^w|@ zMTC6b0%=N3kIKh!S|Xy5>8TRL-C~+!*{N7&!0DonY<@hK!aPsjtqq09n4jOrsCnpw zhM?owaxw)|kfb)*lO4st`Mq@YEuq5qSz)v~jm)!?0w&Di{h%#&*wtHN?YcmPetU2& zLFh_$xLg<1v@at}0Y2nf#EolF>;mJNi*KKk#B(dCFb7%?fU#rn6SS-|h%^tQ%f5;y zYS!(I&gz6|I;Yfmm}*pi`IP`Pu?B$;^Tx2rs3%=U80sp23?5HfMTJaSjT~41V5)N% z!LbOIGnSOFxG475?7HW__&0Q)T&Uy6R?KXR?0cPK?aJi@KNM|&r?OZZ|^Hkf3 znQ`4e_55u_$$RgLOg(n8yGgweg|>K4N(z2XJ$6tbD`&pwGn$QxPQ;y;Lq!YK$TXls zP4YVvmB!=spSiG=$H)-$i0i%7XpL#wDjRkp4x6JD5j+H~fC!@-j(<}Q2KnD0^5s7u zVtMtOa&ktv?xTFyW11&y2A7?p6;h5@dW7YgFAurG`~B!=GLNeF1k!RRQFaNYE6{ck z9jG6ho{Te>7}j+XXuYPwg+EscV-1Z`sN8f-k<7PnZHWUkY5}sIrRPm(nY>B*zM!m6 zL>es2CGoW2ZuGU;%z^3*fimxdl&nMrusBjZRXopC%S$oFJIBxB^5<8X#&B1?_AVo; zYeatHYf$HWe36TcHsgoLcD3C?c7==6K{m7JmPlRNi3mf%jF7q*aD=cEVyStYrO@kK zmaNuy{*^jD8?A-AM1agO&@9@~$i!DNFPqaL$kbNC8AindN>Ikt%3SXE|IGyA4R7;dBWY^c#o z`E!Y}c&d6(foFn*WR0d%lCx&8L;`<6;exUy4vB$E%4;mroec4{J+9)j7a?lf?!$dP zS??S7uk4oT>U_ncpGpT9 z1y`hM7ARG)g)wB?US$7b!+!E@2mC4yPF<_9js&>a!63mLYMc{>EAM%JC{)`SaYd#9 z%8ayl%H6%?^8ZE9M|-|qqTsU`!$a06gwlWp=lf$Uu&v*qB7w-$3jD3p{6?Jj^G(3a z1MbVc7-yOq$r<)c;dU}I=8S`jOkaEYL;`f#hL$Uvotc+@!an|ah_>az7jw6*M{~%y zaE(U99S1}i-Of1A%$>@Ac??}q$zT_75qEw~0kq*}{Yc|xrs;sxiw21IWWJ_+QVmm2 z7KK8*V{D}>bqnRfArisPL;*x3%B#JUjF&0XZbsc-8B***p>D-P%3jUm7$oytXm2Pe z@=B-j)mC4VA+*h9P1A3gVVAw=p>ii9&V|)xQ%Z-_rzHc6l>zIxPxZNlMj9l$ARpe9 z&!Z3bkpi&&`a{3{s2(No$kSGzB-yR?y0X+c-rZU%LSuQKgu*jLq~KwuUFq$bae6>x zwA@Gu|74or*XN-+%oL8P<@$9HDo8overvN3D}@RBhG}EKMyRu=F4!!hti2aO z06LSNAyC&6lh9EANXWh@qnb+nd>;Su$0l!wfR~7#Vv}~QGf{HE;zE&%c?>LelN<*Y z6Pg)T#-V;x&G;&c!Om(TwiX~}lqCwy*AD=AgxS_4JC#8w%mHrklKJKl4y;i+tFKAg z^|TVrovME&%v0T7=g$&;LbC{r zh$9>__AdDw4`kq&3a-yiCevggcVXrEcv&$*6*?1@N%j2jj`)xgp|a#5>Gb{E~q8ogqbGi}1vcq855AehdAvQ+2%_gJfaZ=o5%c z8GDqDN%{c+IbPC7Lv1jumhK!bHQLT7|FcsCPE#XP$kf;gqVPAz2uR)r+UAbw>rzz! zK2pq5vy1Q@v|sMMl7)JJEoB{n+3ib}%nUEHY3>DfzJBBzs1(trdnuN^i zq^9cLp2$=nMg=YP!Ddw7%d1>ivF%-~@IxQ9>sDVNVPZxNgRXrD* z=R)hHTRR*F!u8Bo>;T`tMSWxhUm2Wz+}`^;2il9kqL1Im2ux+H5;KD?!Y2k~5*_JH z6WVy~^v4SE)wWj)ZKmJ~)$`jr}yN20=o? z{3{y)Bh^Pehl(Z08~N*rBTm>G(2?TRX^;?g+1bj<41o_Em=|cjba)_eQpwRmKk7)4 z5o|lXpHW{W+CM0}l)vY1sXJ7HLs+vyOwI zsI+xwb7NQ@@DrS+0Xd?-`CP(EODxwkNV7&-A>1ZQRHq!k7l_8(q=Q<0%!g;d+>}$1 zD)zj`-$?jLOuHQVYe*&)eUg-QElDV=wE!`+e-TpOsUGW==k95QgXCJk1p{Vax?*1C zS`Isx*1{DU)sEzEPtzGrRTx)6u)B5C0Y@2m)*Y4t2WjhsOqPl~yO;*7N2llps^7R0 zLHZk4e)$bjKlxn|u=zJ{g})bs)l1FzV; z9}}yE@K10fP#)4lt{X4A03);5bF&!?*`M>yp-;_bLN-mt{HWky@vP-YLN}NV`@B^1q3xZ>>v(@*Il4ejGpz=zcIykS`X` zlVPbYT{qo`DhWpe+3k+|uhu8meVA16A-&{Wus(;&pWkaIq*;!yT%X_52Vsyu#_zl8 z$2c5~SNXjRKqTf0pW-HDs2w(!)6E@iWTKNCD$HFw$34V4-qtx4l?i1Wa3Ai{3OoGZ zutn+tYL%}l>#%8*JqieHrn58(2ITmziB(ZB`C4IoWoL*ja1&8@ZQY zwVLi=}l)R${@<% zg{1EpWf&qNns(gM>Uk^a3KyUdB4TUmXB1?o=&5Ndm+UHUk16zfAh38p}D0HOnLe>7oTmGWX&R)PE_Oc$*~KqJDC6DzzNm zc*(>U=xOL8`>TnOE8p+s#1Y;CGcRHE)qtej&BP%e!Tw!iuMb;XAGcb}{czbEwEQLO z@yD&4FP+poVF&puFJPQ7( zBMCCBKS#eM`d-nn81^>XLB#MR4N0uofgpcQulhvOtI55P~p|e;%wdM4{0? zPsmJYq5rc~(`D)NcW~dkuY+g{_+;nj!$AJ*j^#)1d;QEw*4BuSMd{918U!H)Mmv4!qZ z?OPgp04o*5UUOQ_GImeYR7$(JJHGE&&c2_gxLY|}Io0o=aeeNpSsN44VCO;jk*{k1 zpM?7Ke`T0(T(Mws)BeKMC*z?2g$|H+YJ3*iU>u8>O!R>@FD zU&H9agjE2n^l(PW2Oe*hei1A1YBn+J?RCHmcNyj=RNE`&7u+_6utIrVvqHxz(g={k-;%lxe4y2g1DMXrh;X_l^ueMRXl`Bs^X2< z_E-Q%x?Rk^6!w=Wlo(9BvXMp)3AsDb&YV0CeuH*lwNOLJ;I%UogYD>s!ZXy;mOaws ztXCz^_&Y#BWhX?$BL8{ZygzOu$}j%&V3lEJplCbomZVm_9#l`4N{=H))SNP>`2^F| zvqwp`@Hh{?J|9l5Lnk2sx;?tiq(ub(RPn51Cu;SIKjLKDT8~ll=oKh-pb;%2qFH~2 z;|ZpuJVtgHAbVL5VMKsUfU(?8`uw0#^;I>DU_t+&aoKznvS2n^Y^ADTC@Ep&rk;BK zHI0zsh{(3mO#t8W zK0T$7eUE81Gi8nu6^3TQUb%=y+7M4XVbY8e*Yhp;Zgp?5{1@-cK#B0Z{(6p1BBt+w zx?sKe^)A7ZZ3}y$U|p_)Io$m*H^oFj~3N`Y{fZh z;m8z%%32Fk<$*l&tT1;U4tzyjdy)t2b~c$xz{g_|a7QC%_uu&bE=MwNo~eU_DxsUZ z=t#|m^9up!h_fWHZuXUyTOq00TGu~T#qUgYx0%3f$u9I(>Bm8G*36)h1~|^0K~Utt zPYNL%u#&$g@~o2(^B>T0Tz*_CS}fP!WY1+U?b#HhM0Rc?kGm7>O(e@m zYd%DN+Yn&YiMjI`aq>hyxGV$w+W6M0#3Vjz~9D-H_&pcYF zupFtq*Y-iQn!}W5E#t5mI~e_?qI^UJkJ#hl^SS*g2%DQl_ffd|f`V#g_dKn8laP>a^bG)Le^ffha{ zk1eV5;bO;$rn-HeVjqN~&q2Z68QwW+JF_XHNtX|=AB|4^lv6c%L=9L+blu(y{klM3 zhz&^1h;SacId~TFD#rsyx4BB8P&7vzA+Syi_};E2MqSv>Ray5yMlPkc*Rle6x_DMz zI#x&o+oyYDbDy$Wmw}9%O*)X{v`!g5Lya!?`lX@Y-#yD{qMi(*wf!U--Poc4H zHx9NUGE`E`zrhJn3;cftrzf7`U!!Mxa9eKjC5@G~)=#6cn1roWZZ5S&4tj8DgeXv# z9|lT1u6Mb51UL}BOGO}?u;y+h7s{L@`|)cKhF5==UN;gh()%n55e?2yc;r+AA4U1C zSH%RD%_%Ke>=_vZtOehH?8YWfoGekTezh@>9ipEzr6&@xqjMejZ-ttLz6&ZDVGIRIMB;_=2kX#nI) z7Rtr702+OjHJq$IhaJT@)2gQfJ&czl<}M2aNceZx2DOhs+Fm|eeQLDx)fe72no73S zhKZolIYhF;0@yP>I;+rK;Jy%lIHzuL>gI1gwEc|f2JCJDtdye}P_+O8wkv+6VkS)s zWGGrxnWoC^`VA>`Pt5jA|8TYzvU4c}okjxBCD2$8Z=2*Sr$WKBgqgmV^Ml%PWI`mK zzWot%fNjJ-WT2KW8D?wsS)yVe0V_(q*Kl@&L3*Ik=JI&a-31BZ=Kr`t_4` zw{~XVRsxZ92o^9;^e9kIm1j&wQCMcE4E}9Q$O7)+eJ?_tnckjyfGY? ztnx-XZtc3GpyJ)Tip+=Arl<}1z&6=T=H`Uw3pIQtt(g%5Jg;CNXA0rVG=Tpor*MAV1Et)jBww z9uX!6TWH1Y7mUShmd&K?`tgho@M6e6r3vW!nTg%;?6K2%$OVB_XhAMOFj)yOQ4WD# z0TO0&pHCsxW)uDeZl4B_gHaI|*5Q>Ujb`B@Hd&x%6C7RbA7h`e;8I`$@e3xtggZrE zTP$(M_gt7d3=YA>aLy-uq0(Z{r`e=cS25Uk!7`4^FF5 zb2ZqWn$65+jHO&Ba})g2f~)VveUES{uZgahss4Q%sYQ-o$VV(9PG#QcJ#mThU*Z>F z0xz6Txno((RB&SiEXhL+MArJWN28x8EE}*PzO&W>6PPcLM(dBhU~aM^o^CH5f5zNo z-!|Hg)*^%c-43bKNSHxV>~$eOh(0b9YOaoZz_!VLP04Dyr*5Dk>?Q zSSljKnL5PlAv})MgD7)`_-sEL8dqR z%!}twx!Up9^OU>T#VZ3HMiRCeH|Y_wKL~DST-)EF=6fGgn2_b;JJeibo>HjgI=!+S zT@sh;={ONF^zvq=PG^DG^tGjv@$J@|F&d?OLRaAI8+|exY!f02e1kVltfSmNhgTj3 z{U%Vn-*~6daJNJjVBYYi@!GlgegS^BK5oH+IeLngwlfbOg|fAl^N;VZLuB}K#CWrh?6jK^v z#m6CP>&cf1cbA(hJ*`iDp9)}PFsP#W`s=;9Gk$k>4}D6UqxebJpUQWSl8Tj_@Kk zokDF+)uz8N@k!ns5bt6iN&KnB5FI>q=A@aijwr;>UPW}5Evi)22MTInFfsz4O;0a5 zyG|FFb?i0PyqDrp!qnDFUz3z*ei#;HozT~}Epgs(?olgbS03!aVMcceT7zu`|tyZ3Wf~U`g zPyrL@!9`eQ*bqXqwL`rl{p zqs?XKjo_WC3#j)!Y_OhxtGPVGan-3s6?B`A$QavF<&osCa~8WNt z+HP-ib0p>Gk@j2kUe~Q%q}*C07n3pUF!^RjvjXZj8=U&u0XSQy%VDY`?yMR;D?cX$ zHaJWds>^aA^eaTSwD+XBM}{}t=L2Un%a~QgV!Etu6<60dvzB@Ve%>^aL<{L74c z7A-9Lz^B6{Te%kGrX-cemp6|ph#?Xd2sUn1Op8p$Tdw|AdD^zH|^&in%MdPCn6 z1$7FGIh(n&AnO|z)l94ZrpVt*jEntefwBEJZvcOb1)YR&73^`9ZmKN@JEh8h)Mo*PRxL2Ng{XNvoo9gimsjRsC=er01HO6~?})+at_7$tBBS5fstGs!X^|s8nV%o#mkx-jsgn@xel4?bC@2}87&26k=d-i*GB*T*){>bC!H!K^GUx&yM>TcAhvbc`;sF&=rOG5Xq{u!&_zbje)&qy)R$C~d^SF-z^VrqP$!i_BjAEI#T) zuHhF!uz|Bz7c4kGxIPQ(Fz=HJ{NRe!Z09oigC|P!&tn)nDRjS>?s5oh>UIc=KkzMb z4p#ielX_h{`HV*0deB76s~O8^va>x3=W^dW!X2BxcYFAGd##TUQt=!vFwNFphCD0%3n|`M$J;OHeg9zDQ=?1A_Gx(M(0VGU&{{FmtblzpH zV1vHkDE6RzhadMKJFQvfK@+`1_nylITcO)9jB)IcA zn}2&w!9f7}xJdit)GSWl^F{xE?fT`vtIhe(YXASIroUW|Inj8Oj(MiCz_3mS{aI&1 zu^6!n*x$ZZ=W#RCZI-_rqHk5#%j2LQ@$8U5$e8fxO|Oj-0L`+Vp(}_-XL72Y#$?q3 zdkCj6--IB9Ymsq~PA6|m;h1jAKokBt%Hh@Z5qyQ(H)EfeX3nnTWBHnoejFFwefDH{ zRYW7GV*xT|yHCs~7hpwrQTtV4g?b_Iyx4F7=?$#2m1oR_-RDjY5XxDx>ksBqpQzcV zpUQ{r;FauFHeG#s&IL3KGQ+lhCxl_F@h_OQ{R3tMf1pgnLC!9OV2ogWLNO>0I7{`^ zvZ3)cFfyWe3#aG}bjzbAdO87?$%n15OJF?lC0(}r;+5Ah%N+AbgwE_Mf2cUG@ujeN z{Vx>S`?NblrW(2-oKoUBCJT_h#2UB0=F0%u2Yj%VKrxv}@1yJk`h%L$1Y1u?P{ckV zx8}1;OQvR;$0P)uMPJm0kF!`!txdT0xvXI@&aqfsur%=~Eu#h3l(hCk;F;eFYIZ$p z?Dq4~wmG7zcK6ml%FTTEW3B8=|N8UlAAd$i`*(#u{8{17PCaG-V`>1;RDuk>Yql0d zl2OvO;h_*0UT&$!+@WEkG%VP))LyW^?pnNIgCU5apxx26$e*B9uW10zNkuZiZf;L& zH}?NwvUa_}%4Cro-{uRx<2{)%$kq?T;6i^+GQ{dd%<2*_W9ISxvK^JgcY*87Nnpzw zXap_SUOV1}ijH(g*${G;wyqzT-_dSJ+B6=6oX_&nmczR(8si;;xq!4$hH?N#R*S~& zKlF=d@bBu4{aL+t$Osbp!_sg5!_s&n-?xe~8!q?jwIK~hK57H!3TYwadj!TkU(%(z zT@VuR>%R@O6_DQen}$l3W(9S=6S$xeDluDWb-N9=H?>2V1{r1P7W1Ys%YT@zYC2}V zJ~>*;%vZ&TL+3%q3lMe|@QITb9ZnqqEw6w`{#gAr#{ug^#(3gb%AHH3AI$!jl&yYppO5ehXUWwyv5;5cBj3cgW+ zb~CMe`ig75@9ME>mm%?LBn1MF`oRr+_36hhvE)DA^*`3^pU^q$PcV&$oI8X6IICH9 zMM7~pryujog1c|)){Sji2yLVI9E)B-OvNtJY_&@NLJZw+gcWxRcW^*Hh4ZZ>!L8;Lk&6MQw1TIIERw zrQ-_*=A?gngvucXg*)jq_TE5lX8Fax))4=HoQZ!e1pdcDvIfCLbI^8@2CafO#}C7+ zd+QC4CGMTI#e97l12rdOv&@7tm=#+^1j(}rFhF_;N?m#SQ%eE~7c@)4^82T)acGX= zUX+BjmeEVuX_aWeXD-AU2|DqKli4=YVLb?nX37RMN~PLbHME=)&l-^$l2&b0 zN}UbMN=PJ0G-ID*7w5A(xIc zjaxTr2x_;{zZDDNst-Izot5|FLff!M%S`iY2$UQCxrm5&;-$eds$tEUr}ooVBrFd> z6_dm^2@bEum?E6A0p0KUSq~cPZ=ae>EIWFRXa?`2)xNC6sK%&9oAsSue*BsI%#`%* zRffwMVzb$X`OvqFsK9{!b@f$lv_%hwb;U4Pvqb0uTy*dI5M=iH1@|PYRc*IoYxj6~ ztNxw!OpoT#j%pZKCSF?KMV}E1;fm&j7sy;>P(48dTjkj>^cw$Ve9y%80}4pVqd2}1w3)jt zmhzkyi|Ox}ZJTcq+jdZE@Tf9d)w1Mii4OV8`{lI{@1n7!x2IG>l2I( zI$uVz-~z8p=d_!Hvro0d2aiGw7{NKIZqUzz%^X5#GfWhqdENB8RKYH~V^|=e1LzpCLH9IdY1%sIK8^X8FXX zsY)e@j$tklZ1>+)3-!~&P1E!XRSBi)CHTAM7~2IDD&iub;#>nXbc+#i3WxjVZnNijLZt69J%y9+L z77;DuhFLSX^%GCutzCizPqFImAsY5JD`CZ1iN~)^M@NqOC9#2 zLF*+^=TCWfc&|%5ddE*4l)hdcqABMsm1q_%2Rp}IA-c)s`&7LcQAxWUp4u4ln#+Eh z$~wU{0kI;_NS$&hpvwX9`+R*N9>z=9s^_6{KzrRmQ3@0nAhaS#PpL)uZ z3wSK@6=dpRAFdG_rh$SP)UODe9fUBK;{`jbUE}_r_zkeyV z)Bv}0CaG9VhyKyL{G8Zua0y4Rhi&!OLYobNCS^&gDphf8dKQZsvF%&Mqn)4Q8|!l& z2KZX6dRV#)c^NUK6x#8*j5@eR9m{Kn+&}dQN87DQRo+3sDFr{062G&8unkMnRH!Vm zmpj(C1}FAmW=Ca7w5kKMEA8n8Nk8h2!HNfGtWHkpRv35q>wylJGgN!Pc_Jgnlis!o zg0%&RML~&2{jWT`p>HFD&A?n5(0=f{RmpeX3_tMQ9Ta$mR2ny6LHozMkb3zD|L5s(ChfRwaIMN+6dEJ)q@dqgb|1u2y+uL2^~ZWY?_l z=am5++F!dADx?-$>*n}H=@cqVssn7JZ6KOt2(~Grbv)sh1IJ9${d!UxY}2G7aE=ZI zkaDqqYNU6(V}ePYUOP7>FV2dz>XC^>J87%3V2YwADhzqs>0mkdE*)1T5W%B#?h zoxgF&&d|kKZ5qDDDgx6_%U7T}!_jME=`WlLarceMa!vPXo$YJkoM_Xbsxt91iM@n$ zTSX@3xt0aLsENCi#Zo0Tf@g2}uj@TtcS$F*p--abSzLc0>Y>}Jbe()@Qv6jzJJ2E* zZL-dOn@%$I!;M9LDBz5PV4(9g6uL|MIX=J@G+Zgp)!q_Wq*Xov6PP`VGW4-pZ~dDe$2M9bV@zi1a?OQ6N)$la z+2Qv;f6imXGJ7YKD${d+9+{!j&P``;+QKP0wL384_%;A|i;8E=pq5f1T5P`Qtw*ux za5e&*~JW3kmdhW0*KXF74#TnPY6W+k6$jfAl#EVw9SpbNTYFUzKr+Q<_$@Hl!fA zqBXhTGq>m)kFpnzwZpi+E>&7VZ6IgU`y4PUL2K%*y9D_}{1Jz6s5-NI%U%{J+dLll zr8u~ZQj^>BF*%cc(iLQ|AH}8RS{K3LmT3SAyc*k}n5vLa%hoA{9X3Ljv=_R0kC>hN zen@d0S(EHE*Bsiw4jpRLL%WGXRmlZG?^pn@o5~^_mzFdr`5My`*(XhXDlb{%JB&}9 zozBbA*dpfbJf}LSN5qRwd{R!DqjpcfxjO1H`Ba|_dt7B`2RTqsafV-KLL`A3wL_3Q1%#+$|)Knugc$ zgsb-M*qCqksXn8eVs}r0e!fPHF}Zl)>!X+wWHOG0Bd@-3L>yI+;w)s8n_!Az1?Vwv8p$8<=5kh2>2Z7`-VRr<+(~PSV$*O${#ATRP5{;TNwBDYK7m0ln2W-sK2A z(InS|qP(8x-FuG6i!@Qlo!%sTuBy6?BCESx@oszTv$SNaKs{jMyK0qsw*lk4Qo_?n zr;T!J3{mfSJ>pJo%%JLNkC3(hw$y(@wd>~pieU(-r>7y@Lt^SkaVr%`b-zFwxtpOr zxx;W==D&KwzrADUgUN}hkdP?doH@~t&^ymbbT^vn%5$t5&2gGpUsIt~edB6ueka+W zl^YybC08j+JI65D!8LB>HbAXOpeHG8b|ic!=9QA~foz>Gm#Z^RKSl$j8mfMqDj}4o z<9o~ZDbP!|hgd(<0T6|=`vf`Ep;VPN^Yw;K-Yb_jz9CLnv|=&omdBG}?k&^1W24#7vEG=7P~YWukTVIey=;@5b7V4a5Lc-&I%0*EdraZQR(%Ja{sh zC9E)ueAraIi|AA(lNqY&SjTpG!Cq$v2ww!&-F%8EQL^F~@#AoEGJSQ-*+U$tm1jmc zFo}x6cFZN6V4k}}cgoq38t4`nZW?&yHqWv|RnZUHb_jj$&EvZ9I@zEbkxbx})0K=f z+aevV_3$IzQe=c-fvwsaUtQ?ry*8vJFI5dac@l(U4!{g`_*IX00(*Xc0Cpg`_P64k zX#6}zGf&^CCTbZ3(bV!%9{D^G&m6`@JnuhuJAsKjE0us@+ek4ap!2^td+VsUmTy}) zK!5`TNU%l%1PKHuxN8Uw2@c&2Ay{yC3lJb!g1fsm!J%t6dwX?sqSLklP<6ol;0QwGNzJ-P8(eCyTENZjqJ-;kPs+jpQOBto@_H<{nIfCtKl`X|^ z{LQ==K>4u!)ojZ7EaPz8S3^$)c6}6D;Q23w^025P^+Gio7*}5>XP;VafLnh~JixjI zzPuq)+1sZ!F2@o}mHNF~k!=6n-C~@4@z`vUOl;n)0IhsX(ImByBsmoFPWVRr{+kQR z7p<3fnF?5I?pjx*lbn0^eir3wz}i^>!qWj7OfT4 zp*%r4THd1e-X=yuR6(g)t zxJEw4Yw2>buWV zH>kBu*vjNq`Z8D~r}tbu+jM>@a9il0v8E;ox=T0se5N?hm9M4in;7rplA{3^Mi*7Z z)hXCbO3qS;2iMj7EQrC%?$fBN5E(kUqd075O|ACH{g`BZcO2)@ddG`=KHl^Y{|vC* zs%1g^uAy>i$gQr8z;ZSdNEQKl^T_mX&=UR&%}TaM2S!d8H*aSr4ZbdK*>=)jaVtY8 z@^Jihz_Y2vQCWANZL50n5?;*?C{s^{=zTSuFKzjTeb`Pl73bXYEo@U>U`7`FIfil8 zI08bEq0uW4U^`+bm4IViXXei}HqO<_^d_nJr4cNfL)MIbr67UF0ryJKu9MhL;l+k! zvROig2Fn8DtT@jvO*g38clj7-eT=MJwBS{%12{v+0%`@pQC0R`<}7 z=31zIZ;!@qe8+qFC5n1h*EqiXtxo&;3dUxMzMjJL*YTNO<`=)zg4da{&1GW2@VBd{ zy)?hvxsMQA28#kSjo@mnb${4+6JFkK>b$$r?LIcHrm&vM>l@RJO9lncSM$X`XBpjx zIL}{iSvO2d2{fBrN0Bb$d&@U}1Q&z^5#)2g`+C|%8pX&eaA)B2{jpiakso}{x8 z5!EZ8*dS2_gkVkb9MkQ-Vw*-G@wy25#QyzFp;1UA$ySGu8+a(L1r3+{f0ePMeocZWG+! z^@n|qVVEn_9*(tY)L?qLxEv*I?9tR9Sa+J%|rv|0fYh`EErl07WY-d*CR4k61m~4IUkgdj4=>4k9guiPz`t>$fDig3VgVSsu=?nEecmJe)eW;zo2pafw@gBz8?zo!edf-DZ&2dw{Yb zpzra&?S20fchvv#_1`pWQsftVg%@R~9vjvf>om1tZO4Vk?1Fo zRhg<463PFxRVc?XXx0a1>f&N4*lZb&%QzqTZ9_{yGY+sXRj55v2~%O?T+&Q@FIdJ*{Cd_&iy)aeHppvUq}SP{Cm!vm-DcE1s~fI=L@ny z5wcJVY*eq%LVU%#$MaL`tUU1bkj>aI7{EE{lJxQ}l}-C>tb2QvIcD$F{U-j+;o6SA z=|%woQ{3TNCfvGVP`( zw}(`q)h!^SzamV%2e&NX5N*n-&dsIvhVOblP)j;q`mugL7*Q6N#O73^33;I*2N6WF zZvFPG)>R(*tpq=*&HM$9std606K1I&r3de)U|C%G&@%m(2HxdAY@f+17si?r+*SI5 zzlsU>(5V4mDTBmUv28mg-d|!^w=#AB2s92rP)gh4YPL{kIBzVR%O+Hfs=?a zo&6;c$ah29S!f)j`OA^#w79+~x#G%ShFhU`OaGdyLML17p;R=|QH)VJt{px_g~<4$ zD==hpW!31N@0ie}0Z}~|d)0O9q7D;qji<5#-Ptrv&;7cY!Vz?h^*_?}u|Ut%_9pwj z`B!oiV0y{6RE12@NaJR)WJ9vwrBu?w!kLYF#NNDXn8#u+$GI)yXb+n@^se_tFu>M* zsQp`**!-JuvLyYsuB7YGdx@%G*RHopB1ldcH+6FHDj#$A$bU4QBi~?5=s=LrlG0Te zsuUV}eN(fd!d$4mYE3&HAE`P#KYIkdrRyiq*JVqO-`xDbM60*7`V>#Jd&XI zeQ^80Mr8^NxU*|_Nf-@m(ukZIsK)3x1N?O`Di>n20VhjxjDl*@48+(i4u9+wabWy- zxXSEvqsZ%-OaaM6gu@FcASbqncvbqtYVo^9$vm}k#EpGn?1zY2chItgx@qdWQMp!V zJ3L3FTx#NQjWPOm_a~5y?eH(S^GT2XVdG}C-ZjtUq~8Rg)!R#0Nf^7Pw&*f!!L!v< zfdZ0j`7!r&v4f;ZS4oW}79xvJfTOB9qypOtj#=fKRP%KAS5i%~ z$I?a#Qh?lB3(QgvB!%U{4}lB~i1|&RtMHnhGGga52|)Qc*z5%*{k5+Sl_+{=gPYG2 zLAlxQMPEFqAX+^=A|DY|7zsIkFvR-Q0ICMeUL>U>p_wVhc_DLw_sBoy2D@Hk8b@?; zVB8=%DT#Ew4R)2N3ca`90Q9s+hrSsX11ba1n}s*ASEm*~_Qo=%oFd)TU7jd}X zKs}4t8@kCKiLhi2l*Zmka-w|uMPkA-xe-m*T~GXM!c-t$h>81C6=B6^8ft&zf1}QdB!@9| z&5zKhQYz+kwLp*%u(ph@C}McDCN2SyQ6xA1{qb^@>VRMsgBAZD3*Gf;awAWe+z4g% zw0tP%p2>tP8ceN1qBzMl+P+w#Y#X{q&JLdL#>m~}c-G{ZZRlBSn^%$g1~Z)%1kBr^gGc3deQyM0cw3w#J+f>fKE4R3vavX z@}Lw^+PjNbAR;a5BJG}?EZ6br><6XkoQ5Lvc1uvTXRB)S>s^aF_^^xVW?|qtavqLs zT!W6w5tm+8+^L?oLj+M1M~F~!-aEHGXR}D`IJ3S>yzNu>0ct?g4)y(cU>v93YU_9> z$JpyUoJp)=H)4JGaP7KV`Ot(l(Q>T<4Xl^Y63-ANibZ4q?tQUId-bE6n9ak{J50wo zs8ZGe|1d)pu?PwVYiFmm`%PlsZM;}3s6RTzWXG*bK!@|}-KTi;SOOG!z9!Xtl0ZZ7 zZYmP5HDOQtAduZZPX9^n(G6cKf$jwk0>|q4b~qlQY87IyQ`Wq?_#_)U#BZK@j=!lM za=radKbHz59ZJTYQfpNmrr-a zD(o;unAy6f1idoa3CotLecu?0cq<&sxRiUnM=M52?pHJ1p_6t^}>D7UNu` zMe)s{?h-X<^yuAx>UXT@xiaQD{h*2-d@#I&vGQDG=o zxxuKD`$SR@qSGIf$)d8qI?*4>t?hT_g)?INO!$Se>6{GJ0igkU6ow7~DECRd8`3r& z?zA07F9+|X3PbG+M$MqoQMxxk)N$3D)Po2kgUuQq(eHNL0|IhvF(DV?RX zi)Lxh^-!Lecc{<`C{X6#T-tu>c?Gv@kWwsPR@8ehS?BqzYqT5lt zwjznm61w%Pm@e|S^MANVGcGg%%?0(Ytr-xS5fr6lMzW|XAflck{?7O0S8%UX+ciYV zOC%?WsvtM{G-2JcZMw=ty*zTksUa%0q6DXz+mj!7+g|Uz-LFItBpl^Hg0{2?sMXoO zw~y(u)NFk6H!gh{WO-d{bm z)PGB=H`K2C7Fgtf;Q*?&78Gf?z;#!B0o;%aAmg7h;XidZvX9Zr<8s)HothBq%& zeIl0@Y!^en?bcq$8;&(De7&W&K==t@O!9^7le1HEv*!X&S)UfoXyF(9Hsv`mFXjNDr@IUgglgkv<1 zFoa;RAWT^Kg1@2b%-i+wimZ0j^D4+?fNSOPGMeguC=1=tw_-AyBj-!Ih2{(ZdjI~^1B_>YCYE`!^w6o#h?>;!8~vN zV-rzC=rrsoc2_x$#XGWCG8H1Ydb;2oqF3xu`7l&n6ssSolyVj2p*j8A2Ud=oeOl%S zhktthXkb?vWTt`AT)KM3mT21jBgfP$fmNgKUM_sF0uhYsPn|;r!8BMnRx31ij>QK8E#-MlO zc}6qiM%0tiKUXm_ z{P4OBlqsZWKiR%1U#(XFusNzPyFN$m^4g9hkGp?4#zl6ezr(ta8K4OB59&E0|2Wh- z&zhxG>sqAomleGHufTuEJAzdsNO9TG9w9bD@7eu-kHS%#^HWt>;VeJlF$K<&kmz*l z=eUPN?CAVb^R$U7JgoxZVYP|xacy%{0><((=vQ46{Va%YVX%1jEJed$thEAeut(T2 znpwgU0VKf+50c;^o}*PQmz)t;q@J%-?%^XQtkA1<|0H|GlG^b;`Kd}HD7whO+S1pn zwsYIh>HvKxUV7^_q^>mn@9?7UG$M6>aln%vbQ%MLKlVE$rE5SuTL*$YO<*M|E%Z4g~z~xCYm-8RmW;Z7|B0j@O${EIj|RUgTdTp_hBfS?+kk`(3k# zu+Z4YkVjlv$&Eh{2!h3CV&n6eeG?rHXPlJb4LflN;!vD^6JxgCcEj|cuRExiprF6A zLPLfZgBh!e6mF;fy~vP$8*?%M_}A1&#A{D-Up$u52hAW&C}0WRDQhb#g?@ z?5%`O@A^#ZA1>}g{q5qM`L0ZJk9jL?Op2b)=>#xrG$u43SXBpGjGnVph@|qj%p9(b z4^*6v^-C2|8s^K#=+43i#@pLwh9?<8G~mqStQa$5>3s-F!Pkw(!6 zt+(D_+RzR_oii?~v^nK1_F7tUc9qTmiP9**{59~Bpjg(#>{PSVLYf$KhAo4@6I7d1 zqW`cxaA|`jzO5()S=B{8Cq0KFv_1F8S1udErC1?zfuoS)fhw)HocPh;It9CjU-D$4**tS&>yBe>L%}U+^d8BGS8l&W>kT$4e3#^U zLz3sQY2|}KR;W#8Mb@bk5TtIFn;->s`=(=*A17OFtHKyl;>)DYf{bAQ#sRv~PL2s3 zR{FH}B7Rp2+K;YJY88-i{{y7hpN)N;xv13 z38CEhnMlKGxdR4|<@BUMyJf~4>sZu!AHVa|usfMC1}Blg>_uu3-lY71x=K%To$1i6 z*nF>Ou6M$z*JwZC&w|BC|K6#g7oQvNdgooO4l>oRF{^#Xo0@cg?U_b1+`rn0mb;j+ zCS1|1;lCzTTC(oJ4&9WT@#Z_)?`&+9Po>=&6$wT{~cSZ|g&Lfp2NoE?GwR}&;%kH?11`PaGfXqs(D2!k>V~KbF6W3w~;t-^te7Vdus6&PL;P{t_ zqh{d*tmJ*X7rS_x{*!vwem*oScp?6`-^3n(VA$m_9v_B=6+*-Y(wP^bW$1zm)~rjT z=X(H#S&?A{6a6pEpNdvIw-$?Kk%UnvtMDM~>B3Bk70etW6~KAaae^7zahQja$V4Jy zpC0vPB(&6)YjBRQpO$k%vIHbNK+Qd#Eswx(zbE*WbUbXH%-vap!PesRhbqR~rG z>X98|_OjwKIchja6lL#osI}#QnjZ$)pb)IMQ2TwE)xk0m!<4$}7A@mv#lA^+Ap_A+ z;BBw{6MOOFkd22FMTII(?^7d;>-w)x1pnSBwy@~38~No(mn$%FPZY+;vsA>nZdJmp zrX=cQIMJIYachB}=B(07w6j4}=FT}<-5$x-}YwfIQHF$cfK(USif}c z4+E8?vWn8-ra(eqljqZv{&v=z+{aE8pUY+ccBXqKb=ZDA3=Jy2+*cAITq&Z^g(Ecs zX*>)nDy9=}K_!Qqv^(Vd1rZ{v_9Y{Oima*Thqg5y@q@7Pi`0>gSegiT8cTN(Sd@tBX^isNI*#(J>4!&HoHMhR?cJAhr5s}O8W0taV~u}O zz0o z`ZT{XGzj#v``0LNz{Ny;yx2buv*V}+6%oSeKV7`dSEJtFK`L*ZN6WEbS;){Zob7@Z zh17CLW15g18#bs|C1kt41N1I>xs2zeAS=DS4AAK%@KdcdW}K}xUK0sL*webSNtxtU z%Nte%MTwJxa|5ccF;zLNEJNRl)LeF?DT%lblXP1&RGma9lzkyo<}|;&WMEl1+=!JC z)wOk(EdH3X6gaD%8S~kClaOsx$+g-62J=82tfcS%&DQV!N4C}yL}WvGW0WtOVZ$#U zoi{%vy_#?F%Om$R3egarP?~j`q+XcaU5j{?Brk}6Uap3<&e%bAB{)lbo9F$a5PXG6 zJMT6qw9-OSrLam&pZ28w>HX&fV~PNz7z+?XmKKPCo)T2>itIz77TRre9@axa28(7k z2{6x3FXfdb{Yqd!ZQ!S%w*h}m`YIrwJ0V4LyxwKMh(fUTSK+cSRTj<(%<&SPce%t) z3JSc4z)a{{g=xa#_T1f#H|-AK^_TJG5xE$uJWE8SI#m81qJaY9okH=|NjbsF^Cg7P z#@5?k`U}3(?3TPk&(o@J4PEnNjAr_z_sFTHRGZ0#;Y0c_bP+jcH(zo^??Ual7N#J4V-I?fX4tq|Fvr4&spoa|uw(7aq- zKc8|Q=5dET$AA#`UwDsBxgpJ*=fL6!9pjRbh^Pj|UIBq##RmGH-!+U1BM?U@a^(EY z_~(hypfX_|ge#~g31+fT$-E=4MAw<5g5gEoUk5upb#2m2RVqv!EN%1rKPb{}SUM6L z4c2M;e|;#T?`Oz67#n4Au_WGHc5_E@$e)=2U%_W@<>MwWZusAu!54SHhT{{3AuX~mKZNq@}j zkP`Y8#&!u%M3%``@0EZ!x@h%~OvSQd+(!$nnmjCLtS=hOD2b@EONp&{?1M{)3GfVM zP^gomK<7W!pjm>F$i@p`sc8DZBZ@*rd9z<2;&0m`S}CO;7^XVrr4rAg8YOFuN9A)C5NC+-ncHwvoty>jpU~Df z(WM@Zj+bk2yp=@?!i`2mZJy-I=uf{tsVA`fo9SJkwTNOe-_-=&3Hw_Z%sl3-c+FEk$LCn4 z%kenNqdfXeURA>6%E*$y!e@9=MK|%7YmK;a3sHF+>?akY3=8g*x!n+ImGd1Q-+ zIGi8M4XwH8J<-HkR$~2eAuHOfXu0Ig`8Ydmn94>aRdDe>6*ZAeJwAFyY z?#c|#qw>p%Bx&^bd(_;=8o|PPC!ZW6Sr=G&uhH9EveO(Z6f+kGxqoV%c!W27z(M4Z zEdKv{@>gPrsuU?w|9KlU7MHsDZA~w~X~c6oIx5-7Z@=qL{9T+vRzfEu zhL-9-r6>?(DP$sPsrb_*KfjiZ95y((h8U@g9{jp*HUSyz30L3mZO6>p4<0{QT`Jnz z&R_CX0Zy=?-wIs@e2eca8_9oo=GxdF!5yb)CD@c`q*yFFJ=YaFEv^?jl?^e8gyNWq z*ivrgvL${cmRdQ{m|(e+nWNhSR4*rfI(Sa#y+KFMcO&^;>r_p5pz|Y#226msc}oTK`K>4PY0J$9^JD&u-RKk5Ju$Rx+8dWbE;@ej_MP#V{Nkz9AS?E9c$(@g?FK6A&hPPoR#lFmbED{RsVKmrT=LU{=m62R3 z6RMgpb$OtxGPh@^XUwzp0mT1WPEbK;7b~K3qQ$Dr<2^d=Kb(z~6THzJf2orBR4wUC zos9p77%^;b7VG=H_mB;3>zVnPIc-5L0o!-E2d0#{UfGZDZWC_$qf4Xx^zuA%@A=N} z&zw1R_!Db)uNeLH22s_oEU5C^J!a`cDA|x=;q6{@&ZR-@C2qaw=KkWqh6 zst5O-Ff8^;hM!|&2n5Z^>eNiZb)zzUCTQ}z6xIX#%L&C_Z}bXlKihe(w!F(0w_gGa|2zqqWL_TKxZ+q=CxOXWk7pEaM)eQH`* zZ0*63L`Ot+%Gm`OaJSmfH5MCSI^3;kX}!U2lrck~$(VJi#41-ez>5?UvroJbDbWim z5}@0aihxYB$wA_=)dui_=Z6L&1&b~ftx3R#wAF06kH7SVxh&C7OO~bLb=;;+<|0H~ zMo^SPudcq1oTQxK9FXWW=a=f~FqG@p@0=#f&lxwXy%%@<;?_ZV z-ut9|L=@N4aK{IqGJ>;PbX1V)k$m4I(Q8NY>0LxA4Y~2A3H~R-GhuHQ?B7FL8Tk%{oy#cZep%2C)1X!PnNCW{)*ICyhZ>_rt4^o! zdZOjjc&eU+GS@RVCq11M0l*Qg!k;V)+`HdX+81s_@5KwBr|LjwX(>PNOFc?&G{*d!RPPhAu^O24qafTh=LU-HmdMFlc^*0T z7iGA=Fsh&NHa4KTSBO3T#hOSwlPCTmz3!jp1<(R0KHD3A_2!?k8UZz9{b#hEN6Q1- z2Yhw31xVb#+%PO$q*Y>a{FspapiHjZ=TGxl6{7JYf&sAg?!mSX_2%cJL!bZt_s6oD z@LQx_{BSa4S%Ppu-!+yI?4NE}4($NhnBeAARhF5S@wVBasm?q7Ir+V$>xGlql7PO81>ZSZ$T(pnR0IGxd}E40%r z4Hg}%!`d119jAuut&R7eS&2VTO!Vpd-wE>Uwp=7g<_$L(RUzq~9=<#wu@&fXbIbKI z+{$qla2m<-o_TFWb6>T;65F2yiM%_faaUyCM?N4;zj^nluLH0b1Y((>C|51*^5?9y zfQN}97h;%1$H12wnT|mXAE{!38dUFlz|EzcUJbH5I{`jr}Eh4qEo z{giKv6q(oE1W&seG!y5%Y1Z4EI?%OK<}n&ja|vGYqD0cBr*w;+5D5tSA-GhYK(0An zk5QLOh33gk_~Ovs=L7w{ERGt3&AKye87 z-E52IJ%zO4!Dl4BOW+YdfALV`a^*9t z(^THL=1XrSAINK@Wbo}oX6&|Z&y0I$WCE`QW0f`<+pS5yD<+E{IIjJgkAD~o0eVYv zCK#5kG9U$gkcWARnsB zcv4$y^n8mu6&%{{Xae3thpxQi95Q0A8?$%!2ZCOM<+S@L^K(m~MKOlN!V3WJ=&zS{ zXEfzi_PodKf)`|eR@8Jcd+|T*p7sAQOWGYUG*1%HfM_#UGwuSE{Cf@_mn0+j%8Avt z`_blIf);&~y&MTDtQOKjrfyv`aEZvLYgS_APuHAeG|BscThXx3y&Pqj5jqcDv)0Cr zzwm5@LwlmZ6F>uc&|Os)K5GK<>|LjFlVwTRbZF+U*;uA^(X?KY_&JlAaL#a3D#NK0IQzzC~rK?4&C6pYA*WvvE7HvoZo%7X~e*~3zp3Jk7N?A5sSh}-# zQ75sfvJ335l#&bXQ`ccBEbi;mt_Wx?4wxl_`}L?)=rovvYO!3i+MrkpaMa(dM183K z#q-BHYvudVnxe&VlAoUSHP**&$qxfV0j+kyVGg9!;o7uGJLI5K)tW z{tQ5r?hgyK|7#rH{~3n>pZ``sVSmQ!+4yU%!SR;24N3j&or9K-y(ybL?kjO3k^R6k zHK@%)2xT7VFi$x(diR$6_)Eao1x%UXBTH+adZgd02GVYcoc>aY2F?-B;)u%FAL+jUnAh)AfEW&`}BGO5EmqF_yxi8RKwYNnE`!M!%yI<)~lgLz@M zr(vBvXx{e==E)N;Y)&F`-?cB+kGwyigOI_7aGk*rT@i?dbSSdM2EdhObEgD) z@3xR{Dz-OUvRyA6=YsR=Tez?z>qV4M56sq|9262z?6V$e_^Z*M=$;EZ`;bb(Aw5L| zuFW^#lw)t|Z8y>TZrSNOz?gqXYTr7G=Q}u{`E=pIlz@QiKJ=4xoO=B1>;I?T?9>x% zNDAp$k|90_;>9Jer`K&zs|3}mq@&5!wJ5&jSl`i4R$!RE6kKUNdlp#}{%rXMZK)(_ z_iK$+DH0r+Q?dE5B<@squ!I1zYQBOtjtN(Owz6Gkso6s3*L-WdPn-Isu_esuQPu8;Qf zi@xWg%xg-+p{wVzojz8_Y)-_27d9QTB2JYLnIV2m>HI!l?#Av4P-Ex?Z_$Q{j*|Q= znb1pf@Y!=T97AQqqXi>sDaK^3ujCk9b|wY-l7rPyo;C8-i|szga_Rqp{YQAS|6Vzp z34ROm9+`h*_9o|VG{5(C9PMZk!A7oaSeFonCWVX%yLvaIQf**$e|>gso0&s3Li^cVzL?j0ICaGrCVjfY#WQ7aBTsp+`FV_hFu z$CbH`RYI<-dfAB3VYS#Mz#-ip!yk`Ez;<0O6s3phgejM8JO}{?+NC@34$;X#|EN9R zDRLC|`#E`fpJ-+8Z_UwpqVU@V?!RDBX=n@NE8jt!m#Jz#O~lq`#!dpAhIu|=D{ zg&3Bw#Pt?CP7H=UiNrjyC`v&w1QO|Ef0~XPvF@$%>KJh*oGswK0Z47aPo3$R+VKwMcIR-cAQ^f=~ z7-Sp-KbS;zrR|13I*m!C_B`xln@^D)(7|sgFFMm493M@jHFx*k*JhbB|9`py@CD+H z#Wih+zMC23<4OAT2|^^FyZdb1)F67z>7QExH7ScP{&KSC$2(%BpmARztSA#c&Ni)2 ze~d2+e87*i^US17u=6R|7FDVfb35WyfgC$`_{r8UUJ}{Syuv`XqZa~y8yIrou93@6 ztw9`U*bX3oi!InmEIK_k%a2>!w9Ql58Y#z zO|it=XQwRemsaE_z4^Vj@F*TU1F_~?9k7RpaF~u#&JUBDa2!xixs z_w$wS7W<&VyQ5Tcls^jg2^WBCn(|5FuHKVD?%CG=>`ah;Ps@9x^tuPs5l|HQpIb+2 z27trY!&a=$n$o2~mmPl5MQIz|41gK0Gdb4{wV?G`lQ!Lkqaz(+K~?p{G!$kmEH zw#JU&Yt6~p*P0d!U%+YEe|jc^tnO<~06p+-l_i_ZO9&&*HrsUzQt@iB4gZ6A%F_yW z%fP18<@o@BArSbO<@}fS2KO0JS>zZF4YMj4*RKvvw#PF0-4t#a&gOzuEI=zG6Eoc((q0={k}B1k@p><>~9 zOb=;(!ZrQPXCrapA3<^-G-bzK;R6FTsy!m;9V&2~dZin{y1{eNH9P5By77a49a&Yu zJK4@haA`UZBs%{Bmu?@~n(FdNB$*-O&-D`O0+r+6!jSHO=R%3d&ie2EYWb}VvwKNi z$rCHLRc%)K)nH*GT&4^I^v17Pm)G;DBs|L&rx~T08_m&{o_?%|LQ@%smv7w3X`jvZ zceb~>4{sx+^xrCD0{pUMX&=)T5Z@$!08xtNx%lnPVn(POQx}vipwb=s21~)KWVmmg z1y_4_i98o?yvF>e_Aa9!n)XGgWF|Fb&Tq3+o$1E_&D%RRdP3NfPyDjd!0G4LTJqY57%8aE`U4+9xM)IN%JEy727a&=zSzqy}#4!A@iW{0vyC)O~@S0972 zwGt{v|mKiq&9B zMHtdxPSv|70TTZ~1_5(W+^(+lhBeHnHSrb%zUHyt*Xf z5JYjog7u&__$$mxA$Ep)nQ)ym2M|Ww?#J-=d{+mj=%yVbnlI-5a@(a}O|f;2ZX_oE zAxM7D(Nu}0_GX2Z-80Su2-rdV1`I;OXY2Fp&7SHGGeo#FEddXZqnr;*-XWC1{?Jtf z#y+AyS{eXj6R@DOPBw3`osgQ#Ys8vI?@oqzY|?)}HZIrn6=GsF{QI#z5s&?weo%L* znHfpK(^e8__f6^=z$WbjsciALjGuU~nn%U!YgMKa=h5D7WH@;}y(?(cH=gE!0~6>q z8R0GCokHtOKltiD7F!%gO*)?$)ad;>7j>9;)ipFcPu2HEGRMc{YjBo2Kc{$L39LOQ zaP$qMo(7b??OV?HXpz4v69ZU1pRpyttc5WjM3B5vB&p1>mY(7b z+&$M^P()Ybv&7!zT$sX6!9%s;!bzRTg`-FLweAmfiOh9Y3jZ+)^&h&|?n)DE5;s5fN?kc7UMB|J#la>*>!5B!+SDY32Fg&*GwV8pZ? zL!UP2CcBu5Dn?A?lw63AGJpob0||Y3mwQ6=X(2RIC-i3S0@l#ERx=76#- zdt#KA$1OpmNL7`5!jorm%P90V9mIZwmrVZT4YnOcNg4~K&ux!VA0Bq-VxC=0k?l^y zESAq~UKB!W>`r5>=)8f)W8Lx^@Oc~0`A*?H82Em#ZQyBO0PqqNZ>PewF#P?YrxRluf0> zUNtL|RkJCXvdg;4^X*J8(qolIK4cNy8Wk;i1Z=>Q9Pv4vii1;W6|d!c>Aq%htIopI zMKyB2LP{lQ)nVOmE)aiIRhB=eBFoGeo!D!?QdYgGQThDB4|AQ8l?`$|{U3Rne++l) ziqr(nFKP%^B{3|han{9AjcEy%rO|zy=ZnnX)gI?cE)Z0QQGcsv(1gPS?MB-Dt7v-U zvZ=?2fkvX}@_9e+Um5+%(bA5MwDLgt*@&y^9G_rl3oR5^z{<-4$eVl%l2B?Y{-}nV z6^Nw=_Wki*t}b8w`J~uaY`&bBZ>f>2@081`=(paO*j3_u;67UGl_GH!oVh0HsI8y7 zv2ISMtiSmo@k48N@BK^LzG;dsv{aR2p*nY_Qp;zYf>m)}MQz%ICSDm2TIMhxxsiNE zN_^amX^5w^g5HLDu!P_F+3EqoPN|M42r>t02LMDi=Z`YC5_L2wni zY7r_a$x4rdqH8vlNlF%$Sc?@eQO|I@Idr&NP~_{MlVdy}bUHzgj``(_XJ~c=Y^SuS!hnr7UdD7^Zh_#J2AekSA0kJM(k6R6fXSe@l^m`T_(CNDxdvUFQu79wWi2jG)z&9DNu!|E&&7!dEGnMF=a&< z4e1**ec?O9qTwB`!YeZOybOq5B=Xdaq}?gix{-{T+iZkCc{=c? zU<8moeUD16i3K__ZqJQ*od@jc(=`IS6-R%j;REEy`k>mBP2FK_cP4pR9?>~=$sDm< ziR_rHIG^0Ks&)aJ@k#C)!ivtm+N5|WI>X{nF_A~pr{xOH`diabnhL_rh99$PtODm* zDpRTBK(_TaGSSKK(;zr4wX{CGf870MOM&|9CUQyh2kYi=`kMPTz>b)CL8i34BK6Id z(qY0Shjc6L3jY1JFy3o^Od#7ne@Qg=xk25R=!l+O3#ru}uSCvzfJlE_36Y23DJP36 zuXB`E!sm;``6S2z-|(FH_+tM^-$$wwmtL6qXh8Y3ww$?jV`|0e%NLtFN{`!ntjm2a zVE<&HI_zolYm+8*#TtyixVXT9!ENxp5yjJg+0C&d~Z?AtLDGv9ear$M< zh^|v)=cqwJu2`$S>HOLY(fCO?Ez##vFrwu|1};Fm2jw+lZRu$PK)Y=h#CrQQeAx}4@zE> zC1(QiP%K3kW`x4}7fDL@qVxhv^n7SMnM-hXIiV`o*MbIe9Llm&lw1{g;E7h|w;Hdx zK=HoI@uXV4LC;Dqb~)!3&XoN_J;;41`7b!1dogeRwY_LT>$O zh8!>Qz}>v3C2l*d2FK8iMjQFk?Zk)}y+Bsu}E?TToP~@==qdIw&TF}#91bk8* zBpqvgKV$4>ZxFQ`y}P`B$gF;m93rwthl-dCQ@rnALZi6Xnov+fL3_Jqj9L9|;M+`z z#Jj@pU)k`Z-iYrRZ^mV{%znlXFBq6&jWZ$IFp106c-=9=lw|!}vW0$2?fVBDv)59j zm!qci?@7+bw`jj5IC7e1GkX}nbiMknn=I4WU7!r`Pnf+eZs?v|7;>^+-xfC3KdDm{ z)%Z?pM#U8pr{GPj9EtuQfmP?G$(vVw>RvUPhUdNzes@ai&!e;iVnNWI;;`!Q_~ zA3(hM=%zn251IQrIjKopu&8ciF@}ZrUi$bk*u?16dg}>UehxX`3xgxKp|i`rgj)}= z(2lF-B2vLxM%H-)DFg5Hpxf#Wyrc1AYS1y61#&P5f*d5X@J^RBPIm0?i0oq5;_tfR zlB4$a?g*YYd;(S2ZJb?}ZgvpA>JzxaCVs3_a6eORTWL`e|=C8ebs1VN;w zYla-UYiJOV4(Sf*?q=xjhM|V;?v8KpzMuDbzdwHOV$E92^~bF1+~=|D*!$dP+|0Nr zX-=TxRwuE5?)$o<_-_)N3UTqyCW6vY;b2`-U}l-JJkwX7?_RH-TfQbw93Ii2jLP0H zG{mQh?Vd`>Gs9__iOA62^ivg2&y))O8B3f0TBz_>9K*CcL&&_DA_6Lp|IC>lkt4mg z%iCoB?VAQ-E{(PU_h@*yXm;N%QE#)3srz$Km8aW~)e5bQkPE5F7Mqejxe-H2IB#b; zn}l)7bK%uc3S>A=#H_YBhDzb{Cc7c}XC)L<29Gj|&q|fc*Te!B78Vx**~`w>J9&*5 zh7Jsl51=w9BVvyR#+1?x4l&c!JrVOQ#-3C}c#iw;A4}bTYv^!y6ro&GaXeK5**m7s*3L$ z##i=lh(C{yDYo3u;QQD{AzdVr9_f>&Qm)F6SBASns-1^SG9m9BwOE16MXBtYA}@Ih zq>245`U`dT&0??aJ59>WIJU7d;VsI25G z6JtMNYbOoF@@(%Mjc&5gL&zK7>z(&^lxWxy;iM^(LeEoQdT)VQ(czS?um zRBVo$i&~ntEogTJ3pOX{?_iTBq1U?FJAoI`NpDHIp6AQNNGuzolhTXb3}TVOJ3(%d z8o8n|;=NLg&G#_*EGzxw+2vo61WlIUcf3&=zf9$;!MQUQ1X3iD>N2nI4%zyQ zm0zcAC^V@f;Yl#LW1GmZzcy*Q5>KmC8i^`N+bF;G8RH$6zJ8}$b_9nh{zvbqP4IzW z-QDw0Rfe|(9!Jyp5VQRyDqCCAC1XA66@2_$kP)>-bLr*8GP%*=LxoRtoB1AsB7d;J z#k_QE4ZUkjc6K;C!q{Jd5c-9VouN#>oB+&mJ^EO$_1@P&*{oOn`$-P?2temX#ssWd z$=?FUkB!>NXwcA(<=eMbWjLXe$IKV5$@270_BbgceQ&fgq9)t6EepcNJN6B85*AwD zfkfGVP~jIGt{c6l0e&A}Y>7-?-v1EmryAgo&N}{mTPOIlP$39c6dIeUk1b@uk>bn8 zZByaI(~rB>Z$hn%8t>~+9_G-cN4Ia-3cnfBxJAJ`${nn$-=dH^xOVa*G5qB7Up4WW z#z=a>p&%PgZ?L}wq6z=55cIhf$Dk=|_f~4IRsG|MnNIdeQPrrg=TvtxfbSxS0~0C( zu-4-6IG{VA@?Bt>NT$cdotF+u1uT^BEf>_?y3Wlgu!WM0i7ypd8bTmw3Fs%(|L=$m zLVK7Kq-a*Zvmch-HM6%dHBfb-+;A|jA_Y6li4Djy`jw!y5RvQV(RqrbBy7Idh%uox zJ@+dgpAQ9xqZ)px3y+o8PpnF?Jyx~U7;V_cmA5V;v{9^L8feE@rBQ%|o;$Rp@^dkU z_5Ek`J}GTD?y`M&CH*P{xYW8Xxe_wOJVZ0NU^{0hl$W8@uPzf zP2M-Wfn?<^`y1%cybI>Haq=A{VGNkGUc{%>)6r%KYB zX2yD0tnF`p6W@tg?rL@C5Ius*bT7^^wXt7*J*@DtaS~G23`LRI#tI@mjQ0&H z3wqm#+c7~XJ^TrE5$rIaWufe7kD_}6Vxi|wPRfn-6j1>7HQ;<=Pz9XAYt`1z+#|ur zBpaGWFnxAYV1R2zk2+Os}b!@ z!h@)6jpj*Nb4_`CP!W*ZcG&o{BluA~1UD^T_6&)o_&`^`Gs4l!PRGaJFGx|Y?$dLu zu9H{t_>xp6F12sJ^{+EuWE#c!Ec+Xq_(#8QKr%rCCGm^WzNVXe`K*Hd=xQcBQJO9Q z#8KDq9!XmxEWCdw?d{@ znV&x$j^r3xwH^EhS9o2B)~H{9UnHv#Y1k)!dhV^W3*qc^!LJmi9(jogct_#H-9jHr z+2weA{btjP0P3ru(@FipBk;7HQsrHHrIbFrlJoy|AXGAHf|_GuoQGNB9ozieXz3-c zmu_*@*YHL`z@T?jNX6-zE{5yn%&l2{{Y8wQ<0w71F693jf~PiW%VU6y%9Unk_%Bq${FvM5cx6z7rb&qkj zh0|!OFKDy`rA#+yV+Q2DkJe`4A3ur88)6gQ?tCXNm zBOEv7*&X7>i-K$d@of+ z+&wN*vunH2v80zcBX|tz0{aMWRA^JO@pYFs4BnJ)R6EjyUg^AB|6q#ZQ3N_36Whsa z0bK}aw|%QM{wdvHQS3@gDlMp$>{@KW=fr|U?{nohG4qOkvK4;On{{P+4hYSQq19ZS z{K%9F_XF{D%qXBJb&9krK)1k0^osghcAM| z=|c3qq1f`u!9ivKIn1K0hTVD*uz67b(6!eEuI4RJ8i&qL1+fOsy0`LJrIu9WB+dmO zJXCLj>+9h|tJ!5o#8nzg-C9opPVHNa8{~f68jkOD&)02988tjI)L+Ma$=1I0iM$=3 zn4Rv;IY3iMS8y=C8O?(X0*p;^F4TX~yZs`swwc#@qg`J{Za#oV_hp7?q|=+%uYx1={_sDqdJ9(gss1kDE8!Hxp8(sQBm}@kv}s zrH{9~8 zQs-*VcE4{{cXC|+oTEh%en>F%v_(;pR2=^lS`Pm4%014PTmrAC&ioCFq8z@lN!E(U zd?nVY<}BjKx#xA05BgVna7yH0>IvT7&W(c-qGvGa|MbE#_N=}5Pt2cy&Qe#a4WJzw zDTp)Q^|039$BK*SpE_7+ojI|WKp0-7RN=gyK0Iajmm5fdednGYh-Pxpa|;$qm|dl$ z#6E9GrjPO-U$o^Mbi>n+qK3Axe(4;g1?G~P?mn7JetdWygPr$E~mY7f%D+wn)UX!7VSg|NSr{+{h=P;qVpML9YL3KXy zI!Fe8(yqheiT@q*Az@rkZ?QP8S48j~-4ZB%P7Aww>n$X4Kh0Y}kYaOk&;tMm-3XWvt4SI z^FUrT4xn45V*aOrfe+uvI|pI#(vWfgQc*01?$qKl{t-zMNrE9a${+2M1M~Kl`Y3cB zMKWG~{rl|A582`X+qF`e7ary)yN%HU@G{a1c3UlRJO-!2q`IAh8zN+84JI|@L+<@0 zx2>%mT<^Hgtfj}MI%|@vyMQM-vm!P6#`iAp@LoVl#lzO_+oWfU!NDM!@ zdgG#<@4SE=&@DMOQEXXTCZJUvA7u06&o}0|4+g0vzug)RRw*(0v~sMfYaL6g7S$mK z8;NS+8s_(S6*+F72pY?;p->z|7LLE25ZF&xeYtjt@Kjc$`x|n9ap!#1y<%VRD-NPK z-ddfVlBewu^CLQzTu+w3HlPEhwCPY=pXg_jIpD#3@5VV^P`8~QTTQ}kMpziBjbD9< zn_{){B}^$;s8T1}#^*a0xnYo$r*ee;Q4i=TzQ)~2JyxZaOU#%YCrqLZ3#WMon;A+e zdf(ipLC(9ffA`K&imZosr8oH-ivn?~nLu6q`^FRf>zzF}oAoykX8S~6YEzc8cVg*a z_(&%o)z|Q}yGNkdYFx!O4|${cbFhH(C!x*r_h@JV5h2id;ObdlN46_vYV};Zzsy|n zHGXn6ox+#ur@K(wm-x#!Yts{|%z%Yl(*9wIGy!OkH*E`FVqeAm$%EPRxsZcE^zzctx3C#q`c4 zDRP^8aKmI_Rj6Nb4vHZGGBDylv^YWIwhk~y>GWS-=^|u8n_C~-x2~CTku?V~E5BXZ zzp?&^Z(^*irP5FgCex+#T>wdsTV)?8+G}S4oLoqo>Rr{um~1L~H=xJi1HR`tPKFK1 zT3F=IAL)2k$wM@_f98;d=%b;5e&+BcbE`M&|Br=C5;jAY3C}TzX;l|zK}urqj>030 z?;G`HMo!zHezk<^A{*~x-e5qux9w_dTEA0J2a_r!KGibHjPt9n_e)#=Dv^zL4ot5? zEDs9&`l|sCT;~bTuR>l|HSO* zzcr>g!<80vHmR(++bbo#W`?7&2%+E?Mur%;Ba#<0u9u8LDS2M%|6ewfQ2S5W%pVp= zDcTR@%SFD~`hHv606($b5R_D3uGKk9-~o`L<9MDPC!+_dFLR! zeQN?(@tO4YyI#(!y13V5BZe#iaAt3%?7{bYr{S#dcE83e($Nr<#B~$IhWGktBW^%9 zJ4FOWlJxb?2>)o;y>;8f+I#cp^7{BTY4%7LLb8x5oLl2`%-%tRH!lV_W@U{G8+7cV z!|a!u_k&4+!{5i0KI#&GA15_pP>M=RPkw3EHS0NB82h}6kn4$cxCtM8=~(b#Uu4EL zW=PYQHjO^ZXIO;RpiyTp?U6#UYA;Ra>><(+OufaYHs2vVKCD#`R&kt=oZ4)jq*?6B zfA|bfwtauq+Es{2r_EO1U;YhJrxjL=}@*eUFb^%PxZW|%jt#tI_y@(9N>ry0Z|2Dt2lH> z{Zpik?=#z17TV6IPxgml=<1%M(Ack|2cd{YPKAtS9 z+g~EqJvO3w!wHe&w@lKA2wjONk9gRwC8<#x7G7dnOb8j?)l znu3F6iBQe~vf4)l2bG0YGbc`RNtyw()Dq{Ny>Da(qhqhpQ4ZN_u6G2a`XeEwu!S6v z7ceNLdU04rjKN!ToIgZ@-w6vWUwf+tWU&G#Y5j}%6=wccKi@GY7C=N$>t@1?2~`C@ zCLh-y>+9lwoW{k_aMSZHS_?m)?-Oc4kfqVb>rq8UkVT!KFfpb2y=bt6t6LGC7w;Ej zT(tRJRZU*dqMp}`Ijodt(+K5vc)s&n$2 zlc2p`2|KmXt}7vrd!&(Ds7ir-(9WT-76A?kjYE%w2Oow zc$>V52c!Z$$9&GAtr9CAqsx(zgaTE^2b7JwiLQ(z8 zeCeDy()%Q#ab|5h93kh7O*CC9gx(uJa%4mnlF^Ll7l)nl=R9Yn-1>p}#~_enFGo%i zy?ReusrO~2#riXF!Rv5ZvZekG3JEk&c@(ytF#VnRF``Q<23|FI_sfJdWC&SZP<3Ib zYau(L>~>(Ae>>mOpbSj!VuWXBa0wrqS_ymXK$+a8<^JDjI1_H*4E-g{N{ z1#FL`HhCf$wI_J3&Hc4R6A-6H*DZ<@??oL{12{0?sp9;al_*}CkmhhU0drNe+p$Q} z%)5dqxr3F3`bo;c4e`;%&d$H}OC^w9GFrK7!(HOwkh4*g;p9sFuaTe6BR%sR~==LWYasb35ml;y*t z#1T1=+LafLO7ZxV`E&A~%W^x8pZO)ekR|-4XTD(>G>h@q|9a%{I>0PG4m#6$VI>sZ= zC*=M1^B}?7QI8VqOh;p;uhr?zMaEYiISU^iRh~k=o5(M=A;=o7dXubd1c<_~{sIp% z!IW(pjilho?!C6D_DPzHXS%fyR2;oP=k*dgz%{#$k4d|P1?%?m7PJo>{n{s(P}pn) zZHs{If%6;7FTnQVc!REUVk(UAGpm_p1}u8A0V1_|N?~$Q-ZiPP8hEOWqT08JU!~Z< zXp+arJs~eW-y*-2$HIK3$T|Moy-%*x0fAdy^bHjs<}`&QKf4l;J@daAWBxn2zEjjRo*q*!LRpw z2gN2U1_$yz%$S08f}$BIv3V}NdKI6Ux;0Eq5wqQ+1{cBMZ068$!sKcfeO4B8PEqfl z&>)<=4=W^$=%vhoeUY~k(YcMM?`7Njow}2wSOukmCOHGNEqQI;&h(xyHhdiz`Ut5v zXkrhjt>12E%PL%z5tZk|Z>a%=+et5A(QQLQP*6K4jm!MWMw&dOmmXWbH?yr^0GgyuMXwmUoR@ zl+uq{;{Zerh=EP!@jc$8mzy2yqE|8iGUQwfm`4WNxFz+CaUW(m?_8jZz4D8PsR8M) zExOJwwdZ1`T7#}3!>Yd|2;7rT%p6sq9u+$u9%KKSyVEbWY1(uSX7DbIu<*Z4W=?S9Ceu+(Svg6HbW%f(%mSIw!=IZxsRd?KIP>!3KnVa3t-p!LK1`LOqxES3HWBT$S*~?TGY~n zzLeSS_KHHk#o1r>x_^mw;rk^3ibscDwq(>vrAA9AuHIst!Mh^HgBh3iTL#YcXZp#J zQw{|CD@dMJm@b9)pMrDWz-bvUdO6urGCzZGaQcKoba_YL^emxEwWctva;Z*kqRcyB zHoDEu4L#MoUZO>7@du;Cy=cDCDIRBy>b<;mpAdY^$ON3-Ow9I$XD8>`(d25U)2*0L zpL_u+m(ur_0%FTc%-d~L2S03+5pQHKm1l4LW2asw!KyYcJRUE8K!~*!$Uk33Hx4&O zpQYMi3lApNW}#z|^vfqa23@Ln;g#Kc?36o-)9RL%(pFQbj)H#X)I7 z<$V7eN%(c+n22}T1reQgZqaQ4v)!-Wi846KjQ@5HDf24=7D{D*;h+4}N7gYD`4g-B zg>nQuqsVH7JFcWIF+=B&s?cHCXJJzu#N!t>CV92hs~%l zX%y?_wKGp+;&U*_n$y0tDK0hrjmg1*$-AbVVi8nZ6)f`UNjwYZv)APXK~<|b{N)Ax zu>s{oRl3Jklh1* zcW$Mgm-%zx=A%^_Y`>@LeU~~OnEBuG(dpU~gFZfCB~np}iy3C$`S!;vaA`~szC=zQ zrHNm>6L)>R21+^Dk^7Z=VC+tJx&=1Fq7|`hZGE2V$u4dg%YKJ@+TnUZ0l_QYrQ|D{nL@@I{3ci*?DTn6<|;n+&<7|c%;KY&0*QRp@0iOc7PbTn`mS~sAae7V}7VYc~h7#=T6Ob?w zW){BbQ4VH4f*BrW5H+tc%eF_A^b|9C%pp*R0fB7y=6l*I(0EuxXCns9yMy9{UbFo_ zoGE#86l8Klcjsa;Huho+c)FhIw4w-QQEIAXnXVSW&$%Xh(@7IEjv7-f_GGzVTx5T+ z?_j-HLUps7rZ8=g85j3w*~w6l#ws^4R2af2HC|TiIj*21h^9x*hDyjw$I@cwX>Y&( zvty9|=2Li&e8g~Lm2v}a?e?moXF^rY&qOqk!073eY380Fp~Vfz8jIXxKOX{iPur+F zIm|g_gHP<0o`)y7J51w?%_%qqSUjuT8ZF&E`HjRaF0JkQLn#rRAr;;X(8WIw5!uzl z4ks|eDR}sq+g#ZE#%zX5qkMYibeW~y=yRe_>5VVP_o^>qchQ3-o1f3T@*rK|TN5yS zET?x1vcu6>>@}wv_OXyWzvJE&tMPkkc6rBLNIV+obqVT(Yf=Iekv@f{vuTm5MGSV_ z`P?Aem(HAw>|}wh3=@fI-x;auc37Ju=1kLo`}W?_&5C{#Hh`f`zJeSNDYa zy5Yl8pQWmCcu1qUXdny^+CSBkq?>4eNm2bh8X+(OSS@%mYlzREXp=*PLH4G4-;JB* z{zrtGTb2($;6qw&xc(W^f(jqfLez~Zo-KtZOj`$^#S%e*3jc^lNQrM`Mo{EY*s@TI z)BZEO1t17GKQ&tiYrH7u8HRWqst-RlU`_T)FSbLG znSXBdCJIacHa{&px3aSZ+tggJzS43~<`?IdX8tN*x@;al?{rClZXo!YF6CIYR;pC@ z1pOgDprmf6YqEkf#I1WmW|4h|+};w)ocb&4Tq0`8Rf;qN9u4-Rt&AtANipRNcI|*a zuNlV>OPQKyrPq6&wJniH3ylE+-StHyoYT9qln7L`RB>|LWH$HeS$Fl$($6z6WGr~f zxZ6GrP)x8n_8%8{;y;ghP;TN_0Bc^Pyb=RDay_{55rR-B;-ZG9nqh>jhtE+m z*EmkR_BdcL+*b}cha+X-bp}C!3mA4?Wj%YWPM|_+HhgtUSA_nNJWiCj}3?ljPb_{A{e~3J-Bze+*kx! z87`Y&7j|;S&Z{~Dxy@(TLprK9&oe^WYe;I&XPl;e5~>Z)EU?5%lsOXh&H)=A4VFs~ zX>;5kU?^^Fp!RLdoD@{W6GFc@B)4KF4x$oe-=R?mEC~p3AgMl|ah~S_2s-aOg>>jc zqsC)2wQTf`rE^D+95xn{9V`#-xjh020`P)j{G-rfQKMzuIIcT0V_i$Anl_4wA7d&! z8w09*vZBlM23+bbW=UxCe`oLzE`*rF$6OSzcyLrnU5yS1qZXPR4)#Y5Mb}k)5aZA| zm`bO=P7Jc7@BJaABB9V!O)tu>w%-@nPlv;f&Bjz53Jn|=XujC1SB1T89B#ht`sDq+ z9Vf9pq-+P11*?*)dT_CCc(!|L?qCZni=s>UfCk@L9>*D%x$D_04J1`$_4kW`N|wJy z0|u!%MF~ZAySI|nJisvSWUhU5Q^w+^N?JK`@%z5Og8mW)NhNWW`lhq42q_=>YqPpJ z^h5euUP0M+1+!$w^5KRGTE~s?BLh>tBVQ$9ygTG>1PZs)4tvt8J9Rs78I&a436~1j znsw=wt;}Fipl`q;dwtN927bsW$f`R`am~*T_ri+&(ju$$0rwry$Wchlvr*AZA=h`E z-UB`Z?809*ZkT%jYv}*SL!WWJ{q4-y`KtHIniqcHx_T~WRYVJ(x(zRrK>2#%jdYE2 z?$bkw^>$vJ4f60Y!G{Di%NP|tcANz(j#BpaMC~&aM=ZPXDrQcb37h7mGduNwPYnnM z!<`GyxXWt@7S!!*t+QWPpKGZ{{~;`~*BQL7+O)eVxqq2qwfbXx*plgVRpT)r+QSYK z+xXl3F`85t-{hUedmUV3N1O3HI2cp^I2li_M~mRDEmfW?P(|5BE zb@#L_5G+Fq4o|0|Hofp_YYh04?w?)8n1uqWU1Nl^b$#XQC3XVo)Q{`S5@iy6RIhm* zc}C?R@273A_#?e*l08CP4s`?@^Rckyf+S85a-wB8I%9dBYf^ z9Qy5mftus-)j3zvYoN%xjvP6z9zYL(91T=V>Ny}<{C_?iR7gE;OgQpw4Crlz^>$7! zm`Wb`ju$ZIWimE4U1b9jqKa- zT@6|g95BWMauo-FtfWG+__ncMD9`j;fg`)`ZbR!r(ro`gcDg5z8j=)0UV(TN8TaZ+ zXOSF)e&a7X0JRdblky|dXPez`!J39ssSBS=y^b4IerBbf3o5iXFKl~}FjVihnbds2 zrmc~>>Rf~3>~=R2&515bVk+@&@b6V|2^@yjnmQHeq_XdCpHY`~M|}Dv0kjfhbU7Z( zf-1?Y0B5b7B}*G)(b<8Vk>082$3@FsIUKKU%xK=F2YvUxVnaiTM~^qn^c*+`hSl}p z{T~k{HrKL8psPD-8gF@wA5#OgtVOxadokr%j){W>BLljXA3SE(-z|vnP0@UC-rcU0 z@Py(lhFTh!8s2<`(;mY&|XI>)$N&dRF=7{a?q~O;pmBUY4R#m^NPHWJ9 zy^6I4W&II2R-p7qKPOMiFu}ia`pq^qgi{Z60if7Hl(hivc(UEA2N8XKCY|%__r0VZ z=MDrhA>A;z7vHND2(~aZF>v91IPZ|~%o#_9pzn@_R)X$I9O_Gxp}%e6ubR2Bq5`;1 z2+GN@zpy$hwR`-pz2e9}e zK8Ww*|MgJD9D`G!X%gBs-U>55ba!E@2-E8AXeK?ah5~^&9(m zx*o+6FF-Iwj`2*_hW#)RReH~S0C;xah_p*O);YrTlk?eHP^-o?4WzbaDGE5RL7TTD zK5Kn5N~II?eC{_-9YomAG-uNg!RCu!f$uQa*ykJ0J*#u-df}yh)CNyW?!tzBzPWDK zeXzNp386N2Hfa%YP72~=AOxKj-+>^hDX0$J8E6%QtU~;xv^S7~pr(+zfcwE;CJO|S z{XQhapRl{2Y^)R@4 z1J&|MQ(Cm{%KcxH$??~Y5`v1;gf_^;{29qGSH?dPffzx>;TSB?kq@@v*4N%(bthaT{S*`yx-3$sDJ0Nui`Ki301 z=GVOrT8jQ^l}Bo@0kaIh74$&a#SHEA2~mymK5!V9=J!uPU(_75u=x?|J>FGxaB1V{E@ z5RdLRbROd)AvseiT26$#ZUj#PF!ZSh(hJjbT5Jwi0 zMUVSr(A|+kJu=v=d2+jL$g{P+N|jjemTAw0c=`bSRvBe&g$^;G&avy2F5|=RX8*b? zO3qS1gIq@}z!mH3=Cp&+H-b0+Sgb(gCo=}}9$=?{hcPez31dY6#x~_b8}G0|j5fcv z$i_Yhgm|?Y+Oo4)cEjZ;5}p$9`1$4#v{H8PWzppEvL4V57gM&){dxB(P07_x?Trii z4dUn3mpn+N7a!H6&hQ1o(HoF*_H-AwJ@?={?gO# z#By?{ ztF7@@c2K&eO@)UWgv1H=6bg4)dND)|%hjYSG0=k_Mrqv%U;j)PI_uxX$woy8U+HhJm5zX};S>qPocrJKF zW7X+y;l7M+pCN=RWX|5of;!lIGRxkOGkG4{Y!$LQB9K44;5^&_gHrwREU+N6t4%?_ ze;_V?U_;*)6{_iH>*4$3Ik;goXXxOTIa{+E7IGM~Bux!X2HK-uXv9I5&R`D*UKMQG z%Fs1}AI+twWc5LTJFB@ez7BdsT&8seS32?p?h##96swOxG6dCF?atM3!=5VpabpF3 zi&fpzniw^i5p0f~nZtq>CBJU2Z7-R68`xp~i-HKD4dD$k^sqW)cnI?!ePnskN6>Q? z=m)s$u>Jv-#Kv0ok8!Og*I4wM7v7L3;P^#? zce|g0&Nkz$(v6RD7}T%7WY-RBj8Ok16r1LTEHRjo5jdjD%qU5ZN{xiD2hTgt54l_l zK{e|ww%AHF6mpz%9}^djlS3a`6Lo+Y(;L ze#e6*0{ul3 zGKld7T)B|{QLgn9dyd%2QI5F5s@0b%0e+K$pK-9^A~{syL*4Azw%Ku*a7LG zkr8@Cxl$Gh)}bXhVDOdM;7~mc`{YaV{0U5LLqpFjmvQQ1w195b_k7{KbYzSZrKg+{VcNw;LK0ie&R;B7C%jhdlWtLjb;s zli|&k=yAB=Ouk&WKY#@j8-0U*`PbXv!@UjT2||1UDST&<{>}cpo(zin2itRm_@IV= z#iFXs0o}EpYuJBvXm^_s0OjUXq|(fciVN@?M?p4N(J8`Gp=$CFTUy+;!IUKIEU06+kkLsX?w5QjW3*O`|5M#2iwPQR~E>%{WF8D<18%{QiAHdGW_C{c=Jdzj$AG(w4_fPF5(d4 zflfSEb{IEXmcpQDOWkfox%#*d-*{+ybWq6(^~Q>tI@}OSO1SK1~x*!CHal_uS0UAz82j% zyoZl08NB{MU`N2k5MJ~0ZV~t#bHqC9Fx`IPfBRAu*|y=apK8D{r+e6Wx7J9RkGe1L z?U|X**!zVl;!x0CGi-@O^Fc+?ExQ`~zRC+n<_=h^keN6c+Td3)?d*YkOOP zJCX&w)oLV5R8E09)Ikx_FQ=6`k(@q6D3;vHSht_7VuK1Y{Hjvbdb$*5tyMXinjsr^ zsKgqh5!5nojO(o;!hshbovg<1aP62gSWry&PfbciUCj51e z0e7DKVqk67`g9CrH!ob&f3TeO7t7TjY~7IlyL!dHtH%cwm%uj`=U)}?i=6??H7tfW zVO7)>-hIR=iIIXYdj?@GC4d@S_ks5gZpEx9R?5a%OY;D(CE8h+PUy}ErL!L;F`fY4 zUs7E6ht&L?$$ri0QIi|Wh3mO8Kj+ER2%GGjRe36;tAVu2aIq=bujpRt>v;wg@Uy}J zkM)lpdKCub^VZB?&25tzdz%8ftKkI-X>AN)TITFF4|NW^ve_{%^pP6a5ie|;I~2N; zC5)c`o2N-2!K0p_V4;nh7c2za|898R-wlVeIa*&f$f)61P7e$C;2C|4a*>(a6yKs% zwKEnGJWlkM*UXi4*hq%~ySbik0JPX7O&vXE$Y)?~tmn?gs|T?bXG)xXL+<1L!l&{y29&jD)IBSJcSI5#gh%Q*4*XklsZ%s zjnx8KWVCrS`FBd17e(~n-ncoim#sQ5SIylhqO}0q^ffQ|D+Q_#WCXo`GsyVQd|9ay zk!Y8y-Qjz`T~Exft|rwcAzHa&3&LZGoahWn$G9qRHH)83hShH|yr4t0*E`Kplvhy7 zJkhg9qIDCz}{v?!wZ2P&Kt&}aaNxy4=m+zm(9$|a`@F12_Gd59eDQ{1MGi`>f0`j zm!C2cI15r~_H!jcs(-Asq|A8>=-9OC%X(5c6zPB(lu=eAx4{d+#B#{x+?~2OJ5l5B z`G(;n#LzrdV#WDYz~Jf*9_wC1d&p^)1wCr%JT`so;Hat~qvXSta!^YrXLqw3>+(h0 zYgRgsPK<+D!uYR-)fZEOuJ(JEgT`cFpp$tuZ-55|s#6uqu| zX{PG{)-$;*TQ3P@dCeptuhKIRJ{ib_$BbIAYIF8y_rhu|AD#6LO$d}sSx+k&o6gb5 z)y&1HanGk;UQtP&4#~_dZnj-YMA8OSwVE&u1gL2YZTGC<2N8<40PmEpfD?^Zz{v(T z2X5z=(U(A2QpI}xR%eh@Tbop$&vfFegHV>92oIj3NY4zpLREfa$aHul^NbbUVW`IhSf2@_%wX?MG`^54%F;mI~d`+q=rG63I@djNtj z;?X@@o?^jwgdc40UxFABU+9_4+^}k1a1zv%%DCj)S||wz(mL!f+&@(Y24i?3L;VXw zF6W}#d`&77jV06zYOizlO;?xd9tOO4UqojJOj+M^Z?5b->reKZi;tKD0F)HCh-eYX zgI&!QLu307JTRGEcu8s$u^239Qr^wW!>N!gHXJL-R$w6E6%bjDyWAUE6UlpQY1On| zEP!w>KX=1s^-csYpnzJRwrAt6a6Do9HVNNgtfM&Y4M>ybL-gUY>c>xVTL&FV-D9(& ziyfMbQFM`Mjxy9^RyUD2iB2Z2W`w*K`@7ne=XuM^Mji7}`6q$DtMprmBR^{;+GR;R z-{9D87^H64D`6;DG2BXWS^QuEn^D7Z$#cmg9eBlAjiG86RF(nQYD*ATLUDlp*0BBf zRj#ycJk7`cK0f|8Es|GF*dv5YTQaLpsh(Dh=_=qnXJHtyi-!_ClJPY^p4ezdA?{j_2pnj4-eu)Z4GCCh@3I4kFicQ;r2HTuwK@R6=~z_nvxqE(eKs@TpZ5`tqY z8E@quFZWB;n)+)@Ljk(^Z!jReYXq9cpilTyqp$ragoSl4n*v5FOEg2@m3xWj$V}>NwL(N)dAm?2HLxYX#Vo_qH-vMIy`Dkpk6< zij?W72`Cp^T|>^_=T1S+4O3}AA{j~C(1#z4cE-^VoQ*0&g~$o4!!;ue&Xf~A8J7!h zHvQ{DKI5V*>T#DmLZ0ga3N(FNHf(!61_S``EQ63IauS1FOTCp0%lpgMS`}mtr1qJem-138;)QU{Wz&7Z^FLH%{phb{ zkjr#4{EhBebf)kfav~Eg7#Rt6v_X~$*G8Rq6=C--XmB?e5*&YY!!im!A+b@J@EQj`q5>cc*l2ye;HkN+$*dnbis2MXb(cX z>8B&ou)5d&Qb@fJd`}%JkD;`KWS7)D%mp8ox^Q16YM451fr}laXn6MY^x#-oK~Cv( zA<@CT==c6bMtvDeU8LsN#e@yP_b$_GjaCTX*U=eiO1$!8W@q@x2ib(0cMu+Dj3%$F zh>2!Pn*N_?LU+Z*!`~R9t`_pU2(MB_956Y4Wc^fjO+ze;6ipbvD=cu*w`ts?LlTjs zMUoILU&m(CJG(2y+AqIk@%M6QTevqL(Dw+(4$N0qq1EMJ)5KDemTCuQwn$d^+pzMz}flU z1@xLkYLZMV2+Hs>0>w*LE?vGJ@yn{HyOp`md-69-_L}U7v-7!J$~xj$*m*GGMLUjm zDLg2vof!8OFrr3#yaj%;c5Yv|S3e$4JsS~7!YhDmw#=TUQ!Z7VngRh(K-~FfaUkRE zR63pt$?!-}-UiIMA;js>eOioAWX6RXa;?_AMQLQvkM~_SH7&ML%7VQl^}OU3HCW|S z8lD!7Huh8>iye!*ktOEUZy{|DT#2_L_M_f85fgZUr-u z;Pd4_)VS}9TR6mownu7u4QHIXx1#++otP5aPn{Q=pg2nMt5qa}4drAVW{2(>{`$~t zK^fKuz@{~dy?3h2|a~U!8$<(T?3SXfxX_a;b`WWiuLa_Nvq2Hz*A}s>LbOP2v z_JRGG(b7EjomsvG{XsUdSUSeR@IEN+Mv5P9@o^GQGw0%vXk)ePkNE1gw<{g=x*M7s zN1$DwZH7`)jSj@yo00IMjki*5Gf&Bax9S;QkPiNTWPN2=lx^4UqXTx(tHRsf3b&&KfF z%$+PWz1xTY`M~%nlYg6ppj^NGvrdu}u(}puFn6p?iikf>2PeOn!beG!~%lhytPTiJi{XC$EN}6=s>Ypr7wyRw{UqoCu?^j zAEW5UpyKae`{KjnAy?``!gv<%gnBmC#0#loP+qZb}SDv%_RgwS+e? z{nd}%SeEjStPM`rDs7UGRF(jRvPrs{mcUg)zTMhce(CBmf*I2C&QAg3BpuFO#`?F? z`go-D2Q96(uu>)l99OQ`Y{%;;qp47`a~L{{q_Qjf$#E}^s2`xN~p1X2!thGy(PlnceWmkJg)x&jk(*d%-*EpFH)3;HYd8O2D z?`@SCkaiYM&QP>1Ap?`x{t*^2$rZenITao05Z&D&JSS2%=Al%6kiP`aR(Scs1(F z#Yl#!->r(3r}fo3>74_e*4^Hj_^LvyEAg_GYt{u@-AI45V{X0Un!mU^TXPVf(TAv3n0ktmyB;# z^p{fU3cr*XH7S*ux@N7X-ZI_1IpN;7%V^%D?su87qs^zx^z7O-WJaO+5#1LcvB##W z1FXcpoO_?!$DbD>*n-KQovQS9YwO4|sdMK`_uc#i>py+%blDAj*>u%qFm5{!^USWh zIu3sJv>d^xK5gg8#lAV%$`1dwsz>8EmJU;G7O~0d?zWBbT?%~6^WC8qYYLTQy7rk% zkKWD+8CYLQnD>qQ6!l6ofY%euDFB)u8;EQYPqoYcJ`3E9`LxpHDZW!meLT*O88d*y zm2*`^S*u1bF5nVUIk$mDo8M;CZ^=*;Ve*TvwTLmQY+bc8L{~*M;V_JHx~K$mxc0q;?cGNs_dI(Px#ZuBk~uhR^RPyH7!gIH`m*rzhMS&mGMQ=Yu-UCU@^ zOfxc(qwY}P2wJLjH{mD2l$vXOpI`c2VU+d`m^2-}3$KD;{ZBq?J0Y^U_@A1H50}Wdh$?A zj?oxo%7RNm?tFK9L+=II_@kAo&Jb4Yo5&+a5J6A^@`~v2cEw8~03jj64Dxt$4}w{L zyz7s9E?dVJ=AD#Ilcr1+I!FHjqY~fRF~`@aWr@yxY*=;R1wA^G!WR;3Gc__~wz|wS zYkHpq(CSWSNUV;30*IO`o32%gaib<7LP>!u&Bh63(asV-dG1+rjD<|lI~_*`=Ze|; zMuMQ`n?d9wohNVeSPv=~{8{&JG~j>$WQ34lp2sV;AtJ|WJ@bNYGWEg+(p2SRe;g8b zTR?-9XfePSzV^Foi+DzFAG`rvnIt&$~MM)|94 z#4+hKN?vwj-?+rUW6nGcjid(a55Yrk3O+haWb>imF3QMz@&sioSLm+Npm)>JrON8< z{pg(v;i+}+33D_lzSRQ3F|1TouAOYgd~XZt*pqHRZ;FTP&2~!&^p4$m$KDfYCr10(_W1X!HSi64@F_d^H7o@(tTzeF4*cyMqM4g?- z2=O%W(Xb236CS4%qWEFyLp2)muO~5 zw&;9vzql+x5y0wvQ4LXs!uc<^Rq4@(MhjBcHj)dTv(-IkU@UGkwv%>kI^^tMI|c_& z)$=t6KVS{~ies%aXH>f(p0V|^45xRP!em#~E?LL*nC$TK%n!_h#i?sWE9QRpMtNk& z?fywHw@D$lfBO#ae?N^oA7LZe6}y==>~mx$u{G73;6Yr-39M95^6S4KVh&Z3!D7(+ zVr@)^A+6YD?3ka^@AiFQr&o@@8pGHTCq(phg8$1nVa~+`aq{vYEe4*pt(z&v>x|O) z?3T3WF-xpbWgav$L^d-QDkn?Iua<;#AJ{tj$;y%#heh)E)uqWXDke}<-Wo^3yHL}O z{%Gv!OP%hycFpxqV0NglcNl56*2B#tEl%|1DG2X-vr{V6B9ELvQ(%A3-(WS`_b=OI zEID6Z-qOtrNlkb9F->AkyBf*>^# z?aX0%inctpe)_fH5=FxD1W@v;N)mmxIDS*6mz4Z-lwVv}ToalBtktH%Uia=m{nE296XyKaF2I7g20sjl#hRh4vF00>yU z>jnq&H^y&;H!e2LhCf-5oMA}4m3MJp<{?HG18y0(ymG!ObTieF>5Zu?Lr9u{SGTlK zQT5#@w`4A)6P=h4Wk|>;t~lBdCd>YW(eq_b+Iw?nN1+`K4TlaPZpK|ti*e=JU%Ny1 zy3~6Q{nC7k_G|WO79n@aLhX*eBMxgw$1C!NZGPf!y{+%x8cXd5I$`js3++}Bsn_ip za+I&U78pd34MgpvaI}fm75zR_>t+)j<&LsjW3ZH`&oU7`hk`d>S0zzJ(fjovrWr@F zFv%Y8t}2%xr~$oeOCWxsb!*Ah#<})rJ;r%64vDK=^<*Mq*D(F6W0ZGUy6)f8dTs)m z6RnnMI~m0KD6B>1YDIZR*71l!?7Sp7s$>SdQXDG}8*rF4qCt~V=pxLIAZRSrX{3QC z!u7zq^Mbbbebw4%0lc#nlae$IZoB8knvAHUA>yS}*~(r$k9X==kKC!llEq6l3_2N= zs6Lov*F$L9wR0=lzWx_<7C%FXTwU%5t`t2AhkAX-x}D^O^fCXLBJZ?9?+Hvgg4dv3 z8xx%7Mm!f}nc`s{)%Mk$=QB=OFVAxft92SBaCKc>a-6CSRpYRg7pAoW!!0F;za@Yi z77p*=;(OLjJoR4nukB>4mnM3v(k=Ev=U~9Tc>fcaKz!rbWnKg|r=lXx=YYgS|NchV zxeo~w!I#vJK$r|j$*%#Wk$b8=ufYMD@}wuNaS0%mdwB%Wl`O%o;$_o$*UK5DtD?fb zcB$lft`sGSw4?a)tf0G*ex(?!Bp?FmcM^p$7w%q&~6b6m?4fv}gZ?w^63#IArBWf}J z9JSyncCo>iH2+Ge1upE9ODRgTO)Qor211?)Qp~s%{5IruV_ei}R!FCcHi8&sVNh(c zdIaAN*1&m|^d6Pox`_D@G_5c)G2@*&kknP>0wASgzydPSYbLy5GPR&~Y`T$zwBWEO z&BvdzfOddd#$V2(a&g>?+IePT#gIAAB@A2^ZZaCpR2n+*itD^m5HdD1Fql-FpHz|v zmRsqHtlYgVjvjLgiYGESaULV7PU%petM+Fy9|5S751eP0Ccx}^}NWOT0vS{^>Fm+8Yv3}m(?qk z>mMaP^t+A*0kSxpafQgYvbPC|k#{PnY<2U=vK9deAnCn7oV7LY_8JMm zSznFr4&}i3QK3BeV`rwu(LOQFAWj3Z{Fm!0^ebuqJQjN(j~+$@XixxJ6rMuUi%bhz z^S($zv{aL+7?k1=X~oprl_l7g>uq&Z9AWxP?;$5Vi}k)h-gd36sG4e)!%> z60T1-IHzDf=|bNQk=&lpJI3(YU10fv%a`~(^By~%R#Ti<(T5*=K-^!~kbUr4h1?*ioR0_&d;N7d| zC)Q9~)#JO(((-cU(jtRDxZQfhuW;f>PP zt%g`c_S8&Ls<4?$kCG$cCQbLzJ|z0)76n7Pn=gZ!MzLS?E)8YTlomX7{+B1N@6Sbbd) z-i3sOxzbR7ov4ZX`xx+bHywW97ULc|>0E29Z`1n%caZ2qkz2iI3u*!OAGv4SLVx2=Lj@`8*UdbDm{!v+X}C-@5dT zo0QjM`fp2wST808-t-SPM72)9dvG2({(7r;lZeu)nJQy%m%EQtwN&aGu3ACh0BJg) zW^&iZ>wovWY2;aI+l7|KQ9i92v}VY-gi)7GKW=xk$REyG1ggRn+hopNx(d~Iz5K)D z{#&`O3MO%O2GFJ>a|IWWmHeS_b*WV8`lg-9vL#CiKi1U-BvKWgDYRe6B2$2g3pXUk zryFz_5*lh^2`3ozxt~IU*pIq@02%)&U5CH1fA$y=)`BJOxP5vj@D@@^7z$TsVd{)8 zpeLj!f+`nV=a${{ND_uqah@N)08CFT#2h1!T!v#GymO08W!ZCBqB zpRo(E(o$zxwGAb)2HK9-MGTCev7uDvuoP#CA!$ z@5_w7icep&5d`&`Rq#}jzKl6I1TeH@Y;W4R!(Kx-+ij6+p~0y!e^Z4B6_=)r!1i*{ ztK3$TeW?cY@ava?Ahax48y8a05=F^6rNBNmNjIkav z#($A02!5X(!H|p_=9vpO_|2!X$lhp4>UF=d4c@ecZdwSo^|Qk{Bqfo^yKYs@w6tbG z%yw_jsbZ8&!2V#aWT#W0r(?oy>|B?vt_I*ufmCz?qND_wIDwf_OHab91N17;d4kM}2h@PlKU)2EWWGO&v)UWk zPe4{ot>c-pZM{vWqZ8>rhe2H=SPZVQVkZ$~;(=+CLzd27J-phWyjF+!$FA>vIpJnW9xnd{|cbta`Woqo|^{{R;7JAEi1p zZ0i6 z%Wm5JY?x%WbKc2=?otY})f+4dZZXLwBa~@Q)K#4P7$imx?6x&^`rQPC`My+)&ESkjkP53pa=t=~4r&O)wc)LQz?D zTxa;VT1eu1&ZbEBrKY7A$K+J;EU2a$%IS;8(%u=r7TsGQ)ps%yO$1uD!iWN$WjtdW ze|I~zoBbR`>$2ccYW%+ZNKA!z2yZ+GN-WWckL_YtzWwlkgMJs0>?%-IN%~s!h1#O} zoBO59Ngx#44JVwXqT%`!GK#OudMrrZc_8=i9M@D{v>7{&coZ!AsB3vm@`Z18{@&oO zqsy+W!IsdS0X$7u2lqb7Y+AF3+r60T%9lHqkM_$^gMl^Uawo%Z^oZ0ej)UvsblK<* z8VSr!T~@w1Huw}P3n}Rh@IjY1_UH#7x5gw!(TT;74J__n{;jL%Gm{p1_2W_3I8_;M zJAF3Uum}c&?j}#-P-gP5jWJ(7r$rXGkjhw{iSV(AMNEr>YUhUxt$P)WPp0cwNbF}| z-YWBFReHH!#*xtCCPdaIeHW--3x55 zYKD;+3vFdL3E@pJ>DNA`b|!#t&W!Qe;EgT@1ic2d-2ja@e*N(O7N4?BWmo`YHs&R% z0j}H|U0?$8J>#DVNOyMSxtj;A$REExN3MMqiBx)=eq7_m6tR=FlYCrQAE{|aO(7mm z4va;l{ZnU){iud`H!823gRG1z{&Mr<8?&%^M~?|#oXl)Y%E*hjMJ+EA(Vq@J zQcZis6I@gCYDrHE#vL5nW5VLNvI}$avkM-sx;q*0YN*U8cPO?}Cqm}DjKxCrqHVvK z)v9j|jy3o)_!T|30QOtMc*>`Mh3vLPL{Mk_($dDXlxEBHv;2kDSYYKiU;6-=R92_~ z>d@ZzIg-+}ba$&c*L@VtC=NyT``gu0D3hg(>VRBb#nDaxeYQpC?ygnQK+I57Lp85X z&HEgi^%R^=g^Sn)ooik3{Ek)sp^d3yu0u%52xoQD`pHKEg8EX_#wfuGhe{_I%cOg& zuDJwj7lI8);*CQ3r!u!z#h=vr>nlc*B=n6;%Jf=QJLfx^=2o<8?~Lh8?vHX8)yH1$ zz8ACR;*vEvAeodoR~a<6I(fIM=J#Sj&s(TN$V5$!Yw}GmcRzn^LSsyIRDNLV%)r3N z9Pco@$iW_|H?0Y+G2L2GUETF$!Q7~*LZ{?GyDKgop6hdJjdWvJA5W_ihH|TG7hPcCEpBUUt`nlh&D{0O)oj|dy9en} zRb+TXdr%|w#pN8r^b2)(&D2GXQ-`z($zkbn@zMISvz1t(htJBgiju005G0{F9%Qb` zMUY?09@#b@9gMNLx$0wuQl?0iMp=02&zrldk{+9a?HejD0&Pg7 z<5w&Ojc1##GmSXThR7<=$RRCf&vxZ7Y`pQN5l>4TFiU1x7-h%bpBBra?fx}$N?YL}Ho z*7bEu-1}NU(|3bWaQ1p&K1KX~M>Do~I#IZOM{UA>|Al>1^4dAQ4c!vOhE|Z5lWxgT z(@d^Y+9HlVhB1jAff=SHw!v%je@nep4(4kzoNq=q`jS|cSYs-qONyJD7{%VIk~A!U zRdGt_BN=1c4lc4pW%0&ej-NCoTzP3w?^1V_IymT1d+6PE+&+U{R$U|yfTSz6PbP;v zk-(rliM5`#?iC}(rW;(4GT*N1n0fjsHbAt&Vfep)iupO_pB{^%12fOdBsE$+L z{7PUq;hOLXAFcRuQ|p&$foZkp+nMXm%O6AH7(7r-2o}3Gn%0Wb&JqieWH=xZEZ(}e z?8_+0yRxAvMF+W!3Fg7s59hQ6N3d_J1perK(-QAMnz_FAJ-!z!d zMTPy`;7;dmH$lhiPlTe0EmvseEkhTVO$jOUt0sYI2g4~gORb~jSAeN^3c0yuD0x&oAG|ANx6*6`t+xjHxKJE zYm#_Op#Q5vypJlB`QkCD@*@8GYf)@`Z94XQf_0nIV)Y*o!!-~I#LOu34c{4;HolER ztZO-Lh@=c5W%Qs2@jT!gKA$GuH1+xTPs4K6oeq_Uu2s@(2mD2YLah{$EL_=Kn9;N8 zg{Jyvu=6E~H{vLfUt78O=25NAmxx-r_4YOhlI?`i4o{+iH+-T|B%d!?LACu&p4l{z z($iphlO20#6GL~^8Hn~R1f+DRhR;`BTcXj_ zrXUd87hGSm-zp32E00(z#$NMj*6a^Jp?jRa-&rB2I{{sHj6RCvO_?dsgc7NSbUnGA?ICEpXcAmmbFz0^^#F?m1g2VZr^6h<+uj+qXgxM+^s(JE# z_{3sg&%(sW9P4Y|BMJ2nd`BT9ch@PGz*6L40ur-cj@VE9^EM;MpIx$rL(6>qbf{N<4+RV@88QYxds5o3fBYT+&y5F~)UbX#T)Fh){Gg2;%W$a)b5hhQE;uL^8{-<_%m^9$yKd{RUNeywn!98Bf1NXz<%OAAU zsGOxO{RBO#FpejG2TetA`dZzNbK2l7WcZ2gc`}GNC67qVdLY@yfk=|gUC8}zanpXW z)EM($QD{AAGc_edO@{zy@D~P{ z*w`%jdh=U_gY2!8^Y0n^p`MfsgmO(LU<-OP!WF}>hbS~OMd&p+gt5}ofE*}g18GFvGYV{`sl*M;^7`HUVbiK6#^LlpUAh>C`P0{tKU zblrz_G{)augn|_xxU4UhFf_fRKJT9-F|J~Kj&kl^45iNfwF}+rY)+Ed>y&jDs=|wCjw!D7lO36MP``nQ z|0;fHMZ#)gJ~#o+^YC}d>B|d12qB|S5ZC#CN$0VdEM)%gT<0f*p*hFn3S`#M5vhRE zG<2!q7TCFOF8OYUL5iN;ZpQ;34c>N`%Sf=#C;ofolCvpJ?HN0Hp+)+6s+OLiBbRb{Bu0@{JTE2 z!E^nE1}`6lv;HKt#lKQ}E0hf8^YBGj3<_j=^9t4hu6PuMjj9Vf%~VSmNur$qD3tP_ zEb|V7F*fWspc{ZH+~edBOC5}Os@*?B6-g`KB$P><|qUpNB5l^v=4 zWtHGe>v;RH-#-?Q{A4%$k9PB$=+8(R?US&MpM+)qS6C5gLvx+Ek$`V# z;cJKPbbQc>=jS`}^S*hI@8vk$efX_Vg4cW}aAOkYazA^(wX$3Y;tlGOR}sDmZy1%i zY#VQ%@P{T#WU;j)OlL!j?!OVsBODBRzJ>n1(+p@QcDHdy;#N ztrz1h6@uJli87N8jYh>LWt@`68pxy!P=A8EI)q>=Y@y5ojdU$tVEfT-Bt&%LMR)zF zZDNxe?XThlS(aVssW@!0zfk!0A3EJ4>ws6Bt!P)T&?(i!yLE!vzK&=VuNzV=@RfW22z%mSOWSwmG(6~NC{R(=Tlg>kX z3LcMRzadh8-4pMlmAu1$e1p~hkNNDF0&6`u(JPMk4T;tvS_FyO0VO?$3uG%VUO}+Z z=+L=b50!kHy#k?kh~RGqua-#AQ)&J+eRVN?A#~q#H5_feTb#G*sV-QDheP!@O27?;Q`D1#+bVit9!f&?aex2& zC#dr@V*eZSkMS2gIb@)R1*jcL{x$xvO1F_xyN8_`Z;cK$?fF{fPo9X6Cjfl_-jfCtW$@Yq5Yy} z!0g3dKe_$g#b1NMw|ssr-}P2{k5dpWb(Pr~&+Edw>;)U1pfKFcgpei%aP3ISX8p!Mr1|d86a50I?;ES z^$X?3`;Cx9{r$sWJW1+4+J2{wCVHa_|4b)zLV|*n-Umx)BcZ_obx;uPSusc&(lvhn zAo%yAMtxK++NVm~e@3lYAGIzIVC;cGN&xJT9oKzN`?owx%A*Z0e?==dD^!xg$U0@h z;9ht4;Uk4ZZb+cl?JS{)a#X|YOm9c2MJThQJzJPuKdx)r628% z$_rvKv_7jo$o1cJuFybGMas{`mF(Fxrt#Ems+!@ITkF|Xm+#^YN**v;X*%C685{1{ zwdYEm9k^{BD2{6#EcI`HcfI{QELuO`ajLXf$B9XOtaTG|-dD#4y}geM>`R1vX`QNE zl)dTTUPjTp>E{9#`jISm4fnH!tBTpgIZ$q`ntpJFaPrKl=FgG9y{;7W(Qo zt1a)MIPioZHC_v6`ZQtDxhSETn%sN0Zus-_@j{m~wp?AiW5Y!aUKZX7+$NPW`q8$; zX}_^{TO^Z&m~#vIHgP5Mb5)S=yBsRobn4P3r&1d^y5!QSq@@{|bb#HnBIDczKj&IE zv9jW@B*D4%Y`Io#jX-)`ju4tg)JRyTJZ6vm;)`$5)(yQ#!MC+zg^8;j{*(rF-8d=X zqG#@Od!rJ^4qbyZPR2phSDVvUM-%IPvsbp95u3e>1<~s(KeL8=XhCNG(y5drHnEMj zQ_jA7yc6D>RkPuDRG6w1`TRVYRH=mr+2|l=on+pZ{a0D0coD-XvL7w(W@e+Ru(qUrz(+Esm!nE zcrO9WT`}Av`8Ke+Qh~dpjrmhmW>U6>T+#g8kx8Kv3k}^-PB`j3=rE$~g1++-39Q7H zo6BaxwnrON_VTLZWy7el@|gJxqDDS-%jY|nr>l~bO00I+zd|B8TzA|1S#q-rP9_mG zE1cYTFGe&GrcQRK06NQ}8KlkhA*Z6T)MaK@-F6P#J>Yit@x`q_&zJkk&sZf?I_}p^ z9|9Yw=3cNLZc&;3A`-AM*hkzDjX*VM>sKy;q^|FHdFYg_ClHHk#BpU1K9DPLQJRSo z!mATQH+Qs$3ZrqpSiL3(%?vsj?2o`DF>O<^AMWuka9j=RT?^{mm>rNyZwN_Ff}GGj zHq3RX1b5HQYisvQO@jE#F9wugMnC#aZ0eeoxH3|wSZkvufD1U;rX#_E!gwc&@jTX^ z6*&&_;l2`|^1pd`5T{AMr!LHL4*UAWt)A9p2P;eTbN&mL;7V5tPCu>qhEJOeEpze( zwHh{6g=$yeNh{nD`Oc z!#V}_U_Xx8q6ZYl^{IG_1O)y*F>;-tL7<=D2N(Wsc|GsAlMpLG2k!6hD#_D?`nHGP z^+G*hw$)h7Z*AA@{ujIP|(GvnK>mi5bUJyBDKFylU z%3OU;P4}1|8X85y)VnSvo~Iq|ysQZQ{#S^VN~!Y^44s!W9nYcbdp$GB=_n1T`i1b- zk-^#6m25GTl9&!x=6*%5-4?Wt9DqGXd9+WBw9osX-#5Ft|TVoaH!bzBeLfKs*Hg<8K)T|2*%7L|uvG9YW z2dPX4qYQn{`U4>Y>GNKZqYBrdhy!<_{Rp8_i}_84xbDk*C9=^DisLPxe9P8Y4@Z+T zT!H#}Vb12yZevD!2h6+Gwn~{YY`HE8jR$9rYAcT3G^sAGy!ITOuaT9r3nXz2g5nzJ zK3M<2QC0LUF%-|K(03}2R#B)<7p8Zi6^^T%nZe?Zp{AB6&cNOg4@gKdiZxrDQE)LVAOG-ab z$e$tr0xh5u3)(2{VT7XpVB{^_+&3l8`$?wG zdLm7FSI7faq25WJHs1uFwH-I`zhY;9#qS)aGreO1zW64yvZHCQ-LqNYp!yv_Q-8tb zq>P?27Dif}Jn*YD3!V!PMp_mPCbyy~d^4|bXwGk(Z*L}Y6Ye^(te+!~0Q^S0Q&tI5X$@1bVzJDoK1F1s92{%MQa*`yCaEYp~nwbW!=H4)!ff4cW|^t~|} zjTnl;Dq4)}*L>VoX}h#FOUMe(HDv)&pv{O<8){&yG6>G%vTU{;?_{u4j9Y|y?_Ki- zXs=6shHxhxb@cK6i&;ufY1*|KgqIBbIAQO29UQYdaLLb;QSfhwpj%-&!88A42W8l& zL+ojb8G(7@GL%aEz3Z9*W_t`-fP}FaR3M0m$#&Q6ygp8)jEmdwSz6*oH!;XbP{G5^eNae9lWaEI00T-b%>;76hk8x z^!VN0+1E3Nkz&;0@;Fl{O7q4ZgP6S@2N>4@uCoA=0Oa4GkBDJbqJYWsc~h8CS=U|O z=p->&LNb%p-+8(#r-EWfPh5|#8)QoBXw(L7Gh*(J+WL~(`q7Cw!)I+ATNNh*(y&TIe?SM{6wPI|>gc>IVsdv7O1S@&(=I z6&yVVQ|TAV3g36Vsfi(WqBIjG*UqEuPp-rVlI(3<_7!4}MQN_Eub=p1t^UfL)U@#m z^CXa;2M>Ka(<-)hA95zFqR1Ogbjv@|nwww`liWYKuW?p6W&IrcP2)3A7^!D-Xp3Xx zQ^6&(4ohbJ8RI|l$w9|CU{R<3ayfrNQo;@Y*vONpYG}xFrhj|s#C7GTpOk>G3gpao zu0j1*2#Ei9?X?@14sumMhq*gZs*4frYEYb2Tf;V*py%muXg4O4X_q}`U;>J=Fq@z_ z3jahe8WM|3aQ56xWw`a)lObc{!>A<+wFb!MxoTGdgYwy@R*HWg0AVR7;Ta6IZUujUF7y1)X}D z4;-E+*+Td8cJen688Y6X@8wT9s5mf5}A%L<)*F-f+uug{Tb)%=GBg_MpvDsAX?3RVh=WT80gLDb>BUqPg_Ght^;> z^-YmLjW)YSiS>}fTxx)wSD(RU_zjrZ;u^!>yGI!;x)^@}h73G1?NCJtP&56-?EMbp zdO5tK$u-H?m{!23=4srmTEFgG3^!z1h^cRFbV~%*%1Kw4Uc}po$rR)_E9#LM)!(l= zDa4MY`Ofm|AZb>E65CuCgG~N9hOtelj91xn`;O3D70T<8XG3`sI1nd7UjTo#la3!j zt0j1eI)M!`azyZMvf*rK(a(n91#7Fx-REO!zE|72FEleoa~2ARlXJ+yEtR@NFL|p z=+Jjh)^$V?tKad-IE$0G*&dK&hrXg^0nE~6~JgZ(LV!*9&rvBO(FS+ z1V?K1@L?`7p&sf41Wy8;4%PbnA0M4|al1bM87mCC=G?sxRX=Uu;jVP^gj<7COVEy4 zu`b3l4c&d$zj(K_G0}eOu%F9xka7E$RpQmg($6^iDV>HgM~Uv#C}}p<7_Ehvis{+3 zp_{u>WO=`7_r7Uht+EKc_F&3|hsitk)uRqc9xPeNN)6zEDU4|G*Y%&Q(?m9h^q5k1X$|jSc$h%BUK=Zign792Hcy+6xV98;oh2lNjaN zN654TJfn8|!!1Pjl0Yb=bVm51V)267zh`OE+?P!2zn}!_0?6>+5UW^0Hv)od!B|*M z0t3-OZgoXgLc{l?r`!-=QmZ?T-qrhIL=ddP@daPj?!TE#%+<0PJzA5+OfEPK!+}A=rUi5 zzipiT`ED1UILbKprLK9xO zNsx*m-4`mZ!}s<61Jg#gYddIRF7yj+#(N@})*0g9u@NB@POBOCj6)wWq zu7=DdxQ3LK;}F}rvDA}#cgO;v0U9n0rzkcBrldRUAcMADo zbkQXBJ5HcUR={y1N#ovruE;lbF*k!IQn0R@LV?GABy0QN(maWWO$oGcWCfA(DfPs{ z+or49YzAW`6Ub<*i+NUoUjz*Z9$%e1tPHODAChyNzrtfjXp$`(%#s|PAI%pjArU^BuZgVXr zER9-(jYJkw0DshBzU|3>bX?tyRXSDK)_-LlRYf83_6WKBvtk1{6uj6D&#z2RsfBIu z5`eF`x_)!?qHtjRy*5Bv%t@^!&ypsN+oki?sZ+H_O;?c1zg7w!<1`aOb;<=Hb~%Oa z0TOJvrk$tGAtbF_buuuT$efIyjI;K~^EDdx3D)C^5eI#7z(o9vz;4UOlrO^JxD%&Y zh_Q=6tpZ~e{V$2n0VFc{%TIRnM34dO_@^dJ0S=Ex??@<=2yVD#T$s|Q@zyN<#ZyY) z!e~`~p{4$LPKV6(3)rg7nkljN{(51={fr?%J77^Eh}nMiT@hjF&&UFqIiaTAX|F5r ziJ#7H$giUq(|qh#094(#?Nd@z+h?si-bDI^TeM2 z8{+<_N1PHI8acKpkBbtK5h^zOn0~geypz8e?rP_TBmk^%T`}rZ$ib+(lkeIJh=|p# zEOp4i=v2@LLbQNXJ<`l>IFXXU~?M2pbT>7KL3nEzvi2tx-H|S-m zOHiC%kZMzrv0d0#g;>4NN|VsH6k4?SGr_mjOEf2C5_&Tp00@<$#sDYy4Slu<$ZebE zog9;y16H&AWNo55UGD4+%WCVFq}8$Q~tnAGvRLqjwP zTs9y8-2rBZ&Ag#i{Avs%yJvXfT(JyGjzGXj-P%(T%*eN&BlS@{kk>ts1<9lo$mG1? z?WwMKZGv9EZ*K9PDrdb*QN!TqovD00&j%(c5@}$KYFqu0=;(Y{Y%96ea?QO5aYREA zsgMJ4e)gp~izLq_^J&*>UquJZq*{JZPII49IP1Ah`Ad08tYTg3eZ2}K7c$=`FZN_A zoCbK05aS6{8oWAPts}fKqm&5C@@23t4rhUY=joGS=2auXd=YrF*Rn7XusglfRynd8 z#HKUlGZq0sxDim8Uu(lEOZ@d>|b7f`7Y@ zbA7m1ETNvi!)%tA!8Mzw5TEV7;D{GNN2oc2$IlUp#6^dfG+(`u6E&4RV7kG$0)67U zfq7{%Ph!|(F3s!`DQ2T=;R^T~^QXF$SFZ+5 z4GterYF|}M_%6`Thec8rY;WiRS7z5w_|oE=eObyUp*ktU6rZ_u@tilVJFXqjeZ*z} zRGSmM^(Ah?lw~#>KmHKqsT=c-_$DzA4w|`Z$Pt!@tuolJa@$e6=3{iI-k6Irt;wZ* zWK-8vl4LZJ^>c83-*DepIG2zG_erhs3`>^L2o{3-{ogSCe=n5uEr9Gj7pl)xIgRr| zEf!%RP)rBsv`G5xTn5m#dGmT^ijGimsetT<$XF%Y{BLPm*~~gAULL+y=uvhXVAU|} z(aG|`H;#^h`5916Tgmm_)!5*vZie;sZ<@WV@xTgUOsD*84qG1ARz_>FBmjXWJTJ)+ zymnjE&yVp(Nj+xQ-Nt=V^#2LMy!8(P3bC4|Uvv=gtBHK5Tzl5d_mdi#$IZk9(pcqs zk_~1X9w0&2wkC^)^Q&Q+RqQ$l?FST6p9_LeAC+A~@kV>4 zbF6G>yTZLw;SAz=3X;amPVC`)wiv!dLtRS6Sj~Q@R7Uxs)QH)NjVYIO26f|eL%p{( zWvZ^i*Q-$hDWrM=8Uw8uHa!mX)xtvT^GdpyA4Afr>t=@l;aq((XE!p{WMd?>AI#_E zg;dYvML5&HP}r+3@u$_y-W`V*`n~)qo9%gHALjMwu!}jqr<$ z%02n%S9q+7%jn8H>e?_jcr?|NkWj#=6qAwpLy^h~W3+v38?u)gBIbi?o-6U`hjMlL z`!FM$@aLL0TdJl*5fh+8_!H97>m#AcXhZQJikgr@8fj@kum*EW*N7&$X#Mxn@CxAjd$rt zaI1mW{^q-d@YcmLqsL&!4h+Rr&(^8*_&5|6A+bc3cggP@X-JmPuYR$}wYr=mifcGn6k5cDDF$D3)E}S^P^eSHF<0?rH$zc>oD0{O-H9O7K z0ag%0tF2yp977G8!H}^ieARxVbq7$BOFn%fVf$5WwHkUA+ z*)Dv1{f$%^Xm5E1vxm3oWR~+8c?wvQTo+#oil^JI8khN2@#_q&R&|{4+wO&Gwcti& zYL>}=X7@=LaWb|J-aZxU-IC|Q+0(rrNWty2!O1M_`0x1aO>Z%`Y)7)eP(Fti~H)9~z@6zaV! z8875%MBOX?;b_ShXm#e2=_(`W+^Tp!84>m6DkPgMjKMEEgvwbU$y-~qqGNTO5~$Y@vJelf$R^_^&qZA6W2 zHCh4aSZUzGmWK2b67%66-pCl2U@^4$SY&y0yr9vM;_BOS1m2*%;!Xuv2{Q0cZ5PCM z8X_yOU+z4&*t#L|AgB}LwRb)UI+=C!pp3-Hl*NN2H&DXo>|61H1qRZQYpi95~R$91+glKu&jv1@(4eIWkYL`=sPBSsS5AUjvz3HpV73(Cmt zg8UuZIX?yg&^a;q-Bq5Ig zaU+DZcf%Tz={!kh2(?AW^Pa2S&)?ZDpo|Qgr(f`$uyf@NVkBA0|43Xyg@+C_lNTBG z{2$8RGAatNYa0bAmF|=d0qK$!q*Fk;ySo`0QM$V&r5mKXTe^Gbp*!Xb=<|N>bIy-% zojy&TUn;|k}s z)=fDbP}#W!Y;hpHXvPPRnj12n?Vt(&VNpoT^d8dDwE>aTO)bQ$LoOoE%>(wLKysfa zlGq0dQaeZ;`O&{Ra(DLlgM08m9IcXs1f+yKpOQ?HN|I^-ikn$hzqNVb0vO6+sEhV^Ql2J>(7vQ`9#5;Qth*l zV!sym4qhNtJsvh2G95e-J`>7^W`k#fWq@Ji z^0e0Q*%b9&^G@Hhq{UheylH;z=r-@Mc%zZdyNmq*r17%RWirn^2s0%}U_o z^TwQD0?GZvE1eZbFz<1!N6iz3wOfb8gmQD9%h0gkF2JnxK-;7nZNFVUPilVV83mCO+;}_)vg%T=Gvi5 z#IJ8u%Dg{l(CIW^{ru`Dv+^BqHlJ#1+g5oUqjS-5UZ*q4Xp>2c0Q5@pc@KG6_*63b zZS2jNqtofG2=QwcZrA>6ifyREoa)fh*^YCM2>_FITjgz?=hb%rtGgoHT3u)Y7rG>Y2YhZ7;LsHVzCZNls zinDabo7!I5R*SuK$8KzQP=46jyz^&;@=_$+trW6_=04{#`+@?gGfX%KT^mO||aD`J~#v9LPsQSm)J02hA{tw-h!|#`v&py@YgxQqHHLLgo zd%O~_sEhM{`D^-kA%JE~hXOP+X&imL{IGtXN6z^2LSVq*^G+VK50GHf2tA07aTs23 zU;s#~3NonU_G#}`*r?3;YN(EfnXh0JuA zdBTm3Wrn+saL4`%2C{f66zzGNV~R_b_BWE<$=68F8|8(eAYWK6__?5aXUS7AH~v1D z4}Ue{B{=%XzVo}~hp4Z2oJN(wci{vrl)A*gAyt0p^yUjH*T-zGcCFt6)Q>WJcgZ!G zMX^8QFGsQyr87%EHQr35{IK+EILkwKB;!x}nN$Wl>}*YAP)6yjSN73buk5{-UfCBz zy|Pb^dSz_lz75S*J~LVc;L|F&cb>r4yKA2u$2)%ZW(DM22qE;D2cXC)BE0i7zOGH5 zbEb|pJe#OF)is05lSx$bIRmUKzM?&Xz6Oxai#T9JM4Q8&Zv|j74jC4 zby?GEHhgC#Vw2L`p9Dl$g+LZ(Wbv2o>6;36TG|>Bm+lD{J6ihq%fnkP`zIHwv6&KE)u#p*2FB$tljGdtF4#co z$gZ0mu?s~so!fIe>+Q8`Y&(_ya6ch^La$2;1ZzB#>gj$EcajbUt!8|?%4bvUNyQ|HW1(tAf#k+Zx{m{g63{_3%g8_ zyErhE6{GJ_fQ>PMmc(RS#Qb{(7UJ@a0;8$>xt0%}xH655Aeh)PwRd*s6sW$bBnGc+ zo*F){0q^zON5JZiUOug zs|>q(hk4NQmbsa<@P&8g0N(~RP2ARPt8Rf}$p6Y$2<_;1UzU|fv&SmMB4PP% zd~~KWPjFp{{QWp%C+dayH+aZmUtA$5$bv$SkxzdmN$?^`=;w{U>@a`D_{E7~I)V@F zA)+#%7r2Es(N?EpBE(bKRAw^(3+U_z_!vpM^;8=(x$Wb$!2@4u%dCnn26*iVilA_Q zTV?cY`Tf0I5zT#5mlAq7(1{u31s<_(*D9og2yL-r^WN3tsN1kUz{h5gN-cO9{b^9F zIfrI02;4qX_1dNzEN0MQ;yLtydfr-%k4mk*HtJ*hXL{h|2vVoHNIGfBp5 zd}HI?i1GV&?w0W?_-I|@q#GXOm8XZideCEw>u>9i6m4kBZ;k4O>!tw%)ObiKu$f1+ z+v%wP6xG1^`&5(FReL&^1Fh z%-H+qt$VLuKd%togIy54@e>Ho+TAGJWPiQWdOJGP83xpK^E(wd1l zHJin|Gt|qZsRf^}nrs!mWBEaGf))LwTV-r{c%>H!^!WZXjp_Fe$6+Y~aHAZ46$YI_ zl)9jA7mft~j`(cd$tj=Do{#?5?E62nB{b^A z?2G;5S14ln#}qY`jJ@oAn#YdjWhO^&(G|uzK!B{!HN2WH=+XK3&c~aExQQ_AK-Xvx zBGX4&Tt+Xf0ZhDlbe}=E`JiJ1c*4`WQ#*<$*H@gIm!Ba(z;!0MrA$3Fq|sIyn84%R z6V90L^(Ha8JM78iM>1&^GSTY|1z6nA4{I-B66z#3+1O+j-t9%E3F58Pe9K~Uo%oA; z$i7#0TxjTL+=u_eJM!oqx_7^Gu%+JX^C3v2wWik(Y2G)TxiB3f07IBR zK{}3*o}60_4EZZvJLEjWK;1-sm8l(utCx{SI!kc}U7ir5GLP~!6DKX8NhVF}wItS@ zUOvrTfe!v&v3du_H*h9jtsqN1b+Iwz+>HN&U1hflce{`q+KR}(cgO7W05KLyi*xwL zbGF~YMk4V6_k9BOev-I%oSQ>)JDi7H!o90ctI-XiP1g_^Y5_qk4l(Fbyn-@%guH&H zpzOcC7x5XioiCtm^!@95KfHkU`HQl>SN{XrKd9#V76r9e6|Twf?oFGk7vK=s@bY6g z@Qs{jAlg|OiPQ0QzE-~rv&UfVL6OT6cr^$tg2=fs(ur*Nx-k@tr^mL!+<*9CE(|Pk z=97B5U{A_zvXL9wc6T3JrZOWY$GlmJmJ$Itox=)0Ra$9FX^@sYJk&p)?3x-i!4Mr! zZu;Ly8h=3&_5ap9;pfQ0GUeYPC^vK%dC;sp>H_i;ALiO7>1?l6T|ni_!QzVI2Mw!? zBJvGpU?x{5Iwi)(U!Qjl`LxsIiO*jVo?0WWi#?D~=+M8l>aCvuubMQc@BDPUQ|QmN z++04EZ!`mp$ClM29yHk3`|up~MEW?Sawa!{Gl;VHjQNPH2yRViq5p@wZ(g|jLYT*Y z2z%i!>I-+F|8h4fsC3iHh~vEeVxJ<_#l5M`g#PCGsGGv}&b}q|IBsZgHuxLXe2^g4 zE3OUj`%34~c9RFP)yO!PGC8?2l~zHXJp%GRS@(h+9lkP{d}Sbrxwf>;_zOLa(0>ym z4Eti6{)-Uw7eb)^5%Rl2usL{}|1o+J{9BgRHZfX-n(+UnJfB@GSi_Q6W62eHuY~S!QiJjME6NSn?dJc z!ej^Z8`~7{JI3Y>GXqg-?Y{OS65hR-k;kW8#1>qz$;8D{C^$X2Nn+&!)d=L#_z{5g z=Zaa~w}A74=JUsY`tj_582#ZO(m${b_^afkFEGS;=4~7DU+LQgO-6WDw_iY%PUnm) zTRo{h^kBZyJjd`Hrq>2`bLG^>mgZ?1<52G5^&1x&V)voQ3x=4Fcj>f=sr0PdcTnCr zujLw^`0V?054xIZFovl=$7&GNO$4h?IU z`vkXh36Bz^c-IH{{*U>Ec{ac6FME;eh5qst<%O?^&wO2l|7(7kpRZ!jT8&lD4X69;zww*DTcXHG3<3K$& zSNqr%NGHrFnd3>-<*||VHhU1&+_3gvxpq8#!#>?hY^B-d~xae?=VQ( z(pNtSGkoM%MLEn@%8b@!#ugv>c~40Np&;lb?9g2d27}@(zhOME;wCw8pn(8SZ9_h> zjIoYNX--~VOx{UIO}Jo(Jtv6p?~|L_wJ=#--^pK3x>gb)(X9RdASPT3Wvdt|$~%Sk z)4cey?5wCDSdhWbc-8K6KoIs=ATvd5l(PKYuVX6*=LMJ8pXaw{ z@4Ys^L{|xge1Pi-v+&NTAlAS&Z3MlBS$64eX#ctdPDpYnwKyVGql3U)x;6Pm>lfTz2#! z#;wII%D-~+>E)jP#TiwxSx4)=yRmE3{3#|@riFX#)Da(c6;89}1@M!RKM;U3p~yS- zAyC2K*2*@uuXx92&h>O^;?p_ey{*+)JOgIowpYDgP?~|=69VXtwE9CJou3^1Aaq6} zL-L&h8Fd^?!qer_TlGDu=31S)E~7gIY+(%1V1klTxw0dfBHG(O!`9SfH!~z~n>%YD zP!@^kY}8@Jjcg#w?Bb9R^fg zM((T`F1+QHb*&m0<<7flW_#~X1DpkB0E~tYV!>V|^Qo~@R3U?b6LoY^*+(4kahENStS1OID>w(be_t%C(Ru5Ky78 zte1}_s>qseIXUDVNlrz*FG6EQRP4w2Ww1+>RkAs-gpo&5j%~GXY&4#6^KA&2@!sBvwmf;YM!%0qF zhx0R}wEiMTPY{g4ZdkX1QKaWCDEy~Wg*qln|Dg5~XXu_f;(%8~`rR_fn)I>xGNE2J z9!fJ*&MFLa&~qg7d5gWpieX46D8c4;{e6hd@K47pVwy;})Lu)`JyV-PETSxsa(IQaW9}%hy34p@H5c*t+T{56^jO zQjZO}Tw~WI%!Xoepk!h*Q4@Rpo`q%`g>Uuhz zOFv*508T{x(#R`QqnvWaj6b775X{opu9EPihe4fw4EjPhxRj4cXficQnR;N$t$x`> z>R3j?kywx}qQG_^Ii;cV1(E79r*W^@3WBt0tg0ed-=PQQLG$c9D+aHr+UAC;U;=@e zlG3k_6qMQ*;c3{y*R!VYbruk1TpnP1$C!y&@Ld{}sn%=@)5N&#_RSg<5eYRz74Ncp zIUqA_WdqW+{biMu-%{yA0C7fHeu%>E>cWDeEQd+IZuRvyKln4EW@Ah-=C-dxN}F*! zl*>l#ey}hOGj{Ls%^Rjgy-8^R8`g1oIhsdpZm9RSZ$+84)6=vH6smOz^^qxMnl)d! zM^It-l&P|!;!Ap04viOO3rOIsfaa=0p=Be7N~OE0d>m+wkMi0mM>0QKKYOqhD$+5O6{#rX{*HtdfvSS!#_+dY0b7zaR0paF3t-6Mth-2J)!)ZPH;Q zYtI%5vr?lT8{|?i4GyTbKc*5K?yBw1IUrWMr?hC+8J?-S4@qRe_*r>Slo?L~U$T=a zh-ZOh%d;i!2fXtL1g6M7`$HiUR7jqud|a#gB`WT>>L8t?>?Rc!l%i4Lm1FxEpBGq~ zaliei9#QG5b#vZZpeHS4Qq63{W+7lKrpVzU&WiE&OQBg%unxPfEP`QUOirpNr?Stx zNO#hKpf)Pi&ml!$p+{BD=40pI9u88}y^3Y_qq8J2QeBxW6AmhcJM4%>E~W`D{pk1@ z)Wqzqg?#%*P?N3Rmo~8$)mr`=v|X}Bl^wU`!D~qh(`b?AghcD4=<@iS)I6}CMD12- z{S4nmInXToAmbb^>2N-TJeW6?@hF@bYO+-ByAek$S_V&R3V)(JaZ~LjKcZ!EU*-Ep zr@Sh#iFQe8MXC>OSg9_My*e>Cmp+iNk*BhTv$}w`>Em%j^VW1W7d6>{MLLomn?NU2 zQjx^Qe2AEw9$lkN1ohKCN90!ZbPTG9#vO0ttwtce`+TqEc0Y$hL{vrH+eG?27v^9s#fmHRU!Cem z^F9bTn2aQrEVou^J-Kfczcvs&Fi477wP?)!y4$XPxBMrb2k+M6BKxyUMoBg(c7)%9 zPI;RjQ9j*8E6Y9kb4$2<;!sjha7MfqL$UHUo@oPm@BDM5-w|x)NU75vGKa5cqI{Yi;WX(Dwl{a`I zzV2uXD^YJ@YH5W`c;&i(mB&(qXcrq%J5@{ZAi|4w< zig3!W#Pq$UyzvHv%iycl;md>vu%@99rF|BN$+L+U>Jc-`KEyS*aEo$ijLi=A%=a!* zim>S)FckVL%YW+ZO=I$!f>VsJu_OTwUX7p46wx$ruPj~3WCcW9xlIrTlW((cewiC+ z!+ZprXdR9ib&&v7-{#voIxcw`pB@k|XwBE6LJ}7wmuXB+%S)HV84}o5EqMu67zUe1 z8%Q$o*&YDLpIejh$Xfh0A|f^X2~W^Cv_*k2$XZ=STF5bh-bn%$ITdu!h5kmh=osPM zg~}lvwyGD_PXa;pXpY#SivT-LoKVd>M^5tlW2^vfhyEu(3{M3fCpU#ObYhxcofpy8WbI8j+gYK>OL(nK`zrtx?H`v>WMhD=BHNX_uYy!XF;cSiBa@hAO)%KuS( zNTy$Vq7L!^@Xs#5x%B&0)Xd#EaGwR9&wrZ<&sgfs9xO$1#==k;w7+w*#8x5MD3uN$n z`gjRq%cpCcRcRFZ^CvRAk|Pdf{@qZjh39g1ocra2EO%%7$ONC=m~o*$Rr%Ml(<$f4 z?fzS}fDyM5*jTiv7<4`K(!}f(3RQBIu4@;4REBwGLj={X!Gf4Tsl9tffv zK$V8yS9+PFpf*F7Hr>0P#~w;*Q2Y`bDKt(?e&A)(sE8 zNVz%=Cma`G6fq-5OlU)%dy=0MqEGw!4pDW|d{Ue;09gSMaL=8wigo{eb0?X}IJ4226H_SKU%U zL2s$NN~dDDQwX@3j(ToNy*^55eek}j5X%nW-ToAXZ)=@WrG>(2J4?B>Useb_@|VP$ zQISXyqoApx7nC4N+%*5ehWiA1#Xqd8O~+F^IL$mgs42ZX+o?jAl22r?ahQ!Q&-zDx zdhpI+FNb6PY}TcBKTU8KXtv{i(kyp4YRT%}xh<_*1@RiSENvQ!WN{Qb)`n>IK#2?) zrjaVhf+G8c`Li(<&m>e6?TSbrt`b>F3cX%ufMZx$;?c&P+;_fYHt{RnlRD^ph{q1U zoz$w*pP9*H3LG*%i~HkjfSOT%?&Nq;A2!`k$ENu-kz(55$FB1slU9cOk zEReDdLL<0_cUS)i?U59e-Z6Y&JhI-}GE{k7KuFIVoDRHzXUvxvlL`(dyv9X-^5Yq?Wu`M~khrwy*tll@K zO3O*cObf|2R=A8~(Moc~+Z>4G#Ft%q)_Dm;`o6j=plGz@8L5dRv_X$1f_tJWi-7Eo?G(1A&O!90{*EK?!!-W}{Yw+CYrzu(hKgax8Rg zJmStkEwmhaqcX@c$uHmCTMYad>tuYw#6tkJ zRy_&Gzf<<%S`IblCiE=zFzYxT3Wy6!pY<&yTIM?%C4tQ!jHSi(Zm5ZOPO<{g0PCpA zztL?uxMg!K**PGyN-Gb_>AO5yLJFUiUyN}?&t+V__ zS8;!gEu7&JOJyoU<;c|umD)g`dhmmcxb_Mu#PSK&pYKMuM(Jq7g*}K@EHK84n3dIbXsPxs?(vQ&=-4{ z#t(U`K63cGdAZV@Kxt%Alv>{<<2QSFC9t#M0I(c(wEh|7XjvhWcX&`COkK4;7d6nhGsQk1ne1lgqCg@qXr75pudR41W7_7&b)e#I~a3$s> zR9O*Okp<8SN%C+<=4dlZ-TM7-xeD)HpN;DXE_R$$ZN z1%1jD`lj#JsF;ByF|ex#o3C+`P%c&inKT4Iq>fJ!lXdCdr+P zf&2$4o%3pavA_IIW9fE{qK1N*Z?KhV-)R!327bKi(z%a3UYl^uN_ z1Jza%s3ucUm8?xh|2eviXE>;?Z$2+DybN%_fV;Dp3NTX@;CLI#_hm$WxqdGq_F%+1 z9cd`@Ks=B@_3fVvo8P9#_Fm$Ij<{Z^N6hny++yp!KS`};$)Pq)w-IgmVRf?_##K_J z{*OpG$FAAta~sZdq@d*WYqlk4?twi&2vm zXIv<(&TsA498rJXIp0>e>-|Y?9DsGr={9%#dvxi_!Mp@T+x45Dn|2bFr$*37!VYfF_>#k2&+|y6_%xJ0=#XC-VM`1^ zE{+_cOa!?`Zi`S#%L^K$mqysk(}~8h9aa{4&;Z zV9{FR#v(BlH0|CH*I?b}jCv0sgr^l59ERDf$z(F!U2>V6-W$yq*jJX95zfA}xG@88 zu3pui)$_SR3NEC4WKUDXElJASn9`XNVUAw}dQu1phu~h~;Ad}{`F4au^Z7}`f7s}~ zpyK7&qsU#dkx`u5#!t6Oz3mWFX`P$LzeEZq%7u zwd022l?M}rn7e2;3{hQR9O6ND9m^u%3lez=(-JM2CZ~eIo zO^Ri6lO2Ef6XFdMK9A$phgaj%q#LG1POY`_F&^~vRfL)JmT2}5B0&S;M_lz(g+B=I z^wYE=(^!J$0Fo@9v(0+gM#b(JW1?*usUs}Bqbj0Hs1vK#GprchbZ&$Vk5A3Z zO#Ew64_5_)Z}CUIdD8(Rg+dHzAhCuN72+&^6rGxnp!(5_N>t25_k`@VF^U(i>1K#2 zhG;72u*C??ek&~YV9%!$Id;m{rm2#@d+R3n5x3(0+jQ47SYFtvyS7g0RItSAjGc)P4d`D+S-v_#-KD@$UzqR;ACoev7K5yW zqnR^VARPo8hIe{VFykPJH*B1yu(lGG|nm_AW{^YyKOx-zw5ZMICV8lUdn z3jD^z56eep0Fyl@r#)q{`mGcW26}uDoSGT6-E5nto%5_sacUj$e=6co~0FKj&1SJ! zHc9@c5G%a-@bb-$8}~&@i>-mF)3B)FdkMY;>B&wNsuMfwgwFV7mPYR+%s%TDnE=0m znN{2(wX^;GGRn))dkhW?_Hyvp`K(KscUpGi+1@sPg%6n0exE)w|JPd;oq6OZU7}Ke zzE6o<%w1kfTDUOw!7;tbVGFDxs~pjF*Ed3)d5C?svyN|16>VYf&N&zGPSNa(rHTEu zZtb{BunxxaQ4*R1SV8Irqt ze!amp0%8;pmtz-r>HBQ?y$M`NncG8(Gtu+cSN3iZrj+vh z02b|m30Z;EqBi08b5qP(S^JyK!wiaWq8R&+(>Mz!#aAP*xh9j@Mas;Q6Kd#C%HCi|_% zOj`~$o9r0%t5GA^C0Y+Dh3THJlMZ{T6ej?Lz5{a_IL(IE#_sZ~ml@OSH_NYbDyUJq zPQ>KqT6|IxweA#TiNy3r4tsx=mN;o6rH8$fIkIAaUJ8%!_e7UY=5!6X9!?N%^%(X$PLLFOJXpb*3IoJk`w^M1{6(7`_HXD`( zUd>Fgx>gS0dvZWRos%h!YC{H~K2s{8f$*~F(d2L$WVtJua#X|iCZ8W&*U9SuQ~LjV zx}m}TmpWSSSTFA%QlO7K;+quQYpTMoCe#jGGscJKbo6uW7$U2ZAF#6xB2ARP(!^Yh z1*g@W)?HOzAQU#S^BcX?Q@Bo;z9$y^86`TYNsDJ;MMS4IFCCj_xAEHm-b)- zEgN`=_{wAGR61u^B~C3QTQ7RN3L^aZ-c+D6Y48XuzTdMg#I{9`qUc-UtJR=nseNn1 zcK2Pr`P}!7&kf|dVKjh0Yz+}6vy=Z8Qnx{!4ymIm6^E)y4Z9Aa5y)k71e}%Ta;6rs zRM615jWE`?|Mh&*mDVKfV@BS%G=-Wu5kVftonEoSjb# zWwnv%I}=I~HYZn~PoPsQ&qC_3*=MbupMeos+58}n3)$9?8Vo|e7vCteWkYW?U^)aG z+$se`Rq+b3snBRJ&3*m9=Lg&uf8~GhTx@r?C()G7{_bX9_)Eo;zBrv`o;jx863wqx zx$MJ?%H||Qpc{o<+eZG_AQGjdSd|)u!eI;g#OgZm{fJLfy>>$y zvie*}c}{~7WVxnsqr%a%nEz>ih$uLl!gQnNz)fwXI5&eQg`QW0QNv?bt62q^#1P>| z?o-W5kJo^O`QV>x<1=2}HYgqq9b4*7qmasxox?$AA@o%9-Y)L9+QXrKcy~{^=3Lof z-KB<4rctie9A$3g!gTOYHYwc-R%z(e`lbUq#{V*}GQ*3aZ0o3Czk6}PFeh)q^RM)C z8JSYZ74Kv4s1im*T{clOFKeX~+uB+)070JXnx`A-v%@g*#M0x#@m>v1zA=@bb7i=# z$F{>=dqNWhO>Hx-6}U{maOrB7xh;vFR=@6>t#-W6DhlxU+>u;BtL^iUO7o_!CHaB~ zMHIJ%LTyF)1y=lOlt7yzuXJ8Asy7C-~iw9;g%DSR5D8 z-1)B`FD3UgTz-s^-=A#s@>F(1{FFG!}h@3RA#`CkVmS9pEowXw?8FrjiK1Zbv=1^U#Fw$QeVN~T^j8M>A?HA zXS(MZwwl)xo<<~VG0>_K7$YJQ6R6VqCdHC?U~6Fey@^$u(4_bx+cAY<-vcV*O2<8F z$BQV1|E6~^pLLS_CG(Td1x7UxRwwm^6uq1Lr|A8o@*=qJ%^OKps)000N#Ph;N!Gxx ztW-+RL)4?5hoyfWh#Q2Iy<O&eR)b!ppTBRvZa3yut|j4iiHwtZ@A zv<~+MX^J7k#L5|C7~^Q8MiK#8c0S83m-B<_bjd~d7JK`JDfOH*mNfgl2g`FVUiY={ zSiURtMpL8bCysQEd}d%eYW+>qPd(1rKDzpJ=*Na2PjY5|x05EYb+Eg$W=o>y>b)A+ zf_n|C`9m#G{adQ;ytA`uu4IiuNk{Fw+4|Kbg7U;)sU=>PjzuPo9^W|Iz}6mhGC2zz%xPm@Cf zke;RX`0#xHV??=Jg~uL!G%kHca3W;ZEpDmT`^Is$Pliw}N(+h?vw$s2?jerT6gFSf|vO(Z-m*Z$_+GpQE%q%ddhmLyWRk_kbH zrEb(`27B}!7OgxW*R*iNlzEK_tS_kkSV&4p<4$ANhL05|47TxK0(U3ljW_ZzdK4p7 z1vSp?to^JqJj=-YJC>0R9qkejSXRp7)tVW4RTe(@0~xOFGF_cLni3?(bNjPEZ<2Hxaz`3gz8xPJ75Am8(z%| zCzJ2>6-!e>!oJ2Tt;6X*8c$-QDs_y#-V4WZq;S=(*|bUE=p6G2K!%533RQU^eWD;P zed2rVN45Mfj3h>SlSidouG;giPyLees+rL6_oS&6)PH25W<_jTyQ1bG$)~ zT@o4u(?l@vl0jQ|PQJ*}so|m{xN!U-%l2yt9GDsF07NZ~4D?a=Qz~*PP+30m_h>ad za75;YK&sh+uadXRw+s7>6Iv7NNQ@G~tb&RnL6gmq=Y+p4V09Sjap^I|hcC^LnQ=KG zxfaq&NuD>Fz6~1}=#JXUZ-(K{-VQtKGZ`=n{#fP0Wh`&kN_2s7)aHZ%hOoi|LnaY` zQgg2`QnTSO002yH1HXp1#=Z?wlzt6^Jvu?n>JJf3?m`IXJKOZEiJw|4n5arwR+$T2#EYoHCt)kt# z`aNiNPdRW@Xlm~!Ikxu=OkVE30r(7k378wH|MV7%xF2Ut5$ej86t4d%JkM)|=6lAx zt{MEV?tE+-aE|7{G^Fm?gzaC`-oBtjCstl=RF%1B47GARO6;`D2FJFhgb zxem!P8a6)uT-(Ie-eXlP`TR!X201y*tua1T3 z^t45Y=B6cHjNhMU<7dg*(HQixqB#1cC@J?jwKi+y?~HD*r%Hj|c#B+q*mz;TQZSDo z?@i(8dQpqQ88RcMGN3pp#1`7RuKNJ*)it)zJH=g}+L~yh2jmLTXRWej1MKl8e}YWa zn&^)3Zp+MpG%J{H9?7O|E-Znnj3 zLod>}Qmir9R(jxiC`#7EaM7uK!y_UM^gbhe=P+nYELnAYQvlm9sYG z65_xF*SKygO@tUT1s377t$PhkSDe5A5K~|lyeScOQ9zH`8c$OC7tQr*Z_};?v=B!D zLKs!?ebmrCsN%cb*Go@=`i#Oft-HjGy=aH)HdZizk5(9+k=#{}TE? z=dEL3|5Z_umw4kv9m)TyBgW6v@`%X~b39q0qZ32hbEhz-44+-KCS{iGaFYGJP1G_s-jm$-{jlyv?ud$KsA@_w#0r?Zn6>Yz_~OnvFN?bx5U^U z81jmHdw~$LO&8KrcV&y-FF9*UKqIe;%hUvQesVO+@#hl?H7jnU=R1EvT(HN0U}k6} zGo1SCT9GO=vrwb;;f~cY|4f*eKtw?Fhp;CBSpHMlKhQL-<^*fqxJ^F*HSmY)QIveT zPacx|@|Xx8j~{su)2?4R`km`6p`auo9SUyd5L7i#z{cu9iTLrD^A`Hjp`6nT!!%?TrNH>8!H6D-N z0lJ|4sA{?Oamu-9fa+=2W7@URx23+|7!TLYivZolOoGSk*?uJ&d)AS+n)eg$C- zcCpMGf5v1Ufr=jo`gmElEHISNhwI~%q&qF7Z+6|8$|S}3-yN=9W%7D#bh|D*HQY0S z-hkUJ`GCj5|6$KdGVg^wTYi7Bi2Z-qgYXxNUm;VbEe$G9>#xazTbC!03EEO%iG8Sv zFTx@+g2hu{s5vpqaof)KR}kd{h;3Fzy<23`&jlGhP$BSmw{;M;><3J$NPyzwFPuQ7Y*<8}REp7wAQu)r-*=ZJeizR_MLM)&dt@Zz3O|GKBbXZHm6 z9J;0ORWOpH99mEcb0wNfAS%(c1k_je`PA6DIt`iqqWYOIA+mg`Db|+?DW|=He2ROB zotkb$tLqt2dxN=gnIP^k(;X6C$QHG=(DqKJ|4a}%LJ~YKbUYQ7^NenAdpQ0a)c~1V zEeIhrA9}t|f717G^GR@$8IH%*ykGp64`_X^lw6|bKeUp+(CYnM^vS{fzcJS@|8f9? zzXvc!Q))7s&0F3P9C><`BIYw)A8Lux-n!U#d9*?8=hJ3;E#Ep%vs6!LEwVfEw3~c0 zVS5!_^7JJ0(7|Ryti#wE0qxQ;vkp|CiF!#9~1{W6k z**7?}|Md;<&sGTSS!>D$p#O@4KxEdQvq~Ld?)FKhI3QE`HSN_=gw6PkvL%!ab!({o zL$X_qF%}nQY(S9-;BmL3)!48H%^h(S~>30BpTOjM)z3Ul_8#n8b&d zTI-nqO>I=>i=D*y7q&n(FPd7XHi6lLua7X~EHe0h=01(eGYW=i$P$<6?CEbk#_&&K_hOE-V_(NYL1$|h}u{n zc|QCPpad^Me|mAMivLAQ+Y3gq{$W&zOa4(;-P-gfc^d4!3KGfpWmA453vGA|e5KUh z59$5WA%U=s5*wmS*2qwf?LK4O0bKXmR;7r7QTyp4N>MgCn|9}VcKi3|5sb_bdrIMN z9Vc|weviY88<}?R-{5wrN-j}=bVZMzE4m38wPO_G3>wY-bZ2DM`9b15pXV?5Bww^g z=)Xw&EBfc+YRmsQK{T~K4(?S3zuo@HsbN3SNy4{q*9ysZZMKP?Tk=;&PhhSvjLG`@ zPx@9}W_fnuP*2NRW?j1?rW>*pS2`a(_ji`HQ2f#l8BT1<3Pr-!j2*c|)P#;Uy`cXt=Y8bhqBqm7k) zK~2Bb>Enz=d1*Il;t^UKYGQLdQIidZ&KCoD+|*#znetom+?C6?p^ zN$&Y0JbRut)V~O(d_nNqbAQg|all`5i`#+C zl!Nx-C_ySK96C!VCmpVh#kW8CX5Pr?tIz6)^L!XPFHAn8f-N`U1aITga&S6?!|~>( z&r&h$lH%{#!UmgBU#EGp@upxfuI6NXUr#@tPkJ0vigknS-|e zLiV3Y_%E{mD&E>N@psq%LP*$UE#%*X!6@k~juRi>4$vAfUX(uV;_E#u4!yZNN{}PX zo2<31*FY1@TT2)C8MiUko%5KTY6IrS6twI`-50vkp$7z2w1jW)xL?_YKJi<*gaFshsGbW zes(x7-HNDoXM;>*$=Vq`-|&f=^4~@0AH$z1Pl5fXlYfE#azIMoe=;DK@o|Cw=!P28 zzCC#>Vt)lj&`BT5xxiWBJ%<1w9 zuO$_c3!2Y^T8HiOIN42V@R+@SS&MQ3k2w$e-4u?H;PEcc>3J_tDD&gTk*}SOr@M*d zR;6=HqOHRqBMbe^#`LCdZCFAY>=n8p4~>qvp0tX6v;FhE3;eiCYFu}Ru1>|nyE3FS zZ8IGatsu}}|9Z#(G1DRe+Ts6%^Ma}J|6uCHg}Xf~$K!#X_BT-$Tw&k4z~kQs?iR2` zaP)zKcFnpMCid}P8t`r$P#Zk|~# zz;~-7pIbPrYU4LGY|d~}h_2D`q%aLqhQU=0P?o5=f(rM{C^r3xoO15+ZaeS%BQu3CVY+`1G(o0V~hek~FS z4O3$9+D70bjjzrf635~vzZ3&nnoPkU!PT!jJ2jAsoIhU))X-Rj}G8sNV;lIKHk8n?$YPby^@*S%<#n zE&9nh<51~zi3mt<3D1dhfb6vMIx_NT%jO(|4 zLA`TG4Do1-#)xQfHza&t44`n^{$QkX}b9iwZ4haV=u9#=5krC#;41pep6a=0%mA5179Hd)tiya)#vt0 zMApau{&D%^J`m)6(H$N8Lyq^3=f>Vs&lXx0`Ep`e^LVdukJE7WfC@c7;U|42ilX3` z6PFnoWApOuNX`XEg%Z!CjdcxO@pQ_o1Ts1jnmWj6p3^Em_s5TV^x~^TR8uNMvcF2x zTF$r7HI?7kb7a-MqR|g3wSc|Z|Dx&sdz(%X)glj8bM{=1O#-Q1#j14LAKIlTd5c1w zj!|dB_~-+f3Y|K5i&hgVgI0s#FfF_>*srcfkz%VXBhh7b{ziCfk}n1NXVK-g40&W4 zQq{m0KwWmaF_0_6qerU?k0$f{9>aO4TX2*l|8-r$2WXhF`WjLC7^iRao0kc#jSblO zF08?SbkFYYT-FD_LAoNS_rIrV&r_)oLw2g2+CObh2RH6qbKm0imU#6m37%{64pn2< zb0W+Wahqc_JQ4dajIvowbAA8FH+5PuTe%kV8ItG=3{HAG4I1a^Jbx{4t83e(mqIG(SCY zGHvU8fhDGg@)ip|IW&=yWy%}tscdrk?0?@1?!~*+sRxS3+nry#U&QWPeV&k0ECt72n>(JO4-q3snlz7!HXrBouO!P>my#fZv7J z$zqqR<<)F573W60N|iX%Bg|ir{)gh2=wh%iJe3<{*%+18p|Y_$-Zd+uLH?uI!!a+R zFFiEHDpZ-#8BM-b9Nk%YLstv?u9|MUoYqZ@$OFxiK^uMNSsuPAl;Wg3cN)V^xKFgz( zc-B75hg%-t+Ok-FIkb1FH2UUo_SOAC)xV~}qBzC+oy8ZGA;rdeQXT25g)Z)bBmz@) zpgpSfwZMB67Thu!6VLtO^3+TrD@3)<|q7m<_l_%Mt5nu?nz9Sa<;^=3E!H6kt z8|Aw1VY@vA0&_%DF5CQrGLw7`>eW8S?)k!{RkpDLQi%4+T~RS1;EESEwxV-*T^r`{VAn<$7zJEJ{PD;;+raMM$C_mvd~VhD&bQED&yYK3GVvjeC)8 zqV(N~Q33O~Ny~YW>iRHWV2&a!k49sLs+rve>{3=qXAQ*sRHg_&`5QgD)0#x#)+l^9*#xhClH2>2UM|?gFsjmZ4#8#DW9(tj|_ijei1Pp^c6D&{P(l)ib2`ZGNFy-DXmy zbDo@XjD;%^-YhK3uQ;RlC;ksQ4hJr^n^lkWYoe)VmKjCA2%`J=ZP>J}$1KBH8ZY;z zoV5-24fYcsY(Jg+y3j;tMEnu_nEQCj&G<<&Jc)(Bn2VsE{J9!HP39n|-F9^|zF_o7 zo@)3RWe=@kI(kuV0Q7VUefL>#;v)8}T zf2W$3nH#G>YW8ZT=lQi$Kmow@+^g44M{_K)8sSs9zB_a1m7UN?Jh!dYGg%GO?9A$C zmIRZnJZIfSJX$)wv`jQ4mf6u0Vi(@LeVxAp4N&&(qGm3c5m!$`vlkf%auXlq8YJ0m z7hOs6@Y;jScU+qW3`}*7EKcZBe#VHAN$nSxtdsmyJ6qclp9796`Hba^2?)~o$P?!A z2$DGm!uL+)a!q^t>A52fK8+&0cbmbCC;0qMI&O7#6EU#KA2Qu{$fcx-%aG;!RFA@P z?&kX4(ltBrgh=u{H&_)q`=H)$a$TB%lB)DW|0hOFl054J28GY8iu5>^<6?QyXWqQ6 zxAmbazO-5-Ob>$!))MpKAJ*X4Q>^`03_cyd@R&8kHE@eMKM}OGNpoq7 z_wl?xUl^o}2vk=O3~9Jz;?}sj{5a7MhA0WDIH`RLj`L27D|9*jE=pi87A_E5=6D!l zyhx2|P$j>8Y2k;E-mU(yCT|vfrQ81dv?cS(IiexDaphVz zbViz?8CB}A!CvmG@5AF2nUfrX3?# zt&{o5-C9~6j&@hTUp=$$qL)CRPHSSS-P&_Qr=P_q)a$#h8QZSW4w?&>JK(|oR|7`iKfcqT(|QBlR3mGVyarxG_P%eUzMh5xRRf|Buzht)H&a2y?I@? z!KV?GiDQNgT}Cyyds7Wr%C|NSo;tB9T*WHJJJ?$D?{B{0R73$bwwxTa=s%&xDehfh zk2{qbRUF1Dv|nE7&W$NQ_k7~WH0O1O)9>TamOlme=+?j=PV({&bw`lsKJM*w8DjaRHkhAo=*#vwmIwcX5~b*o$Hly+}q8qjk^J*Y;6ruGN!*_`4N? zv2L=}Ev*M#YzA!i*XPe>L`KDy=ywct9*v?XcK zl!RSuOPZ!K>GqcXI`E#r7ZC*jv>v87RtY$()T`fQjrK=l6H$Nr)XDrbbcs?!fLE1b zVA708lgi)I@5;GTlZ4XXp0}+sfM_apd4sW<6HXOv7vVv2gFX|AR)q%8@CUVT$;>|> zZ}mS`^u@K6!}rYcocUHO3WY9M*V|fomyL&!FazSpd&)6M;y!^3 z98vY?ef{>&irF2bD`Qwk$C=WDaU=ju;jL9&u6^5v8J@~sGka6w#UrTw!PYCo>&-=!Gq<RT6*B*ndx^+PHrYq5+^}3D2Wx)zZ4HI(Fp-`PSE2S<=VBI6KZfmqYKw z>s@cwer05eUA~RHet|MVQ*Ys?tDg7FD4~ru9YjH*cGmG>ZFGNwY*gfBuA)$UCUY|@ z0~PS&cAVGyweK?!_t(q}-Rqd7;O2V_UpMYI>7MN}@Su8(hc3F=|6xsp@BUjDeEUSkxv2k1*3)-8_|_zs zMC7Gc*e(ab4@}|~3rdoqf32pNpWR&}Pmp0fHJHeOypIZ9bYRLUtamA{mtj7%5Gk#i zS3nYTaM1#4{Kcw`$;+SrCX))eN062-9T^lngyt!;|c@`5@tQ(g_BG#&@ z>LImcwTj5~xat-FIZlRk^hbT&en}zDb7x0<5zOJ|)5t3EpS4RNu1ag2HAy0)t&@y} z3h)n&BBP5&C@KVE@YB4pFF|OfH$28Sj?f5(ibD7(Zm^Ti=w~=*JM`i41)=^ugR8^d@GoF8kTOgt}Y@PbfRg(AGa%ea6~=iU{=^B{UkoU=>r zPehPvPGLeQsk0+B594={{cRwDSn}YE=(=c@Q(5j>XUAq#a7{cKsd1Z|aPJEge*3o- zmt))S9C^`htLCgy`5ZJH@Be)L!%sW!kaDpXMnbm>_|+h3!VWTLyQ(r^8FGVDpR#W8 z4Y>z@vu@b^?U)L$zaUQIclZ(R{J6S6kvqo$qMNemk`!i_?XpLtevEzHElI`mnn;ZT zX5ev>OMSJl?}9;hb9rOmbTvdV z{ATsu;fkY(pg9P7<7uC^`Zui^rFX(p>;fm2&@d83p^B#JvZ{*P#MY6@>oRQ~NjQVQ z)xf8M#M?2rr_%F3a1OU=(#IFz8`xjQezHiuj*3CSH1cvUN2xSI`~S*=IM3T&-waB` zdgXaEsPlsYAR`I-94!%rq;j}babGAvKr@6C+&>{t023gODgm*?t1r-eE@sJE2Yd-z zf%2bk!sbB60$Hp9_Stxfqo@0fLj<+IYjIwk>Lem1EG+9wxiI(40?GVD9FP+WnxDu? zG9yCU-|kiNsHlyM$l>jE*=tvkAo9d_V`6u$vuf&`nQl$Zu{%Zrz--+!$u%RSF9lSR%V*~I`Rdq10j)#{Ot&X@w)0F=0)BT-)rFr^xdF1+*oz77eZiy@ zjkDEh4f)EolirTj1Tx+G#wdl_Xj%I}6L`RX9+d-NYK57^-wQwb(=b(kfN81Lmy(%> zX<rel^vqY)oaN1EoN!0%_F!$x=)nFw4yJ%Raxe|L;7KYE`ye^`- zHeHeW`ELo^@tU{-)BB9t;?zETP>X9bN;tw2t^Ge{wRmDy9|^pFA$74N_+D&dNs#mE zr&et7#2?e|y=8uUW3MelT;9%7A>tI<;se;0CRS6T=~@3^#bXsJUnP&eW~-+ z$zx~km9(qG%_cICZmAX>RFp~_&lkRhib_vbqdTVo3KvaozfS%wjpDlXkc!yHTU)-` z7@!M;lB6BahH|RkdXE+`L^0`PVNf%rBJ0M9&I9Dm+*)PmD%h>VEVLeb_U4llT!y&V}h4or&}Agdq%MAu7YH8%$yi6 z28?!J(M(AK{XUL(P*|pyPxx^Bm9kI!S&%TEGEfe>pW4Gnq~1iT{MotUDrawAZL0oy z0Ds!NA)(W#^V%#wMtCmrT#CZtBc{&f@_KowyOY+-g6;H_{0~4bAuDCN#SoWKa(Sm) z1u@rIP9ywL;IL|^Aea@7SQ1RHy+ph}rC)wbG-N%-p?Y&Hs|X`+TW>+jSEne zmWOU<5w1A_OcstYU03zD-?(}}#;6!eO{KaG7 zAJRnC=)j4I!oNQ#C~@8dd^Z)w%_^kS%1}lD><7{T*o(pMaH~N9IyM3SyEv>r07YJI zBe5FWu!dO;wmi%}H%NCIrvK?H!CodSTEL-DJq1yuJ>7-dZ$Ut z|6aclksy`-bvV4R}bwix&(#o4vLsfzkN$?~< zH#>2^NZ@wv*s@*E#YA#kzTC#z)6FG*k{Tp@0rL|Ry`opZMlAK=*2pGyy}rQuAz%|} z)%8(PNnD#;7h}FcuE}+I|DGJD|{^P0OEYV&ICf}JpjP)tqzhQb+(hk zbYjHX&aTFIPjXbVVG%YJL;oRqGW5aGUeuQspe-^}3wwlnz85zV865ZLKvx3*O&17& zhQd4&f~bsIV7NvVtSp>7jP^|xS*d4(h+mnE^I$B(y!Jr9yPIu+v*zLK)qMK-^pTi3 z)Fa}fM<4e?mh@=!Hju6+=a~oX_9_Ru{;MJWv4Oue=*NTnP??AX2%Q&?7j!nDP0Rbc z3_w#@^OpM#X1dz>aeJGMy+edDeRBremp@Fe@1Bu3IHRZpstupHf=@@(zA2&9RD`9i zcHa5@`(%--Ov9|{aT#{`Tm6!8{}qBjc&E8Y50WBpi!JLLtBM@_@Ouo z7|Bj$3TB`|h9)CkT=iS{k090Hgg9{ojg+9!xQ9f!53u%J6D|}-Ujt1#K{TJ(B2`Lg zQOmB`>^COF%!Rf4Z@$g>Nukm{LDpV0=MY%&*&)cDU;XZKzT)!}-k*QA0@&%LDUd%|Eyo9O z!X>1L>xGu`aWUWmQ3&dCuf@8W&Irz%zF~?qFLm&3jMy;NE+%S1BaaJo!L9e;^Ge}2 zy!vsb2F>I3#bXRPI+OSh3(+uGut`rb*9(s)Mp7Vok^oOT(iOrVU_XOx-~Ir)%{Lif zU|h8K(p)(vW>)_o_r%jtXv9ZTkOPOQ%aa0sz6j0&z2~2!vw^O2!RT+_#2>rQ!?L^6 z=`^adk8jdBPp)kFFVy#I4idTThfc1)Cc7F9Dy%jg?C==7k|Y;uh^_YX6=Z##M2s@K zM*y)+!u`P{;dNVz9S|9#f^bks!QjM8wfUMAKkSfrs>MB=%4FsfZIK%Jr z)w8!OEH01x#vpp8-MdQ@^EkFFlD6FMF0?>4oVBK1N1&q-vW1L&Hlc+YC6>WVjZK_g z9FtUoZ?zxJ1BD^nJ26!Xm7^^CC9b8{%LTyyqNH2CM|n`rt@`w81vgZip7sYrEKSrs zW1(oRts9{VW*-dP-rGCpE-&apz7EG+o+s2i`RGfnXr=rw&2o$Vxhv~66x=4oeuZAO zdtp}liFVeDvse9Omj0N)AtB>)oh3mPX(eBNE6;~*8v9+QfUDp2GfkiGYV(U7z$QN! zw6RUo$}{K<+$muyF(>fL^~&{YT*`xN?o+_l@A@cuMQ8%djXYM&lh2bXJhyS7+Ff4P z2bKPCXuPzRn&D&S-}w7KMA666{0Pc-sdlJT?ATF||6pbLh7KDnq5DCbjw;rjyGHYu z^6%gh|Ez>7L`Zf98k|B?`DrO2l#ePUtzQ>m<>TCN+%zg~H7pv2uA$;Iszw*4Hm;PZ zcWooq1?S4K;bNAK4olv5%??yic5uy;%|3s?y1MXpe(Aa-mi6p$@XQO2$Lo87hR|*} zegbTt$63!Uk7MOnN0zjNi7!Wf`%;b3%+b%L&e<2ZAG80E-+2*Zl15 z7H4|3;9M_X=r5w@P_qq-mBN?w32);@B$X0J+|BhwV;p{htqAWVU0BjSfMMzX-+HVobJ~tO8ppda7-Mb z>=PTJV#Mu!eEk;SWt=L)v|sp=(C=Nb6eoDUveBYRLz16oo=ec7_U!RVPUwVznihw$ z3Ads-4~#rvps#)-=EFdr0WShiz6%3R7L__KMwJ36#IZ#a*=<;poekDrBv~X8Xk%Z% z7{E3_>~}O;+`4PFqURSg0ol9zfc-3mihRK!3x8+`8!WWW6lowX8*aeSnvO*!_e1`b zCRjM!21|#VQWBnJYMh~cfOQ9sWy(*bMYAtm(0+>|NmwJuXJ-DWJr5(*JY|7OQz$&C zrT#n5VU`@+)0ADo|3;7X3B9+^fiWuPzgqu(8!XYQ^)}Wq!dAY7n$Y?n|7OQ-=+4do zjD5ZVI{VYpv=wb0gR>8B_{DDkQj{_QJ?Aw65vKTn1q6M;^wvR8lyW%eGh}#ZTV4du z?VC*S;2y9O0C-&}said<%4rZ&!4FKPg6OK_k!{4CeS$x#0kep2GV}OXc3$g}7~q;= z>q(zteUgQF8Tagrhi*4b((_a-;!Oo+DGFqdVI9Na#5;0H#kdIqko!-kS|;Xzl<5q_WoP12zw{qRoSnc$`|tf39v56}ie zEBZKEUC@~`n@$ae?V)p?As)<)CaxvOB2*AchIMPA2=h2k&JPKgY6A*}KOj49i4=u@ z9BV`R{c)^Zvg#o{8Ro6YxIpX{38l}Rnj44mkigKOQ)S8+iIVGx8>&XICny3;o{gdGeLo zODB~K3OD$OrdwWs@sA_+cS3sS`WN4-vR8;VJWGGL+hbk6(y9IRkP+tQQh&*r4k5H4 zG{O%a>`)*hTEP~q@{mg1qm7vMurj&8t*1GjGj~xDZqPT1(EfT(frI zM=-s?Z6YdzUsDlhG@X4u`g`etD5d+_C@HC$asNZiQOyWU5I@LPra^rW|IjTA+HdU!UE zS1Cpp+$cmZ7PH53n5e#wvlY$#{$1>Oj! zsOG4@aq#CfFTbr!oiG6VU(nO;@8V9Ho9(U>90WrR7luH|GcAj~kb!GUCUjsIi#>w| zt{9>-jmhwf0(j^%bONT%7R?Y>i40hKbXF6N^5J30uE7WS^^kjh9}Hw0mFy_Y>C35j9ONS`MT=g3;C537-m?isv9+2!edzy%^mDTcsF<(}^wXr2vJU2?J{xYao^ z&EGlZe8tm+ZDdm9%4q+`+@Dy1=kbb#wji?ROA?OqG=h8SagC*)qOLQ2w1b8pqTm)$=2z?!>s12 zInKKhYupT3z&3_QZk(nT=3LBoCbTv%(++Pr*LTM60DpeX67zxI_5lr~JaEZkIkm;0 z(By{`uC-C44RJBwYb@h-_62`8cF490z4c71UuDN_pko@nKRD^MdB9EFx!X01LbzuG zSxv$s5D>Hxn~e@I4QuY~Th*1_9XBz9wEsk;^0*l3FveBOuKsmXu#Y38o_~D0bL78w zPVjW+FCK@|vqt^n&3rJ*BK;g22HxBN3dCuxyUf#=wmnS0N-_`x$X?1@>19)WWo8N!?>y9TQp!&xMmS*Go<``J)9T+c0 z!F4VNyyp0#iL$ln3|fM<>6kJPOy})m%|Mg!e1?(Y0By`0CfrHvLl>Y#-=xMesO#}a z>TkLdWK8JouANd`A5xmMfFHYdk|E|9Ypp+FQhI+%A{c}hAIfj+006T{$LzEL%whur ztJqm?nY`Axz(D zM1F!5nJ30rNAKq!(j+hZk#`@ekyPbY!!Z7b)t`(@_OEgML;iS=KfL}WSu!qJ!&$STc(G>dy#LHTd;-jUe?#UiQ{S(8 zA@beXCkW~=ta%s4h%f2+O=^5tld1C?go(fu!4_w^9xlzZLDMts*{=WZ(`uyuJbFQ> z;Q!jo|BG3nTvpI5o!(;w@jy~Nky1oH@dfn<3{mD!+>Q+1M_AAosjzeqB%A-zA#F=B zUsL%mP_oHzzE_ua$#vFOsg>X)yS}lPA@rxe0H1r%JN??Q*E{;CFs)ulYy1+o?w;QM zWs|1!2tt!4`?v_NmwoFES+{^Mx!A1RP*Xjrc(cwy=K%Gfhnt4{Ltx8rGanS*KS2nuOXkI59ZWew;>} z92(Hj1nF_mWqpXOXnv`fIOP3;=$8#*;!;z$3%B#5a=0k1v~R<$6J^46$wW;=Z5W2{ zcXWqiQ3?CV0{mX@NHCHPsJ6WX%XIeO_I)Scy*U1$vZB?A5Nn(E;`SXLz{hV z5b=^FI0+NY2R!dwPQLSRI0Q-iJMa9qlMedJ5eQG3p5jSm2>e6#f5pi2CuaNyX%wHr ztNIqcgrNGE60IMu9f5P%gZI%^C@_t`bn+a^YYx#4uszE*RR$c5NLXHY=UI6_dU1-Af1NDZ_x-p zP=GAKXgZ*s9Fj>est$;70gT!YY_JK|*=*}qBEQvB4rXD*^~eN7%n$VAer$d80r+y* z{9FttwImatPwzm1bFeO{s4+I`Jnqz!J zmn<U?t*?ssl@qMP&YJTiefB!%e7p?WZhg$dxair^B_T1~%7umGD6SeGr@L6^;n zpv^)!xz*zK40uE^P`{b)OaU|BnSEwzD~OH08rV197b_MBrXH?ynb3pS+UU(pKq3r* zDdv5^an~5>r#2V1=gvs;MEX+Uiyi;1ranEHOfcL(Ci5h^K0gJ6w*MjFm!U6qS|Ilr ze1pXMLXE%PrU^mYv`L>?$DgpjXjWTj9qHGgQ6#;0qBjCi{4LZ=3P86 z^sgVl3^;n>vZ(yV29}Z5vw<_SPnq*`;PqEkf>IttZyNjgHyQ1H9iG`EPr}-_PJs65Dv?vSL_BTCO`vV@OHQ9M3B z^K*Ay`c~lX&!zh{`8VB?xH*z9%>sJgEez7dwi|!F1)dpF`Hgw`M}S}~c(i?ox^@;9 z28&;mV4FermRkB**J)UAWl$LtKC>?OISy-@;Xf>j3}PY+E3VtVnd{-@Ki%c=i~ zq_h9q>i$`Oum8*H_7-I-zqqh^!#UGP=4CA&I(#sW5vMewPaJ$qyujLrr%E?~??DbL z10r5rzTm*oEB^>Zu8;Pu4)i^pqxmUFV_$@X2DWa4K3XU{pAO;HIbfBk5%nXm6_P(* z{AXH>9KRZW%~pd5KV3H;KM;Dne0#hw5>6Klj$jUMxA3Xdic}(x$gJvMJQ>E`;?rbD z{pU}?B-&FdV78g$QBZXtJrOIO3+}Sqfba`}i>iQ)2|tRollnJzY>?Wlz1^(nPDUHY zom|2 zqf2`JfFr3}ngr7GMEHS3kRWF@eer9Zy!m%%aI-yL5uHrW&f@iK5wtAq0tWfiK1h1H zA;X^M$dgS>k|Mhm&!b#{v^m8sv=D54>-qa%m6W22PwpG&L=);B>@lTg>dYdd-6bC` zwhl`h;dRT}pT+0wNHun4pA#) za=%55gy6EBE=+%h+diSz{kPFM^ZTu7giz&4lmPXuKkP?I!noaq2Jf~G*)KVjL;EkY zNxZiOB1VV4gt*=e3MVOlKVjRUNaKjjIkqhKXa${&ybrJ&Xh4HwE>z}es+pv!j)4r7 z1+hx3pL$hvgRO58)9KyCPnkd6HX6_#=p~&DQe24keg~gJU zLO;jNBF^gwasQ5ju4+>KnKB@L`BjtQt-|hRLMFoTw})(@Krc=^wr9HuYfj~q=5z4D zDjtd0wR3MHifP1pCL~CkBdI?PaS3PFDSl|tX7J5tkm#Hd1C@r-4Yn{z{%lh18hW`# z?HpVZql}`Vk~u%qczwh}mm^O)=_$!K=pps7i>VXeY_>K_}M(+;cK%FqQRObE|GF)Mg~&D~2SS~|*t5LW`T z_qjgplW+X?#A;MTPvHXTlXwuPvGh}FXl`v<)p>AQ84mfQlh<-^iE{L|UR*9MTT*G}mn!)^f(|9_!HxNK;_G9g z+ZcygPQ#ErKmBk4F1mKIej1SgGpS!fMTH_ii?0(E%NaES)Fx2{G|(A;8@kkPP}@}# z{_%M*ss-!5|MDqSRv_`65lvhX-+Plnv#1y5{mSB;S{6dyulkikToa319^md=;?pe* zajmcXh~sIoul!7~n%+A{N;~zHzS>$1VpeLFj-r`&B^@nN49OBA9~h(4YD`heb@`KH z6Y6ZQT0b!c;Q<;#IkLq$xvU64ZkKZX6NTY7t!5+};WffE+#F2fvf|X?if3%}Bfa4@ z5kz0cOrVZjM{*Vu?Iu|9#QCEb5iIEJGG`8x`}#=|+)kR&)cZzKDi;o8pI0}j;_Au= zJ}80?cVZDswwAiUFcg2ij7<%({qYp9wH6>(~AEkz*7SL#J+3Lh~mRDWm% za8#<|rpRL$ef)uA^3Us*((`r(snmKUl#xAR*>UJjJp+mpUkY9~R>IxHc%+rD z90d!Nc&98NdCa$w>)0HwWyQrb7dPAMQQmr&UV>vDn|5R{X^d}f+107J87?`*l730P z?Rxf@cld<#l;IhD6#jxQ9!I4I{B*!sN17VOm_&xsgUd{Xc)W#jOe&F8E3Bc7)rKc$ znbvppJ5Ff{R1677Wq~wHR_USIm9|=o4eQCC0#)C&1>-~HrBi(c8r2MbDG@LUzxx(vDl}qSTMj$BKwYLd`!(vvvC_f zO#kF{oZZTqNI{N7wGKvw2X_Kkg>JRG>~%34eNTC@icryFzwwq_50?Ykm%JpEhBlTD z-TM=MlzsNGC~GUk@krV;3!gQ`yhXB@kmJ-ZXX~j=w!$ z+Yl$`42*X@^!}}7a@RtIT|i=I3q4L{VirjvIHyp^8z+rXbgpwj zjRMuF`!czgLa4Z(41RkRE!OTB`}9?T$SxdhJILB1ueQ ze!Va&OT#`yH>Jqjh*?tKH*sz?7-u;>>pamP%2pSw(8Q+=D?LzcCzfJS&$K$0az%-$ zpW)fVR7ZkwUnO{b1b^H+b9Ak{Vb=XQZMR&c9&Qya`I4efz^D}i*CE9u$5l9SJxJ{q zT{nP)LxQqr5tichG)ih5duSM&={iOk!WX!i4&x}6b_hU3Gl;V&se7FkahZX@aDs2Z z(yBNHXhs^Z5}N~P_JZ}PI93Nr)8!&Bq4UKpA=RxV-e0#B2Kii=K9$(knXE&1eCu`S z4e(U&4IZ{Q{JB15s<)@Wo)6)UVbFU*ml)(Qw&7Zsp~Pa5M7HN@cRux1X778c-W^M) zW(m>3@!&h2ZnoXRmq451Xk(}r?WIXGoDQ#6VBFhN1gjkVY!Pl`7$Rk~h0rgDOKrs6+r;*0piDKr1G zR_5&8kWqDxSv}K^^(Grx_lb2G@;;w$m6QL$X}aa1o9re_ZO;5WIc0k0pAjR^dXXAK z@2mCdr{3!3NaqkYhuZhi* zF7@^&FDl6UE$Qh!I9m=4z$GzOWIAJFWhOCN8|$bDrWtf+&eJXuKbu53CQTOcJ9#=A z1UW0Zafppcy6)d{52wOj8;F_@!en@^K+$Y!o>xrEVo70L-e|JDN-cswt>#&ad%l>UxVK2LV~#SQ<>~sYv^ct99E!gu8W0z%o?|$WPmIR5#A3B1roq}piVRTeN zw%-}h(5(HzI{|ibCP*As7B#P~-nxkJlXEN*8eUsr7}pyWB+@zS0wss?F7)Zh0^ zR$cqni!oc%IAiurQNy7*3Of3QkEOWI)y{It2~x+2?&G8*+{-X;KQA=$`nsvVvt*L0 z&9Jmzb`7gBb#NkBTqFc--qLfe?d?-yj;pwXq}qfMCEzq{8+$ag{c*h`N{_cg#&kX( zWRwWFwJ>b_CO)z}fUk?2Sj)MJ2vHrt|8^m{Wcl*A;-&6lCjys!WCHfz=qjWGg9{tuUCin@TKCFVb5&SHb6!`IT~?EZ zGG+V+IYg{&_KW-cce!MM*AXQfiU)-)5+7GqR`Bvu?^*PNWO)nGT%%tWM#H$ z*FQri)U;&4ajk>skSh`B4~%cD&vqK&v&BaRHqXK*J2ESM(ogdoWf7pv_k?lDm#BMQ zhkkk%{~2+5b&MT2Zkznf-*11O1%0>ZRyGB}s4!}lWt1gbT&Q@uZl0ImGW_c?HqSgs z(Y}7lMZMByoMnzYEpH)lom-Aswf&&0@k=jLu`)FCi;zEqe9n_Kho56h>g$i<<#wSv zbD%?y^o8!lw~93KycH<#V&z}{epQ6^d&7LiH0gYX#qQ(`y~Z(maz25|dKp}?Fk{^L zwd-C5m42@jDdPl`#;i0pVLl-gpU!?FR8%auaktQUu3Hu4-sER13D=xO0*RER$sc7w zFXDt-^Qq;8!$YpXbGj$$d%m06>Z~eczs!I26-!r#44v8675TIu&2R-#EBAxR$~%u> zbBA`dK*usyuw6}{szls^4zN)5U#iSOcc!Y*8mX$!7m_>=p#4Pz+wiW{4%@ixDIu=s zu)%c$4X@TyiI_T*^CwCFMEva1>X?3_-tSLxPL}B@D$sul7h=&Y=8FMXXuDJo&QF2< zKDk_O2Hl#St!92r$EERx#hvG6xSkOt+V|NBSLX=3bT-AWi(?%ex1wcf(s8U4h~r1X zzsW2fs?>|;xTv>S!9dw+b2D;3?Tholq~CYuVK(vd$zQjqxjq@9>LUH#Pk--5A1Axq z0!_Vae3`!83+PK+%MgPk_XK|1W2te=6|5VRSDs!fzPD@EAJj1JYuF}Hk*4rE(`OR$ z5{uFbT)m~@d&hujVdmMUlqJnsDYZv9;#4{1^q<&mAzB0DKu5?}MFOv;qqkJ_;2yeb zi19goC(6_~>iq2HQu8VPp04lhmDc+aeUg;RSB_~O3H*9^F5tso;jSIK7d&)PMs@*7 z1+zUQMl%SQj{6bwsUMGvA{p@+zb;|jFB+0d!Oh*glkD1Lunjk=CC0JFO^-Lh+KP5? zEPf5?Xn~Lz`A(f%S#KI@->+RRtDu(B@3}UUaGcp--(5I5>nRKlqRQn~2?|z;mX+E9 zx#C(;PQvwPA94N%lK-|j1r7$O)lm#w>)~4WexT=~qET`Cm(=QMp$0o-X_M;5g_GRi zzFzRuqU)#K)ti}(^G%A6=f(wGSAK!vR0?CiO*3dZs$!VO4MM(#T_t;@iZQ=B>d{_N zsb{Rw^K2xYyj%;%9HtR>DaVCct;IB6GPkwQv6MDJ4EEGW{4A5MH5)X*1LVipo6R)F zbBOaSMcA*3n7?y3@;f7=4@A%3<&{{2UZ*YmPOv%Ftv+^k3%KRoa8b`?A1r!R@`K(M z$gL)(=IHJtY1*7s?a!$4fE+9oqU@0fIs=JY=vu`+oU_EMiO16#EhK6EKq{B z<^DF?I`+jr3|zY}z1eM^m&Z3%Oh@ReeVEAmS5w`AC$>=YkdMm!eX?WHpcfm~jNu_n zcRc8o1_lL8=Q|Yeb4?j^=e8RG+&k{7u@#M4KB4T8+PW%Rw?xDI*T(f$D?R>&Ox4-x)sk0mTzzv#b#(q9K>pu4(XIpisMV4PR%c%pgs1I(@iyAxhpYJZp3 zvq)UbJsK>e_F?gTAMP#} ziKYDeo{8KzyWP*gfejTFQ3IVwCl?iYUbe@M7l#hqu8d9!MRE;Ze%j^d5XXzjU*F_E zd`Whmq}+bL*@@plAd>RN{>XvbW4_0EJK>e-FqlSTye~D=Y4^J_ii$?k+C=dr=`sA` zd*l36j_jC@IZaYxLo?jUr8s@|GrU%an_xpT$L3cCnF!3WJlU57IK z(43=&4%X=9OG5qd_%$f?Hic5iKCcEjkHIEZkoE&xqwS88h4sNmZ_nc7BDnM<3vU<8 z^^VZ~pdd&agm0$03es+CVy43VUiP2z=ff$vJt-CPlam4}q?a=MkB}seFZQRD*to-) z%v(agjM^S64c>EZ9Uj4#B)gVawl0*(_gqVRJ3~MxuhxI&C<6%`Oco1Tw~?;(RY`6W z5?Q)m2|r4H#=`OKWrwq zaB#7V@<2u>>eUQGoHbEuT-bxEGN2( zhQ<~ol)rnwE_{hlTu+q|D;XzH^?9N{Qvbj>;fvQw304sfb&Lo1#i8v)KeZ0(x?pj& zR@8^RrO#tFCyFx0d-U<(N}%mDX~k;N!BK`ioFyVco|GC(I^IZHBjss7};e97}!cw7ehGf}|<7SWZGz5=G%>8r55-n^%r} z6^^bmjJ_+<7?-#-oTn@IUUOSO6q;?SY-^5LkJx{D$TOu5@X)31?WQR-GhUc2s@)0D z2~is#*+*^5Ft40prposwot{kiz2zRGItnpbphy#aEwJgOVS!*%p-RLnjRdO|k(7cY`-MI3m?GYmpYh8ZwFmuD z?eF=HLNdD`NcOR4|AgpIb&i0anonN;DU~lMOOG=2&CFZcFTB1E*+U9@MWuR+qRfe3 z%V}-8Eg@~UOcZDN7TtOyeWCffb?8feGRFPryDGs<<)3ZtnD;f_SB~E%)gj)e*cP?FC-iVpAE}lOW?k>Y? z=&ehzVrl2xaiFJbt?VbCq_d>d!L2olVYU;J_krj&cW*N2QER?`Y%;|w$@o>pqrs{` z_jTZ|>Vic3 z)v9!FdDSS2hS;5GL~?zTDmrZeX)){i(}nKY{h0mYBvOzDUT0g)egf~FQDc?ujp6P) zn)O{4^qz8EjV>?D3u&iNn}(>*lyQd>^!>n|@sOeUOXRYyFyUFR#WiTn^f5Sukt;9#y;#i8Mop2kyY-q z#!b0*us1R!Jg%GHDk2MUdnw=956{i~898Xl_cT6FUA5FdFxbm|kmjzv;!+`Opd9@Z z`x9|LI94Kc5q>||AfMyL$DM47-5T6Ar%AiIk(fiKf@l0z_F#jfMHlzmLnYSW8Y4rL zTQF({p-HVyA3}c8o2E3k%J~-r81q<1HX9a?QH{iysy#^)7lKi74VzO1y}R4)=lxgb z(pl3sp#;ld>x&GpQnyi=-Ny17drmGE1<$lV0%z}xpy+>tQ)-|$qYs@4cjW;7DW*X& z0FBrIKK#DIiQS!$)_LLgvM(K(SWoH>R**Uum zi{^t5GptnZW~s<2;ZHPv zvgnZv%k{fe!s@60^-ip?*7PSLgS@o<`5ObUgzB zv%Qh>jAj_~roKY&k==kw;&taCG|n=^i194nu-1)NQ;BqBsC$l9=VwYS_lv*&qFWQ` zsj-2S3iru(19$)(yRjjkIlp?tJ%oxJT^pZsUa}v(-=0OJf`%V>*^@o4unA4%-@N&6|(KZ?`CDlICix;^JQd6hi+8%v3@}7t8n&s&8oN7$vIhiEfABCdy&oL>pD9}I~`|2$uy$E?^jRvZA5J7Akx z<3lG`xMqu}7zUSmP3E(C;zK7+lW8C81b&@2mh#lSi=|tlse1d-T%Tq?EfeItiIo;N zmW$AZytNFsi5Lx+B3!v5{2)?d1^@pr_TF(#EnEMv4G|HLqDWVIjR+DtsDSjMC?JF) zy#x>fgpP)2)ncaYj=vL!{j%b`ZxdaO0w@pD{z@Ef4iW#(zHMW0 zr`D6QPF|to!{#6!_t;`!5|OJ3Xz-b6uQu`Pao$FP);@vgHnM5lRmo?fYTy3MCN}X~23FW01 z;v4oygERl@CY9`*hz0ra;a33fZ_oY*@9**^NNe@5J?4*>jMg#X6( zfBt;Ay}T>{_y7I(68;|{XR^e&Gz#mnuHjNv%WHOb!RV~poK>|_8-xt@>;*cB_iPvo z84Qz4gU{YS?Wl`@xap7o3i?vEI$7&-5G}Ey6e~s1j7H@}@a#+t0C!fSw3~{ize&$gB{A0u8R$I7>K5`&ASq_$am>S!*5MEmGi|) znfsZWz(b(6^C6!!st~A9_0iA!y5w}2$$Ct4&+z0@+c%)uQlM6B2-F1bXYM-QUFMB8 zQBTyOFuHFEJT0(&o=N?_u9lTjVBZBt&QW=Dveu86oP=RDZBG5 zEj2C7>V|;AImj=bR{NC)?Fi#{!poF`_aEsxA`Vx+c#!0gJ&s`yM2o)p^y;7D4Gzog8u=mukZofo!2yctGH!YpqAF8ak9PV z=%Cofv~wCQK=piIfGTrv3P5+vLLr*|#l$9`i)}1G-7mAj-7SsZ1*inGdzj~da{(Sl zWZk!-9_$!s?idRysvqas<@Kmh36i@EHERxExOpUQu4N``GP%1dPf zG?4bO49)!TPBz9$)9*V^8 zEJV?B7NC@n&Dh&^4>VxSlm@Wz*bsD7ZUEZe<0e==TBsdzB-aMvd!bmpMm+>cuxx`| zvmb)=(zT&8lDyG;C&4LM^TyTxT81%$CZw#jgjJ`&5;O>ZUz{-G60me+>oI-n504e&FAH z>lZ0^pIr}Tl=B`Y8XJs`O7%k9!zM0TAXkHjAS{FLVcuHrVTroW5h&ZY+ffsp^e{Kt zk?vpUu5GtXbz7Khcz)S{&XD*$bpWs>r<@h?SAHRiu>+X(Lypb^E389sk3<-1q7%z8 zpZ(Jqxx-O_YRCg-KDQBj`@`vE>HzP&h$`>==M|J#NXZM4py{hHPt+CvkEkDz>o~upN}m76 zrY%3ta8^>^T0<@a+U1Pz0d;DCsh1<=EKolI zyGnKiAo$HY|GkOSjMg{^3&Aa4A1#FCM_k0Kc0TT^OF;y7@o`;8Sd8IAL2>$qplNd` z2&op1s=f1lA)kyZ%vm_x^#5(~#jZ40@Td73u{W9Hw4$?6P6iI0KKd)dq^qzyE-clH-~~>ehtFYW%v!Me`Y*8A zq*-KV56>LtItzy&@0LsAIl3qjD~HZdkVmFR1LRBDPjjRetPu6Ov=BwB)C>SRrLgQO zoY6-DscHprnO=#0cyR$a_Ym?G@neu#9X*(0=QC`t;-K({gP?Bk1ak`I z6T~fU@|r$YRZg`>h8r7mvz73L{WGc%O>acgA`mQN0KWPIOL_2V4AL+#1cBtWK@=2d zR?#A*pXj8?I>}h+VbgYo=2LcK2*19t^e=tS15heLzIBevkK*t%8QN$wXZtA=2sOLB z`?jDBorTuY?x9GsJkn>tI2(ZRGV}j4{YIe$t26Pch+u#=)~)q<(qI~04|DYJ+nRIA zJB0r(A>@7wjhp|qX-lwbxj%k(1+ZZ%>^GF)k6W*-6c~{&j7wr)46i;R?*Ps_==i3` z)y?dJ+kwT@w?XKnT|+@CtCq-#;U=vAlX|RBd66}Ym{b~n4!fMF2t~@v7ouhYvv@JM zOCb!)H{zXY37Pr-dXWCrZfrUWD`E(;V+E=yIo@qaTi0=crwU?phdE=;W{ZwCW;g6y zU%)_vb-jgKi_3lTk5lY`NdZKdlpHRYoDEpfy14v1p}o(8P+5wt8}ay=UPZuvxG0p& z(jm`{Ou!k7B!q%q3l#wJNE#athYd@uaJPP}>;2U{Ae3u!$g(BQO(RME>*m(e7Tm4@ zU2r|b1EhyM@4bpW&U{$u3---?A_m+S;bD(qS7CcGgxnbMzs2s6OE=T*flFPk$K>3F z2;z{rZUg$-B|@1Eaj~m-zG1v$!hggHG$;RlFCUtUIISyg_nA03<3M=KDW2C6_L4c0 z9vgq&g?!2U;Uve2u>}3xL{+Z)fUCgm7A-G*qPiXXz9+ZEr zvmLnn508lxDXy!FHM(OYKY>A1Fnzr^aQCQTnmQO?(YoflaW)eToDXqwR><_@ z;nmU`p*065vels& zL2{FUcd3khvGne|FU(_?RuEys@J)UOLeJ5I4JJ3<*INs~XaCq?f9wIEuI^uU*neoE z9V1a`9R0i>m8SHn#B*^WG1c+F?GyT_lOSVxu`3hMm?dku(_y%BOO`**w|TsqLy*9N{L|5gBL`8|VyDS&^B!|I zQ$6T_PStWj*Q@rSVo*|ZO3Mf03t$xLjcp;ya54fBP=$j4Ofo~{Zv~DchpptLLU7by zL9BG>Ly@L^o_y-QTwn}^JaD55MREibqDbx>^Qa>`5U)0jIGW5qixF8aJdCqwBZq>1 z$=Sk!Jc7{~alu^~F^1F@PR5Oembm+?LO;cJi}lugLwY^N>YD#qF8>Ue?7ueoWWG9X z^bPq`Dj^Ol;dQAv9>RSmd1?w8poSvDtN~><`WfB4O%s;`*rZvI(;8?k2^cfmJ_K2c zAA-P*`^=FHhys+aSsH;ZITV>jRfu{4Ekt!|^qM1CB0^Eyk@)xIm~td34`r}3rma|# z>__~VSoI+YWn3FXhz(}-xT1FEbR+^nqV{`E%47do(LW$pn0`R?kXQe;$p3fK;a5TR zuU^T;<%rDIL9dmNe4(nt+dP;qmo`WQodJw(-T>wo!vNdD7{Chci(D_dKXvVyIieiH zAho?aTm5k>+aypVD=!q;ms5!Pw03h4LLT!Trmf|Rj>-x_+r#gJ)muscJ+D;zm-Qbo z207gug1lyGgUB5X0Y7pO+X;AB9pI+no<`WkzJylrV89#M=ngN`ViJr4@Qw@D7h5EB zZ(9HDGV%DYJi*Mh4gue91D-PkJZ}zbK5ssia~~At%5ss0QV|RN=lUQ9;%Yv6An==* zmjs=5ZHk?8-L*z8%^GFc^b+mEv)zk=iw&XDg@5Un-S8dVq z>hY`=k+ZWSeDf)Oe|59wr+ViU>%fLF5FH#^ora%B=61hNEZ|>#=fu4cJOiTm;9&2R-!sLLk~6 zdL68uMF7SOzpc|pV$vqtV`Sgp^AlRkFY6nv8?OM`wOtZR@|cA`#+(og%-#@$qbEO zGxI}`YQ;7PG_h-3J2wv!)R^tv$vDwoM|;MBZ8#Dry}1Aw7YnnSSPy2pegyskgDla) zrJo+6K#)LW2=p#su7fcYH6l=m5*TSmGrj$U=EL6O`8{PqY|gR)$av`y9P4@gb>odG z8$!EVg#_ixdlCcIx-aU)!^dO8voNOPXgM!frAIzFUUgu7U~xZ-G%xaaHvGu?#`e{t2dsbk6G+HN z8jh!O^Z#zTL)3-3>E2yB&)S7Lr`hcS%yu6^b#d$`LCnT;J7UYb$_9?W>I8ix(F1W~ zKK|z>;M`WG;-HiBwKhHBG=cr#H)}K}%%b1#iIf+`wLU74>E;%bk{0C{=^y8i%@0!L8XbPz zo#mws?D@(+QQ)V}gn7i>lCG&y_=EXrZ?&Xcc9RHC|F>taDn=NYe?C(8E;k!)es~gN za6}n6bDB^*ATYWcUOm>w7gu$bm~iUR1fDawd%^L!E<%UIM5`=5x%d^eZewTR^Zibc zzuJBF&+oJ(+sI;MCF6#DSm!afnkj{EDmbcfQ5($M%?clFqOx9`$w?ZhPs*)=<*QM` z;FaHEgoR|8_}^;r3n~vYmw1nMCRQ1p?pWfkqYc-1WNz89z6s^jcG>QjQA1K3h>8~V zA5ki#Dd(|~)9sjdrU(~T#qrbCR?p4e@_updWApe9$zbB9t$5Y)^ZdG-&vZZMZQGXP zCA!E9S}&S>C!&*v=*-+5LMLR^&Hs#5pP$Jb-BC{9$HNg3 z%2wuks~Gg=Zr@<101d)8EO1yvhYL6A?e+=ND)g%KDTWVLLUeUE zflc)*V_br>Rf3%6C_34RBOkSX4Cw4&WbE+a3v$XxuGpWONHpP8wR=eOO!v~D|E(oJ zAH{yh1%8|OSKNvYQ=JEWL$q`0();+_%0SiNQ`Oqnv3t9?G^;i*9qM_SH;h8^0Zq4w zu~URU9!Q`}k%4vc(!cF#r5pN&7;BE#o4UI=KY?C-pu%ZD7L$Pt8#Z9h(ojqnL1i}K!OoTuVLRdeltd0`#p5C!U6~*7Uc_1z+0he0Wh}aHRGp&K z(OEe79xY-uQT9lkywpa?t-=J*^QU`@7Zop{tMcTNTCliS?1WI!(!56D?UjND5ynaF zA*H=z)w2ZRMB`W(R3hP5pC8`~>a|cs=P#u-FBjX>a{_{`?6&gm`SRQCyiA*PK7T;E z!d>VeW8kKZozT~>>%%#5xNMfu`o@j zAX&TOw<8emtMlcfgrqI-!JjMYoDo`t<9I_`joNH(~=~NdY}Y z_xCI}C459(IsR&_f$Ru6s&^PYKuAyg8m@xv(;x>IW`~6~dWLE}X8M#H(-gVrKj$PX z3X~Y_Ct7=1bxnRyV5^g4Z*urGkdjQNG+t`5Z7{ujMH*|xazVmYoCW1ecsdALdqdPy z8`ux?u)arY^_G!E?L8$8G$xZ%rF=2FPBoYv9^jr@uGLcVlxz95>?e^#GffxxYlC$2 z>09_K+};gxzXCc}y;^FaQvtlos5*?ohH(pvP5I}MHnLU7__5!YvT8~Oomy=FJQIQI@;ln3I7&!fKz;&5i(T-zRAMUhG0e&Zm1$1i=}_eN z=DX43+L;GehSTALxSX{-Ti;a_g{4zo^d^i~EsW7_H$PHIDR{FzH`1(Lp}EoX?4pH# zQwo6;WYRe|?@Hl1_Ujs#byhl;JED_L(9veAT-8VVJD4YgDcF2ufa7n!j#uL^HWoS( zsqnO3l|T6!(8(@!?{;1zJ*U4>7B55@Y}z9ouh#O^n*U64J(~KhN2ZiI&hmDo*P23F zGrRv>Ltbvfygw`2%j0Q+1G|NK%TvP&C|_|F9&IJ7%CKjE#=+=+BT?2;a=EBq=994dU=w2!Th zF`)YLT?ngL9o(cAV1V&0Ue4-)r z&P5PTiNvr<4b@UnQM=beLcDyg5CRg%_+-UfAD#x^%WhY{UC0*@&f3U=a)Ol|3Cl)7 zw9}*Zp`TL@$n7g2ai?MzdBmYcfeAk zQ0!vJk?%CC{0o*MLfdOD!zxs>8tc(^=<%vd5p!yX_o#UazakN%h}b|gO*3_J*{DNd zd)_K1gU7OwaZ=txSPr~_2dy^+#1KFFprKQ<@!y{(;Vj>g&MCi}4wXcV;BYJq;6b$K z=JMnF8WOWMu%cXEBKVAt_be-JKd}o}D0DJR{Lx*LtL;f>E4Ae_-sLQNu5={#nkh$F zSM%6uvHy$kfw97@8Q4#-aNTP+iysj@7C+klEn;S6+m=)MB)3X?#ru*z|=shll{ zpD%cWcI@`jmE%i`>yP_+br(2Y>n|JZ+9K)J%=1ZhDLUm~KCvR%vMkNR-d;s%W^uwY zEnBCy7~DSiYF`_4rlz8c9a6Nrx7dhS7(4I&REtpW=7@MkK-EY|rx+I0Z(qO?dXer>}-i&MZouqHE zlR4zu8{b0L81^_Dx+2+)Eb&nG_eVPHaZlD7Y-c+e#N0#0>Ked|Ki^OD1@F7?zLI~L z_$)HcDP08}V1rxUU3p^vA+^@vqyF>4J4z~*-^`VA3nk-{VdOay50B4wV|AIwk{5smZhObLEr}Ay3LJ7Y<{>wf@){+8Y zfphi0#kd;_Fj=ShGO?t_H7@1$LkXjDN6axzodI+7=(SH^e2<&0aXk-aY(JYloB~qRx}es@;@3huPFn=tNsrg+pN5QhC-YlEN@z;(1WjiqaxWuv7CW zqc((%(qeU$f2n=8eZor9%rYU1N#cB5_{e|?J2?fF@3Y-`S%Y3s&lM6rB{h3Z{r-{K z^h$5LG2NRiJrb{D!(+s0xQA?S^7MxV&=zJ=TxY$R$4$}QrIvANC2pgYq9_wrSK_m` z{`1sYo!7OmKN@(ZDm*c7Kjbky_Hq+WQh1R!GOZSsnx<Ilz;c9OoBgzNBzjCwviS;0?I%(^#c zCw$YZ|p_WuJhl*T4)F`&2&CTz`I`tgG^JA~5{?BTsLa*Tj)D#XD!b@}H6#WbC&F9yA|Si2dY@ zS~b4^7}ExE#_PShi}UZI=r5}V5Kcc?-d^izZKa~s-N4ptL$+5FUF=^Aj3z1m81+t< z+m8B4bNcKv8Wj*5FV<71i-L&Y-_Ld9!9#!A@~KL685OXU07k*-*udKhqp`1Z zOGvG~b70Y9?<)f{_c&pd>VE+0P~Z|ah=2YfK8RObK5(LUIAwyW{K88|{@@bYJD>0-2U^+g|P2N}pZ%<7?9HU4PJA2>*5u5C#-e9rvrCvQRxTnjd?(VUHn;6k)kO+ z|1$1_3)j)B{X}}9cNae%UmZ-UE~<#a-@9)3eTVO>`$yZYC;8OZyESTQ*fhgK?I#020VGDHMeHi&ZEZ>hRZWcEhmhyG32z5x82tF-zkQ7kKN>q9X6}ihB9_D^3lM)8lJnyb)>aZ@ey_3b>^k zKb7nc;{^ZY+eckldjs@uD&$dZC zShk9I`~o_uulE-ntUasx@>5iFMAXjp?9+`l`2Rm14u>Hl!dv^ZYPrtlN8(C&upFRY0ur90z%=shDV#*;UNs^;<*CK=3`NA`B66kqxr+E zmG@BNck&o6KjIwLFn_T9Es?qgp*v?)A(}j~u~uj);Z=6PS=u7360Q0sCMmqSnK1P9 zcwQqh(ov$Mk?`!3vk>h2k?rSxNs0Ckbul4tEX_|0fZ8$-pU&6(LCE_KcNUFb3a_Si zRceI6b|No5pszFz*@Ejjj$g*N_BM=u5ekd2?As(Bf9Oo%2*{B%oTRgty7^V)aQTmE z3N{I04(7;?pFI`VpRrB0i_3I8(&PGx`C}*v$t!3C){}-!? zEg$=9{y5k6*ik#yLpXFpH-oBnd(3vuM%O+qOscfcTZTL7GT9n!MN6pC-EU8X$jkXU z-&gW=6l8lO>n!l7!i!l|LO3e&F8uLKe)WZ#`D-0_tBT1^+~cT->V9<&KDNhhRNb7} zFWbfgP*g0EqASyH5<*GX5<;!LWbg`of8f>AJPpXdF)WRUmhpQV+Z5QH3>#&(>p_0V z5iWl8e+390I&2&K>ah0mH(Ku*DpnN_D`!)78hF>s<<3G?+kEj*v9fs*MLs!`FBW9A zb(UDvFI#??t1Wymh_`FWXkO-U7W-CzB8x?F#pLBOs(eE{rwQ*_X#s-2wz;lShKp)) zMm};{>y#}-ru;Ake0cu?E8-fEoI|hCIq_s+e|@}s!Ik0@6}=f-Z6#J!WhsC?R)&4Oo^-?H`-$+h>}E!WBT2%Opr?4WdpDIo zZMh2<`oA@q3?|Ff{&4oAY?q>$=Tf3xkIjMKtcP5=d1*^8l5(qW-1&sMa_3To{bZR7 zs>0iGSVh>!q-hs^?7zFICuDW2q9U;WF82c1>Xu5k1i{tL$FWjjl-EA3Iw3?v{DzN9 z4?en#;P^VFCCR|_%PKWogsvV}7PeZgjDreR^)pzlZiTW2JZ&YoDi50Vxzc*&wZxAA zn~FzYm9FsL_)tQ{NXB}*pQ6R3D#Oez^aw0dV%x!BI(Ejd=h1X{k-R$5&e8)!G}iM{ zl68Zg$V{2m@spN!Nybe_$9ZYVa`F1(R2bdNFlnuof%);g?(8ZGsHr1K^ot{6b3f&=gi522f=%KTvy)K$=bv$OuQM^cDlQG^eAr=rE%jcQjfs{>`3YACnk*zG+N0Vi{7KDNR6??Bn9-P%w%^&brLPy!0Kb!b8FakK`u+j)1v%QeW2? z@{)cYSsvz#@Ci-!h}IiOuv0v1x+YaMMd(1(quj_MCdG9Hnz(q8FQataSV@YxUk2-{ zE{B_x#Xn1YM`k|s;6Q(+o>A1KPGeT?Rh)i5YkYsw-WrA-G9get^KB*9-9dXrP6FlP>@!Q$xuE~!F?`-X6`oB}v zQE+BL@UJHt{)#1{r}Jh%6c5RsHK#t0s`1W9+*(j>Q7e0i>^Y^A@@L#J)Kzd2+Z!&) z?Bk8>drOO!Q+4BBI3IJ_S?^ZvRp?jx>4&L7Ew;>9$mcKW>d+cIsNv~g?L9n=hgmc# z_T&=r5f$DlyHg}ocyt(TpcP`ZQ@m4pV6*9P%yB3{Jz-;b^2@Td?B>u7P26`lT1g|0 z&W&+<)89-mQn8Yh(iBqp)g?3A+Z@N3XdPXWO!9ohSC~sZO_XTx2G#x zT`aswN*-V?`6pVB)R8LLZ*t|nCY_8H&~@EO>=GZ+6pWX4rFh$_&ZWTy0qOAU*VuVN zCz>$Zf~v{N9@*hyCn=iQFN+2fnJ_^NTwZH#<6}gtLe;YxiFz3t-}@grTxQ1Z+kPv6OSQcCNmbpB1@kNH=5-?cj9my zWlIkP9X9;x8tgdrO4-5PDSy8=_V!wazV~aC?eOh){;YJ$#v)06+hN(!slnSe(kuJd zB0~gfR^)-CV&0Dp63yllHd5h>XM4n+m;{yPHvfA zPx5&}_tIk~k7CO^FRC8hJ}UG9@FFz~L3=<(d;Si97z1FclAWO_NAp6IkGsj~aQnxu zx9Edcb7w;$!_-H$aU6pHdZ57^isS|o!x4yM41`=AND6EFpzSRlfz^v{f-#N5WS@abIj^?h@MR3}#X?j|ofMXYqZvDcWZ_rml@^6(=zZ7l zw1dL{6fppo1pui)A20((LqMnY@=;1w4cObfjo8WpN(9H?S8HrXSWkqT_?3Iap{8|7Z2-U6 z1>hIIk?>$v{BU}iW}-gXr^#UTEgP{jc|h+WATTD7r46#PZZ`fafY-n1*08%_kC-w! z9qxs*C;KlRS<>H&ikLYC`&{k3ObpEla@n^O*C*B|%OKCRH<=JML{caN@IPAse4zyZ z8%AF8sE=pK)st*pTf&frp?IQz-;bpLW7-vmAm?R6kVSkJ_dTGO4B(hLt~{*V;18^` z001&k#Y3#D9Ud5TdgmO~1_HpeWdQIt{LBgz=L$ud^;9A{pSpH#CE^~e2@VO(;9C={ z!hYTU&rJI-oX>#nZ#2-an<4}x%?)I?FaeuBw?Wo8haio$VF>xu2MCL8d0UtnGgl>QIaSIxF`0vOhp43Ip5Z-n+N;=9 zI3;6%Wao|SpJJHvaU`vS`~62bAW-G^H~`ks{un?4`?sU{R;j@1!uUMu-YR~FRW^}b zQR#jFE}0mto_iIH@g6v{K&k>bmTn}#wiUM#E6Y4kq!#}7xRv?2^4S2U4sQ#`xD_Ok6oox``ex0romUca|6YP9gc@_G{0yhXdjH`Kl$-5)9rHtO7OB{~YP*9&K;5$6Q5RDdui`kaz=%d=~;N zlR8OkIC~SY3p_nPCiIbIu3`dTb@EGc`MJ)AuoNggaGl2i6bu6{Kc^L8#@~(8&|ZB| zq#}S4RWwAZM@$5raNmquS=n@mek4(V5#2{`wwy2 zLLH*>8^cgyflmDE1B_7Q9U}l)4Ff>Ie&`{HyMxCU6ye4X?CpXhRhVd!A37?-w=3$g zD(9A&9f38e32>&?qd8!h4Zv_EvGJ!Q2#Y}mC@AqpMy~N7bEowuZT~7@)f}bs=Iq`l$7+N?XM83@xGl(g1*?vKpn@waw4i`9{sy zgp_kw(JKyN( z-(O2fN{uVo9Q2o8>4A!tS+GXrl#`AuPzEmjM%w&lQL{Ebq}f!3p3g2TTCh*hQrP-j3XRi zXHMWklyk?ofXr3^X6c43^a3F+#6Ad}x}#ShB5ddtnWeAbe2Jw)jvXO=q4Omt8aA3H zCmK26fH~m5$OU_R>od5{!qTrT3mtyezQ0z^;YXBnCv&XD<~8gx7XY8E``45YXaPu3 zFLG{m>!mBOGwr{HR(h0c5v&e)?M3M(C@wF!Z^@nO3Lc!efG^@;b#*2Y$A7i+MQoxb zbvce`Ru9KE9zyXBF`iV$=Wr9ll_#zypAA9g@Ru^!<+bixzaSU8iNxWk^V9BIJnGh$ z&Gh3G?-2t zVuza7&fQMUV~^}jZXCQqc)wasV~$(7s0)cVM0!+SzD}NGg`p}BUPW{b)ByLOe(9R& zlEmbk)^obXS{tHX%*tQJ%72jiV*l!K-QNsTsq^r7Pe;(%(|7QoV+&u>V_(O~BQ7U@ zBTAK@{K#cq8=HErBM8)R&^vpY0xAazNXJc=d8+rx0qbU=th8r-@XoL`T=@EE@y=BA z?Bbr#nxriNBYuAFoT!2W#%$)#VY9Ius4aQHYv@wdv;fRi=@fka_Fm zj~4gzHMiD#gx2av|HI|~TN?i-gQM?ZDYnbGq-TD1>WB_zDPFYIzl(7j$g(|Rt6~Y1Az6^12CTkT7@WAYG1!J0!{S2 ze4y9}4h$@w9gOK)8iF_#ZM3?RJ;$0Gtr+T^`k|W#WUP=j8$iWTybs#wZq<6O{nv{dLn?vN(Mfq;`1E>hUyE>N8l@6(kL3sfqt)1Xil zl!d4V9u7PuMUP-^WKQB>Wx&{30xCwHzJ(&Ib^u7Yy%`_*mPIB39qyk_e&Q-bH7J_# zVTyrDB9tvoZ-#!TmAbCE)U*~2*&>s zi}~YCJo))Q)F3^;OX?o~Lk&s+kz3^Xr@tkOE~^^5hG4eUq56Gtez=Qnr{1g#0Q@wB zBA1i_@S6?*e#;IXgG~Aiz|`>~{qM=Who#s73h|f@bEP*!9)Fu_zb{J< zSPL5iH(ApY-$Ag~Pv*plu$7pomS76%_C0OZCz5&tW>Ema>&l@wrHB05f1yxc-iIU%Mpc-!=sx z838WFxg_S_HpSFYX^;KiZd@sRBe)TvL@5nI1$Xfsh=F7D@ALe7=j|N0-KYk+!|(s5 z<+R<)*eScDiI060ra^tf9>3|?u%yu6vQ#Zjp2@3?lSvx4g1%u0_|8EPuGJw36ey@j zTLud5q-ycsWpg^=l#)q05$&yYyOc5zpK2?ewP~n zwg;&hwHp0yvK`@t(C$fS4WSr92`N~&O9_rKr$nJzY8nCf^E2#>c@tLubhy61A-L-; zvnK1F@r$w-)=R5ZsPkaE;}KOkmNjgL+A}OW=~>q#cae9Em){t~6{t}$m;{PeT!YXV z;YvK1UJGVowdaJ?5vZGAEs(wtOM^1zN5LU zw4k=U3vAjByDEAK|KDTy$wNeb4gL0hiTyp&5;5%q=>|tz|+X;%5TN6r4?3`QB-XiUs5@%{i zk6u{j5f*RGH4eT(W0L)m24hp0@j zI;gFKRj#61hNlZ^RH7o~X_s|WSxnMwlp59&UC)um@fmteDAsjNs#Bl9VZ%ef0I%v+=AvEEUb$N%u8kH zr5DQH(6x`AdDQB3P)k%(=(vLyne;G+3G)ir_#uRwm}{qWL$3&rH5@`=NkMG}VP=0- zy$;QC@+?tl?pS=bLN7aQo0j!32O^j>Vx)eyT0|xoZ%Oypn}) z2V*Q}6874LIi6?6Nj&N7-dHZuOsb0}3b-40%I^6<0&!*O)9sYSs6o#NodKAxuKq?& zK&%IfS_^45V^vJipeZK0lqI(D5!HVKj&;EgZm6Pvj`~RzFcWQqUIk163Eluzjz!+ zgk6OV$WEDeMQOPNU+m<_iV;$_uW^=>LxqLT*SzaH=%W{(!A>J}k$Y3EcZ%7O@Vw6V*84&aR$Syfdj9&Yf)w|D|B_B@BfelZa3 zZ_(Lta~8=QESkoY?lx~5+bEPFB7JWJ;|cU7I444A4y;xRweUwZ22`sPZReaUd^`p! zi{TqIdHIcrL^m}jH3GwrlIxxSe{n+b9MBWDjo3Ev_DMIUx+T_Z6WP{*|cxoqW zmc2;u;EH@1E!DyL*$^p$KwmJv?~S&|y<*CjIuIjDkVYKGf<=B@dZd$moTFc|Hj7uD zN7RpvmkpCbd;Otf`!43^nqmcEo8{fTO6@WNd_$J~uT?u|m+Obg^cdceWoCvYq^z36 zuyE+;Q@ENn#}M<|^u%;f1 z17~WaMm(QOlRNg84<-CasfV28y@kET{d*phKcd`w9>(JT_-$~wkpSlu)WY+fvIc2} ztC=u|;^cCPT2!Wam^v3NazwE>gA2FHp+I82tIJU0%(bMrK1oLDssC9pUd2pKy@t<*If}{P> zuB)r}GSxd7B6?I=l8YC=*-zCchP42xr-i_FPi?j50ahIgANb^|^U11H>t;8*TBX7> z!OOb!c(1yD;1M6m$jPLFmD=L=SRXC1FSDh8u5cu&v(;R%X&RpfR^$lQhI6eS{w0VZ z=LjM~A!PF+l9#rp36G7q74N8CL9VT`Sm03=;ne3Fn$i?ezW6azEedUYl?KE`*2=NN z<43>A{9oV|R|wQT|L_ z>?gG?;aK{hqwFnq_rlMw-uUo{)#QX)uFzSipji+HCxTN*dlz$`#)Xfi^O z^QgD1_P3_>@21h!IXBylCCYgW0lV^~L1Hu9R$WTp0I1KjQ?YY$|;~ahtI?PSRGBLMZ|rJ_V)fDAKRpNPE1PKTga>O#b+Z z->t&77~(9RK)iw@izAs=8z(fx#oRMA#KYC*wHnv8JiJ$#mFz|tC5BnE<$es(ZQPKi zWEf3N)T@`PCe4kN8o?7Qq}aHjtNRK5$wX|z=Fs0gV`}I?dJXj01Dq$Pw+L^i|G&i5 zzM!>O3OWBgj`CXh`?sfb0t|o!feLfa$*hpNlpfbM|yPxZW zX}rgOW3f3Sht=eUW?g)fcHLZMzS}nvmjH*Gx9O-alsvKJ%FXaRsb(Sl9 zjniQw+P;1(R7s#_5<;;K3d>yCcqFa)_dAJZ*-4-gzg4a{x!V233-Rxy52cferlHs7 zU@Bg>z>wHDS3;{>1-HzwKM1Z06V3R1yg~&et;^q6Jo;Eg%YQ?`wPyB1g<0We74PS{ z`88_2$GleSnf#1qwKPNk0L7M>(L8x#3R}CrTJ54EOg=f4=fn>h>mT%(SqY)9Fk+UV zDDQ&&YUSd+3a;D&veyfl@jdjP`*}!>2aOnys1Y9@NXWhH2wO1k4`4tA*+%;_or}=5 z)p!op@i*TM`aJ%UOMYz6lyN@LE-LDmRCtBaHy`*2?-)<O+viszz2cyd_&QI*`(s!$s1c~4I!?GWF>b7A>}B>S(1uNY$N^*l?gT*JS;Hcq!m z&|y&D_|E9$S2_Jt#$$wS*(4_WrDzo6k%;efbJ*5p#7$376SVbG0PHOA@N* zVeFq9x&{*#@|?hy7wf}BTVjPd()d+IACht%^gji^0JqDyo$TBAXQ9_$51jWyh?(8a z_FwrsCyzHTNl|hbiN$A7={~fi8S18gXGr6V&7y3_br4PNxNuq%EYZpgU0Vg>5IZ4K z{2|tCkq!SJU+*0cSJ#CNCj`;c7$pdzB%=3TLUa;>VASZN#bET7AX@Y~dX4B&NAJD& zKI-U1ufsd!exC1szTfwLf6Os+&N1w>%3f>jYp?6Fm3g~};m6tUg_C+=$Qm>eUMD(v zFy5>YN%l@c;GTsS>>>gMY+HEnA5@0kFfM`&wG{%DGr9j>`EA#}u1ujo)t;G4X|Lr3 zGHQWh=kRtS+{r=&?{P#60ZoN1mTD{PN4nUl=6{G*<0n0p-kMiU@kEGLIv)Evm>wG2 zKY3Dad`P3f%=U z^GXs#swv9^_v-kL+9T|nvLh#ADt`CXdD@z0KT$6DV)U&mXDCunfU=s`X8kk`zBUg5 zE00<)wT9G8;W%XQB~U|;2y(xiPO4+2On=}7>us%fOg*B)jqqa}qrxkfJKV%b#fyTStI~rP z2okzj2MU?O5P7T0wnVDR}F$xFj3 zLu^q&!(A7w)v19(R;zTKHIG;1KSpWwNmN;a$N#uL|JL&vZ-`7}x)rP@*; zHJ~x~HRKTFlmo{(wtyMy zB%DX@T{_7Qh9`x{CG|A!yP6p@R;Om@j`XfsTAlt?eUen?_-BAdbYOrR@Qb-bvQl%q zDrMnfb{EQsC$?*Fiy zs6LE*e2m_?8WZ3LEv)AIz1S738TwvSER|cn2I?xzyBWED{rgdQY?TWy5bNpAl;jPr z4ZFi8f0?pvmJX}@AS*?M$g-CudK~W`P=gLDbXwO;g=JeS~ zqNyqC3Gva%;Fz&R?i7w6yHhk}oiG;)NMCohllCZx;{)nifv+g((9B!@0|_D%gSCm3 zot$v)3#!S!W*>83U5zwHD=dpcE zj|nS)jt;G{9h|4z(Cd$>-SC*Sk^UmdSw0R`>{GuJ`{r1c&-WZ!vVIN4^cB9DIe)Bt zreck^4qfC+c>CsMU=iyF<*smE+`LE4`1c$=0;chj1hD-fwaJVJUs^VLdbVKEoU^kd zYJTn`4R#ivb0@!{D6m@^Eavv@Wk0Z9+bkDe6KQ**qR_A8kn>;KQ#hGV%TIR4syVOn z6us=lb0>2{E;RhyUD-PNboeup1-s2!%)>*A7%1n{A{z(ee_E~iYn#3Prv7fNGcO0I zNAqFZre{cD=NbzZ{Q6LZzYoytiyx=jk}Ia`q^#z2!NAZ2rG`Ysj}xK zur{}H0gDgXqP_|aUB9m_RP@S`zs(*?S|hedyI*hU$hPQi&d39!@cjj)E@PAqZl3JB zx}x?Ef4?xSdVeTev~VoFtU-cIoMDIbQ)&DN}0j+!$a}J zuJeq>>Qj<^o^0hu=4$;ao!G(9d$)ZgQoni;v8I}LKB;v(qpE_q;icvpv8X1uWXXKO zrmW?o8_HYn+DH=oabg=1R+ftwPQs|~lK%}gnC(xPLGRD%oRQO=_;Zp5+qG>qI(^Cu zi->o2iOEJEemcX0CHWazV=R^c$_fuw-6Z4Q^FGN+JQ`IA0jEbKr&&HzdtSvjswW*D{1!}S+oBw*-z*_&s@bI!^c|KgXO0crxec;XEFv$P; zh-pj!0V$0Z3O&|s_OHC)|8v{tLPdN0@vb$w5{N;`sb%V=iaNcEM<3$pNxiD>N1XH+ zy!y>IQp$j_vc0!9Z|2<$w^13}V2ygd)vL{#Q)O567FQ3&r#XsZ;hDO?-+p%Qq8^`L z2xrwEM*-#h4okPR1p;n}&%O0v39LVyJwHzeaPW;dlrA@(y>@`C`ejC1*^RpG28GdY@Y*zZiQko zTmDefR+|OV3fL!1d8emq{dBM^EVXp`PwNDgl#)RZ@DtbFsuc`t`|y-9)OoTl%q|QpvDp=nLpk z|Bs-}F1|i0eX|ztPXG;ojZP>3BK0pp)267)s;FH&K z4`lDaL&$g}`uL%>iQ1FQUk{DSpQ?_z@IKjBf4F){_Y>qNFnTE7B@<=idsY8bVV=81 zgTg#NDn2t8l>mh3(nh|Kh_^P4KU`=mTvI(4C6`Ll(V$BW3o)_l6(yb%s>k5AOs!j* zBBUVH(Y}a>Z@l;FE6>slOgYz_L4_d{_qgwto^D$#pf+0k7stZlr91Vk_{>j&$3_wN zvgS*rJx~}Sq<{T^#=`^|DF2hLrNv=91C^=cIQ9BhE{9rc+d{L1HPPMSDdz(PI_?G^ zbt-(6B+@G^b&Khg3;aP(O;7u0lgZE+QM(B(`3#pXegrY}e^kc5A2m;rZe7LkAup9j z-G(78a6S5ffBm}QI{LP%x`AeoQx$Esi~K66r?8mI{Uewy7XiH*-XE-UUVm2Hursrt zu}M64^yLNjZaYak8F0woewcIUB350pNC?)|2HxR6em0;JWc}lpD|Z)Moxx!J)b;Q= zp`ypRx&60*FE5p?vWJ_Jzv=cuE`6L&(K4P|S@~&>Ka;Z6w*X7s^a>@3dX%3V1E3%vy`smkuY{Xp z<3WPeJ3D9WR6I$Nm6Go>Y_p%B%s`lHo0+VukP=%!O2z61H0x`0ukLnL~zg7=x@+Ph~@Dz_W9r27RAPCQ6Cp$P;Bbu#d%pS z{26{jV*nJm(Ppu5t>E#`>D@}?_vN%CEXW?@*voC0^G9;OE`f+3fsN?zV-imyjpaW@ zy`m%n`<8t@n#|?C$j#r(9a%WISwC#VPgzyQ66@Z#wEK>M2KGJQ;gjcD;7qamHLr>g z?7SfmX)UU&31{qEbLqyPr@IDCt)q+q0Ty1(zGNxU{6rgzN)6n@q7^)RemJHp`7~J7 z2TPWy4X&>0`44zhdGaR27X}k>zQrG%37s7>yb2)W8Em-h`xcImYvU}~r_6&7Ip!;8 z7hgM;l))S%zqNzGr}``wu>0WqH*fy^@tE}ntM$n1MI#CgEzkB38k#G5rrG~#M3}aW zj5P}Ioc6v^ z$+T(T{#D5->E}@XO()M}e#9zohtBkgpwdo;V6~}bP4QuGa^xkFz}bcGa>g&d!%{}m zL4(uv9DVGHE;KsunH-`a?$Y7}8o&3_!lV{p{ls$7W_f2}+u){oT6FR9Os}Ec6FS*4 z)j#nP$fQfHN`LCsRO+h?AbKUN2~3SBy0D%4x_YW%1Trz7(}~VaN&|y-`W$%2b1Y?e zWH=PKs!vrYl*m@Em90uD z@;{tu)m!ekN%)ggo`S?RCA7ivgz5*`js%}R;pT+*d2pVNT>)_D)3oGwvQ5+S}7B@u2?Mk zj?G?iX_U~gO>FV2sT`*_0^KbKfp5@T5ef4z)UkT$v__U?<~KENMhHlZBqo#@|K*xTN5JUg2onaSYM(FUWa(Wl-< zqpeEl7do>V_jtovH?w&kL4%CFCV|K4YAQu*_rOF<5@2C1xdK@Kp3mL?il)bV9YiI3 zA1e(ev7O7U@dGm#7D$u^oA3WcWJ7GRb}4i|(@Krm3k1#$0%<}Tfy2>*K-?5@)FVLy zlfHy@Z1aOV2YTlA2D^}^C%{SB&|4Prbb>3hqrcqiXt%AxKcl$UN4=>+DF3z(zlZ9m z55jAz`7ZD$W9a^zp@W3FL4|%wPtRN%WFX!umPc359qV>9W-R9_hG50Zd#L7XxV>#> z;O_=rtA{JfstLZEt$z?MWzQ0rq|p<$PRPs(Kf0S0=0R^L!BW~{V4W$nzGzDn;WlVT z2I0c{G%hbPC)6s8GyycM@k4jF@KwqKtsM7zh=_d5^Tbn?`e!Ro<8W8N$4Ma*l{yz1 zA`Cwur|5a-_6aCDdld0}@5XgB9$OBrlAqjth4$Lm7o-tmjVRjnhh<9Vt#fTdf*VX+ z6EDD2+6;tj6E*QI@l5i#30vb@W`#!RW`#`JsjzhBYf7-r9A01aFTy?tSNvQUC371z zPbpt>eI#li66=#635h2f_{)(5YPe#SEOEhfp|X*S%^cT&$+e4##6rzrf;D?5zy~Z3 zE`i(Uhbw-|X;aAl;D0pyjd`+JaT$Fzh)C4T_jsLkNwWg3men)hs8>E~o^P6=IrC_*i7!G(fQep-gN1=7rwLoF z_st653z`+;3Z}sfdr>+X(0qu&x^^Gz9(Q6chiXuYtAJ_3!j^;H#2xd`bGm=$3Ha|675ndP1nb1Nbm*n{^!%53 zNx9J&eOW-6>I7v=76}n=n218&)4V8p9zgHf*HyL@h(?p!JP_+J-ss`s`9wS};&8Oq*DXv;@_Y<0+^cq(AKT2g4&$$(yRMs zgP)F`#ew=k@i^T(9A^~z2S#|NED{{4FdF7GSpTjc0>{w{(d@Z1%0>o$9C5$T!IYaz zqfmegV9xP6iygeUWFl) zpBQ)k^T~hwr}I`Dh3QOHHG{jcJ@~kUuq61U6^<7of|)`161T67!z(`T>uF0Fio-Vx zJMTP$cR~+3Fa!I$_DQN~1ZF1HFzr2d z*rmkgbNU7KJZ+;miW~HNIJd+GxI`nMq}`m+aVCUUGO6Cbbo7OaeZ^E}V4G;Q>HV4- zRE=uBP#Tg_6;vI!MDRzHiF5^lCfFD~yl3hbH{KK!k2{Z|D5S4rMVmH$h-jWRenY$S z7U*6(hb9I%-VL&!k_v%5c36? zDGMwi6faEpSHGeB)^%_^DC`{9l|eb^#h0S>^}gd%E{yjcz7J~$F?ulsAl@*sHEVC2 z?OFGVM=#5O#Px&63<3~jtyn3elmB-XJ?5Jn+YMw{5{e_{>ISlPtpK-<{s?>1iNQ6o zi~`>~TMPnE)th6th8;Xl?dJ487vAXB;BBW{Ki*zBZx-ncj>qj?7h_=@SWkr&BlURq zKI`8=;QY|ech~~8%s&4WB8o<2T7sKhUP&l z-xjNV`{H9Qz%?-}JqWZj&fGd51WrO%Td^s2iD`+bzy6RaDlhLCVFh$p+4xA?F zBeFqtczJ?z_d2ctRR7sJU(varu|7+|yZ9#nJx4IdBe7nXz+rcoW-cQ813oHl+ZO(^ zaCZ77V|#{fFNPX^la+{TR>&o+&k}~KU+DDItnlZ~pVewGk7q}Z!z*%`K5o>0y8-Hy zp(Z5!Rf^21=c3i)RS73;NhW#TIliXX&D4`2;m%P}K-oeg@3=Iq@x?s3IBF>h?>nHBy& zktNj>kS*;2*!vsWe_u)SxP3(H-;^mFP{qanp4Xkme43!Iz@K*0*RP|`0$eKurK)~M z@(c8>i+X?JOW88KoNyz6SLP+$OdRj1^kZ;o#4)4hBn&pdwS3MGF$S~35!WsFV_`?9H!Snd7M5&@SArExyUDi6vB!%9EiLuGjYe zw+{RrE%862y>)GWNoct%{!w!;YuP^*=M5T1CBfFP-@Ejkj9e4xImMdK66DL0Ync|X zMkZwAV2nxEeQN$Wp3*!MlXTA-l9h8ck041kJ8qttU_jwI6~E(lr{_Gg4e?uMGA`L* z=LeR9>!t#BDB&|E@zR{B%mH8WFjj8qX$#5d6*qVm_2?;Q7g*y2k9ljH)(VeO@|D)YkeRS>;l_x-m+T?p#ql&p@wzwYsp2gxqcqcO=J}V7X|O+v zqmx#TuVR((JrNb1wuoObt-Tm3_YvS1L{V$tL11f51D4WrVVCDMOv7TK@r{4uLG;^s z_@T<3E|UFQIxlXCm_Yge4I5R|(vN+Zg_5?heo%>Au!464Y{)9FF*nXpsu{m0qj8i5 zYrna0f9DS(uB+cKvLW_GT zsp~l=bPjQ|L6Te>z*ZVPR}Wt0r93~;=b=i$i;rSp{Mf+7HL&ezY(c2~DPW!`oQbxM z>G!pt`E21K)?`Nr^bf^0igiL-yA>LSRJdOI&;R?cVLkslEQ&j&`#Y@Oe{RG2#_hgj z{h{0Hek0I_Idjd^ljEe+5nR5{8#pRc8;>-+-cvu~v}Y_>|6CL^G9DO1GY^3Dtr zxy#B0@Hhn|fU4%;fJ1n1VYt~d^W{CnrfFNR?;O$nK92O`D5&vwm!F{*)erdneCqUY zf2Ta8_-BG6ywfnrI}PLit6>*^W77w>iWv8gcfqZ05&_s?NwWr__U1Hs&6!r}(t89v zlz)YS^v|TiK3!s4-M6|@Z03G4T88nGRK)_fQFr~qrUK)E;Cl<)s@iB3?Z(cen&{z- zo!PbqsRsjXd;DOtr8B)Z=+A$oIqIvWP?DpY#aVY*NYK>I5}xnyzvX+Xd}T6ccIn8J zb%P?7<(l}!)$`sX2{!>$*2ls9M6ucG>YE8^9Cor&95%{(TmCp3t1gIzqzLl`7<>o9<2B|#9`rtHbO4`QKRN-w_jjHYsVduP?&ow>+C8Pv|6_Yni9h4w z{1zt*((@|E{;DWkm&T>oZ6)e@&wlsGJPjH%*&pClL`Un|(v3Uj*}A7L?eBoH+FGKU zr{Y&hzk_7 zNg@6#ZPpn-{!3Svv+Bevq1BNxLeBMr(|=-Kk-Itk(e07U_3sdl{xyV2l$a=wfe)|) zLwyrGLy7?IWn4{8?!Ux9A(EqxbzCQ`p)r*F6@N4dP1D1r%Jk*qXSx7IW^xY4nhG8$1l*1sP3NFY5$kxWrtlKKlyh+xdOkBO(n{+ zSrbS6EcyPU;%E3H3mzptQ&pCpnY0wL?f2=8<2gwiTPmr|XR5*zEr(r(2=S2T%AvsH zl;GDGEcNlz1Wle%3OjNe51C3o4PCel*@7aS!rKK(NWeWZxxa{;Bxz@gVMWzdBS+7_ zJ`1|k+$*#@{?JEd*r|HxsQT)xD=zQ06KspnnNdkYsS>8NO|IMbI&qF3v9Ju8&%eDb zJOX^g58Af?5H4(ajcI=)3qtn%YI55@tF`7-71C```#-OV1u=EtXC8j~J$_MQInweO z%uj-gtg2xwZ%sApU0C?=YA^bywL?2S$R19MZ=H0zCQL2F3yfWj9(Q5x)5li6(-zVq zTQ8%QDqckfKUl8IAqo<7T2|~e%yD<(XI)qbynKrnKYc62)IVUfW2+w)x4I zAEY730?crO%mNZj(jI(zU6qkQ`bQhODJj{kh`ngn8gMmI(}?dRc-EW^FFVm z!B=da;F`W{Kk@IZrT#~Lq3L4n=wY>wOyqz26iZdBvN?$3|=P>w*EV0W! zUu}?#G8z(Vr>7_ICKafAzT(Fj z8c>!Wfu_e(viK*STzK`jrk3@*g)`pU^Isx`aj|8;KVb$&p(WXm@Vj=eNbKN6KG4GU z3*s&fyw?C>Bxjk`DicxDV2wLoR?vG3A*GBk|E~6xSd0c zH|RZ7RcG|%ZgKQlgv7@h@S!d?8N?YHB{P}>xeqgAN*Nx6Tu*SaAA_#Cn%99t%-hc{ z%?#;p-i3*!^z&IUo{-)n6A&50hmrP_Y)GdvvrRLEEmoCf8&O?7^;#mcM6;7E_oFT| ztD0V!$gGPKt)^6K!dQ^uyC-Jc3OMCVk;~gdiiGyBRSZc7yAK>&X5V#=ru1OW%Y?@u z*bN<=XpilT*7+`3JvcfOGZq)I=TO&w)#M}F0>yv0nCr8c63;Ykk+hz1jw+>N_OJR- zyf$hC?oMY9L=pwf)#85SbD4niHEbb+HEm9lU8P=kuyl7#A6@ZJ(M9j9O9wgtY%ko9v+ zll3b=8;(BXGSuae!z*>?j;#s$y@>fKDlic(0N;OLc70>DG3T*MuG+n;SF7DFd?OA? zXwiX~pT6V!XN5;B8ug4Eje>ht-lV(oB_WxNP_)pTb+e^q^~D43<%bewZ8Ms)I$D~$ zy7ALsknO1jSUm9% z%}~FZ>y_Ie^iga+PLCe8F+ufHNk&1_w)h{b&tw?Au7+`x&()Rvo@}U1Ah#+xY@XH7 zn=CD>oh|2tRWlhSBDEhdr}wOkf=7;wdi*(`tm0*Eo!E9qFOEnG^<09dUhoXtk`Br* z@49gF_Kq`tD9)0@$;KA#`D!l5@$nU-I#)muz~P|@2~Nr0issSi+}3_XSIV(!OTm_u z@#~XBSosN7q)A1Qe6G-$Rha&4d&9!{;&QO4+F=sXrf36UNAR$`rlejnEhNSG-e0*! zsRs2*?fo@rlw`YerBM6n|H?L~&m8&jeE3z$g(keO7r3*V6)(I2Dltq*?&JRxojg~! zi}<#8s_`7}J>&g`bHybTi8niZZ}0$1l$6tcTO#Y1#mnuto2^H~c4GihS*|CR)Ozt9 zN*C86k(r>Y2Q9FR1yx=Fpt}}EF^UUV77jOl($KDKT_u*25`02^oQ~Or@mfwwPU=#L zj%-ZkPvu6|IYqbXrHFW%(LlUYq}G>YrKI4`(so?+N)b#m4kGPWa|-i6Xgh?2>Soai z=&k)6C{p7V`Yoc~YtdQt6_hWK@}GU5W!GRmZ9%npXYgc+4Pl2n=oW5oWj+u2EN#!> zB*2u#YBd9Xm$q{GO^CbUt&91U759@b*%by0xUb%`%M=323D3Jo)+IN#sH}@(hpz}c zzh0}zqASVZ2PviWt}{y!$h##letkBC%TbmQc<6S}PDK=UGZpuc^ zu5gH*H_2bhY*aDetcVJg9?RcJty3ITOhTgkvR1miQuc{&QlW@8Yu*|qc&uw-_g5Hy z!`AYs{0xfvHUN!JdA=jYviLIJX{RPW=X5ix1aIY+;3caYs%V#(Yd#yL%>AT#gP#nn zM@NENc2WJl`gmZ1;1b^epoTnUtRiQH5}Fahv8%85CwRC6AM%pYC(Yg%?N@yX zO4$~TH~PfDbDZH}(b(iRMx_wg=Bj*H9>nD{{b9-u&_LTUTZfY8oEz|ign4vEVKOCU za-oQSDAhrv3&RI=IR{v^IH;X4+ilG2>qV}r=y-t6lp&z{tgGsaGI7eRP29`bQARtf z$hXCI)FYvWQipK_x*AmD*I*tMwTcnhMRw+rlA=1x%RF4InjC-&{ zxE9Bo2+oQ;?7wM04VHvS`wg?$WO1#BFM^7~sBJR$9Uhx0vZ=rB9vW0E3V&qD9umZf ze1WxRVxPo=4pcW#bwme^6YNrC?G#EQJPv1QETZ@qdWS>c^H8x z`PkPSB5i|VU(g^L;lukaJ(M0vQNNpb6w)#C!?%pqjf~8@Vx&LqmtR($lq}Xra+Y6C zK9eh+#h;)Nv*4SQ!@-ZuMM|rqVwOhHPC>BvuHW7h-yDy(+vS|3equjL>V(7F?KJh- z{ir)qO{nLNfDY%wpE&DJ;`e$#_wT)qgdVBUc1+#0U#Q>T*NQyjbd`HX@21fsxB_wi zTsnA!nbE3x85lL}U{eHZpi$l#8Xkj`b2^Mn%mF<=FFT;@)7WbBf*fR104qQhcB31( zv=4WGan(iPOUdP&kkTpHm=Rak53!qdEn~ywpY?CE2LOt!h^k8Fr#z?LBAPg0hc&&Q zP3o6Hq1=(IeQFbmMG?RGJJ=prsI7PW)Qx6#d6A;)ol*6? z%jPk8IcyY09#AEY3B5cn2|qnPDLJn?DDQxF#+aIK809xSw$PxX`|z1O=J(=OrZHhC zl}Ei-O%ES+d###&{}wvKZ|-=H{5lLW9?308N(g5G5x|^FPGS1U=u0G93u^3RZc(_c zGXJqu353KT$K7OV_t(ebna!3H=($T*xE-u*9QR4c`VlnAp}M0hUgK$Te8h3#kHBN5 z^-X~H3zXX&Cw*DA<4QzZtG6Jou65`kO771Is-4{{jL3ahX|i+bxV2gw{9MF+(n93R zg&*g0U>}wrQ!Em-8%3m0q_Bx#W#en!`L`qQ1`44WDihq{Dg8b~B8yj!rn8h1GTn#MxVY;>^K@vy!wJIHlhJ7^tOP_NJU zCOvv}38mqJDMrWB+T=y#bO{}np-Y!s#~J!TG8txdlK66r1vE#xRkA-di+zjSyBS6% znKgJa*Ru-h=rrg0A>gE-y#uT_=tT(KN%P0TTULP69d7ISgegmR)-@@s%D}k8>e%IZ zIc1dfE`K8y&k<`+d92Ps6hA$ve*WtPR--zq0LNmTMe~P+A}-5!YFx%EsrY4!F2Rz* zdyjIa$@^}rXRMw^J`~-?2J|uzuUDi2Y+JPR_vurPHQ$}sIo0ete$(#xIIFX|bzuKk zc`b%1w$cahtI~5s(-=f)$5jy2?%E@BR(a@%j!zCPd_~0n0R9Qy&Z?t84ma_v?q!(h8%@ZbIlqQ!>`m&*MfBW%d# ze*9OHHpi^n6TG`sjI)xHgb{^8rY#%`+wd0bX(><0ZvY7AMdCIxX>mcO?)hjDqUJ&j z9Giy}jic3xB5F6gGPx(YIr<)lcUtSUv>$}rDR>$meFuce;{}tGV z-R{GjOgR?ewzFk@Q7!aIr1AFx%<5C3dB_9EM8eFx^;Ckp<)N;YzuvU^tG*G~0jjex zxhIwl)l6l;ayxf`SH&F9wNf&RHOf?1X>A1c$Z!13QyoqP!K~7}8dx@^Vg75~uFva= z)_~I{6e4@ZRN58Ul`{!5vpP1WQreS#%uXVW2l}w0o@%Ju&R9e;WukC+OkmsjuZvs< zL(HyJTFjGoUOf0_OoP$)tuXq#zrxoR@`WakF__B-H7%Trvxq%29wY=^SUwOuvb#qc z4o9cvePT~uuj6<14%;&AAX>_xxOeX3STC#hf#nhH?K0he1@}MZn)(H@$mu<9^E5AW zH~-+zXt&`OQ>1IzQ78LgM7WvJW9CT}`nA2`avD{#CTKSV08Gv71A$8fbTLZErF6E+JA6xS2NDOzT6 z-(U_jMDmz=xpVXyUiWXp(BBwTyg_HF-*Gc3$vW7C$y4!T(35d104(O6ggf{5Bd9S~ zCHxmaP>VJUtqJ%ah)0!WnDZiM%^;KZW~-ART9VSPXpP{kfV)n$wO{|7+Nt`y-o8G6 zHzTv%U$nsth{JyPNagtL>9@(0S){$%VXj|+X1bbI z(75N`ET~@m0dt!Og@sOF8pS&aM1_7uiPe@~o@F@icjM@JpV-cr>F%iXFh%o*ZY(+C z7*XPt?tE5d5ppSeAD>5lR{i0@tRJinF>w}ssV3q&GOq6%lX?16vjd#aXE8Wg{O$vk6T?{gaJ3&!h+sL`2`sj%` z!d_=EV&9|vTxZKh;p#-6-wB%Ym3Q9Ab+n9-bq(V=&;jzDT6IQ5vN~cv=kxoF@8Wg= zJ{BIon>tw|T4Jj_O!=H@=HKwlXMP171^N&nhY*nq%v2M$AmqpKYa#CKE5TwCp&NoX zeo3BK#2KTB_s5a!LHw7WlWO?M?2OqFwG<+S$b6nP-Coljy)QTVV!y6Cat$4{3%^YZ zYj+A&ZKsubMi@jE%PePunh1Qm@!rZ)3S!Et4>&VxWPdDMDXF|Hp=GIO+S_-mP4VWC z3;Ni%usPnzPqMJ_eHNd02$;l?f*#kZhF02P{nRY@gj$-f%GQV}U7_pvun;h&+^4GgN`~W^g`X~s zXp8*y6F|u#W<0lDBbmr}5B5pzLM35@-zqrso6n6;n5md3(>j*d8oVimJlT`Ms{fwO z{RU9~z4$f($kfGbEz~#oNl?^BoCti$E#n>tFu2T(6Iv=wyVhBPm!w3w(QOsmT%gzg zd26IsWFrF~(~;99G1Pf0=^(gs{s;UiWE#_4`pDv@*qQr?KYj zdH2Y-$fYd%>*Mr(qR?3x`)=ZK1?Gv8isYUN^VFi{wb*dIe%#7=N7?G%kqmFwI@v=K zW%YViXqX|uC}u!`GSB;0j4B**mc({=G)Fr==j7wr-cx~Pis+?8X^QB#V)ZUP7+=i2 zXbfz9ABq1*w8S~b%{=ek_AsVv6B)mLyPRd_Q0+9vEd6Mr(NC54`9D*%ST`S8k~$m9 zGYq8X?CQoDZ%Lta?k+=p>evn!LL7|qc-Y3Spb8F)8~D(o=60QIDno}J%Jh#f-l5&j zpWc6G(V07E{O9gy2JI{mDXi(=GU|2ZKL-<<9+qv9pQe0dY4!M8RKve`i6p{8N7S{R zcbQw1YvU6+B&xanyc=D z6qOmfawrX*S&0C9FR($GkvhKBYs|&R-4mX8U;V5L%Go$071wq#c`*7+Zb|Whmavd3 zA2V%_aVS;4+6~W#di&FKE0P)$|`m`l2(uRpdakOjuRY5EG0 z_(1DI1uF<^!J8A-BE&oG?OL|eF~G?iW>(0KLg6k>{Lh`5f$mrjXk4Dhl`Sma*oZL4 zpF!H@TeO61GTC~+uY1ix7IVJh*TG8pS(I!o&Uiv({cp{c>ZK1XzN=2G4Tb>OQz;G2M{aek9PLu1uf=SFog^t1_U6>WspL-E&jg zQh86m5lQs4zNp68k@xCG3^q6=s#ug%6q-~6nwL)so-=jVdB4nJXmn?knSVt|ap#g> z-37y1SfE+q(vDZQhhxxcmU;{4-mkG0R`obq9Bx3h!^_DC{Ct{L5WG*Q4$@~8;|+9h zdMDIB%4OZ1V~XoA0_H;02bMiEJ9gMG&AK|_+u!jVjPXv|@m+&^w-u|FHX2T9FD!gS zE-|zsnxEi!$A1gE<|X-tz#F6t)C^VuV}5F5u<5_2h!)qT50T09l64F4(b7#w)|XFJ zty?c6#Oz`qc=q#(tmtFe-pBM0ArCyEx(VM*Y&dej(k$ zSmMt)m>VjryfZ3NIyq%?p-IIo^(u=$=A^XW1@y$z&NF!$;F!{ql5HsiD|I>lO1c-1 zHFGAv`hO`s$VoM{1AZp*>4ypJysa}!jntR-DKY#K;3MmXGQfz&3rBI+-n|(vHtp~V z^GCWh!-^fA8;_bB2vS1y{o9lqa~<{bUcV~V#<0S8^WZkP`g|%YR*Gr<6n{OkNf*WU zhCn5>*p<7c$9EZR^5tvkx4d-<)~HCmif=?&wNcpXZ$dlb3h#SXH0RDUl^UwZ4euzI z^v-iV9Fw1k!{RPQ6@&75U`tnf?Q{o=&Fra9`t2E(?1Cv zFTVgsk0OaEr8VY1-yGOc)@qBkh7~w3v?fj{HUl8~TT6cvS%Wp-4g7^XtfH&Gd2Gq? zIVz9Ul#1$k;_?=oL7$enWYpkR)(htleRij73N1V?Q?VfS>(Q=<W{T7Xim0UIXlhOlw7AM_&Ga~lZN7e8O1!LYx zj{VMDr~i6pR8JdJ;&UQ<16eMVDl@70I5urR|3hxWET}7miuFD-={N3O)k}3aDerE- z#WpUF!qSFYb!U%z*XQ^^ThQMkOsZy?3Yq5`s*IG33WvU~!PDA2;E5loTP6quA^yA* zoeLMs+lWaG#t&n_V!4-h$xLX(OEF_)x z3n8uwIbh1FlMON&g;h4huAO^Vh4yo6xLO!-9y010a)Oxq_PX$@gL!$_&z%Z`*BDP5 zqI-1b10&?*fyGYzq0aANgFJ66Tyw7uW*>b1q1%xvXFlYTO;vz40La|(o2EG64AS|c zR~=!zFcS5j^263bUHSN!X`B1_HQ-Pj^se};%;$*3B?e_;a(V;z8rWuBK_l{!oE2GI zfy@k7alOvq+pl{wSuy%pla;uRKZ|+rd)3c)@ZW5GgVMDoaZUC;Ve|j0IGT@K9Oodi zaMs;b2JG8g!zAKLevnsQ^yj+Um>M83@X14C6|SF~YB7l%FZ)GlMkh=W4<%E6m?%?dz|#Zd^Ji#& zM|o4H0o+m&5$93y*+lV9GN;PKT7L8+v|9tL>}L*_u|Dts8&%wl$NM%>3kLSUNhjeDG z=PlhAP`z?1p_X2EW+wq>`2xi>pnN#Z^+%82;nDm!Z5b-Zp|KiiwDc`EZ!A4HbAHh& z)Z^tYQ}-6m-rO}YmX9*Cf~f& zqN)|`Dt-@>dc?JxF7)CZ`ki^*L4%f3?ta{@Zv%GrJtOq&UkswQ6d17l-BJMJQq#G) zj#QP2;FLu*QI|pO9_ZqiwUa}=N4@2zL!In;^^%_fb?V{m4V|xEep~Sz7_aky8c)m> zUJod_{um$u3$+BDFPXdV4UmC!FB3)zch4wI3Bz6pAL@ZC*dYBEY|EVipQUTxpog@S zKaE1Iv_}twcj+O;emk{gtcLpL#Rxt9xtf=9lTZ^->RX3mZxoPujJi!)hvH5!?}6 zHrDkes~6*Zb0l?UNXD-ZGx7urxAC~~*>QOYe^(WsUe$vykp)OgH!JsTC5n@Z8)qd7 z5%=}prc$h{0`z<|X4dVxszTKo*WLaWd@;vDj#$ke2t(V6k-$=NO- zRX@9Im6)Zu z7Bwjq-{Er(=ijFj^V$%IdyHYWir+zjqWP~^o3aT3TG-S30$-|2W>61<<2x4~F99bu zcU|q)@NSSR%Qk))UhzSti0eXc(}=%^1C86Zn-$XXs$gJ8v|+D#{zZ`f;iJ;`BTRxt zyWiOYIlhmF4h4yK99X1CdUdekNo!{m@u+ z?vj6RlH7UoEQJLFh1oc2vS7Pwz;g*knUBT&~0}U|5YX3wPCSVotSJrGGGh7kdVB z{XI4>Xzh_Zl`24btCklRzxam!5zSEP;6iu5{^30)J?#N%4geb=y@9}fH)){e)^69T z9H7vJ@xyC%78Tf0u6|!S!0lKl1Pr$;Tbf^`S89biJ6w%PL^7bU0~~1{fswDCddJ}k zKK>3vT^q@AxC#+km-K@08`01=^f0^9XnDuU^AN^?mUozW9f25!wo7pn;ha=)g0KYV z5C&vk*u77H$C!@1!}k(!6L1e$Zv`fKe~YHMarYhBt=z<~F;G{681;1|NharHd?@EJ z3r%7O{B+4&c}=B5k!y~+Q!ef!%xt-+zMYw9U~Dy3kAA(58MZxq4bID&0KH>TRlGq* z&UC$FCdr0VHBlW5xCZhet()F4($eEb0~zFceVOOs$5CH&LC3`4IYyOENejL9wTLsZ zMM(S2^Y3X2Ov1l#54e^t5P8lmtx1ec%Bppb%5eqHg7*i_0UX_rt3BGSD=J_$G{N-9 zJhAU@2YfWZ5Dd*T?aN7+Agqc~pl&VQAXA;jZddSHks#h5qbST-eGzW%<%$N;oQyhL!2kR9@8|I@8(&73gPA@)vCDYFr7l(u!dm<)XR-Meqr*n% zY__&`(O&KEn?zb7e!E=|sUa~zpA;MFaiN*9n6W&>;R0|0J~aMI{~tZOy^!wZ@Yp~f zLW@rm%+g?NmEyiIx)>=fj^i}3YE|;fyc2@t#!!)3hqxQ5G(p_$RlH%0(%GKp9PVM+ zv1v)iJF_nJWKD99TY+s6jX(_0Bd{?O3P}nSVnfjn7#416d&AO?!}0z^C;cDB-a0DE z=<63(5l|^<0SQ66yF(i3?vU<=p+lskML@biK$-!DA*34wq`NzY&f(60-}k+D{ocFo z@1I#S&-1J``#F1mK0D4n=Y)YiGX#K!_NCZ<3%O5CQ=oR}SztVILDV59Sn3DAc99@_ z=F({|HjwZW)`jd0`K_n@H4JoY8VLHD7mMl$nu<3Y#&6L%a<}KVVQwcwgX-PhPQuQl zPkE-t;GxV^)3_`aP3>J>;o zO0=+8HS?vTj08#fFboS6??ri^Z9MgjM}YNwr@r+%Kq+|2d!a5rix(k>&}lK#ZsDyp zD|04@{(rZA1i03ETHv_AMEn);0s%~9^;&y)z5}Uw??ROiR6lc_g6z;zknuTvyq9(f zvUnyuc*#-{4@xcx#9z!?FuYleC$x`rP#J!Edlye=8||RtV0$$pBhkDYr02s;@hyUy zl#&7eo)cLh$To7Ob&~5h-PCR05Y3N>dx|hG-a|AgdR2U1(zBN2OJdEJ{T)FH1hL_V z=FgKaA)C$jcT}MR2np! zcvh5_N0i*WNKbEZQz8OjXTCc(4959qtVnP^c>Nsy+AIGt)+|sKi0lRYM#q1MBEZUQ z(dFQ7AVw{^J7)Gu>-jrgqr6WzYXMG%=amm@6UIRiu1I8{ON9-G&6V!W0BZM0Yf3_K z-@#6B1OHWk6|nx+{i%%K?EOg?OG6|5OgI(RXJsm{i*~uO?{$9rp;xIFE+=(z< z;ZKgUdb3Cybf`(5H&I7#5Hj$L1^<``GlGo2!Kzy@#KJGF`hF#Y{{0M1&_96v4>CWH z+iUoZFoX$-FM;z>;#2J7;HAySqq`BmQ+*NYkHo6*E{DhWy(Nr&hnoT7pbqIOwZtSa6R+Qs>|<77syu4N`EJ_NAO@L1!Jxy)55~6 zTjcdFvHsrSMys9xWLx{vd0{BRyLn;o?2Opnhl(WqxgHhCc0565xYUr36Tron9$Og7 zO@8mY6Y*pBBI6qBU}5>a<1ak%wNFgK5{(jRLX%>|%uX>ogD z&!oF(INYHrU!RJ?dy<|OPP&-&;hQg*TG6k7_=oS3=#AdDJKq9X{Kh2^EwyEYAQmk zUZWlp6TZX#q4>)K7U7dXPeVs~MG7l-gnBOB=bE52h(T1Q#sNQNxA4E^#C()9{J-TS z|0m~iW^z;2$qJbF(1PUnG6uE)pzMBUt`t|K<>20Hlj+*-9`HU80yt1OK)bBe^8vo* z!WV9~IBju;=-ThqTN8Nvz~hMe>>bYZJD$mStUV&b`Aii}k>Bs~yV+87iLU+aN2+F> zuvod}^t+!bYju8evbq)Z+gw^dBmxb!Xck`(`6-?x)d-m_2!PWd6 z9t4E_@3#2@ei`#$P=~&RB8FR1o+fWe{olygs^_3CUVSg$2^corh<2Tw% z4Jwkf3uv_5?V8Lb@y0d6jr)hB$e?mN5qv3njPUb)QI|Ugxb3}1d+@UV5$J!ow2Y=_ z4#Q{@$NLcwKQGEHJLr<|gDq2r*OrtsWGPRU(s$P_zQT8N->Nyo^^CnOo@r&Y-NxsN zk>0s)-Q$+Wa+dSutd~9|@#OoHJ2?Rl|I{DhQVph5nHNxLD!(}LS0>|fELfA1j->zKPARYU& zxJ9>;iwCYBsY&_o?Q6O0$E30LXJIQ7WZ+~dO#tT@@_)om@c+F;oP%1{_{pE!;$AqO zUm!|49gJ;pegQau`Y@4f6A}vpw*)sVnfwCq20O>lja(q{x&TOqE3p8EVndktUs;lP zqqXlgvA4pCAV=%~^OZOfn1?0JO8*h=Uz6iHH#>Vt@MiISkl_BE1{G9uFc`}BoE5*; z0iP8=+ZT2M^(^!0{`K2TNlt;2KcGAO#X*YcW_`=D%m>nTec&Xne-ZanIHdmT(xM;Z(v88ImVU;-w!5!&NNmM6Irc(l4Ks@cA`+_5W4_Vty9TM7K^B2z04U6)mK^3uD=iiMJNAJ$$d*C1w@b02AdY%eNjRmbM6GR5o7LvK zfPN=kKucK$^ME*abatI|U#Gf|MHruUJ9$`Lb~t~B9DiV$uwhrPK2=L*nVKk-uyRD{ z;H=-5QM#Tu#-s^stHGg()`C%9Fl! z_0ODsTs~)OxrbBz9jbqW81|aba@Jh%nz9HFj653!^w&r2r7%{H&H#uu=Dr@ng+@^^ z`UvIfzhC6n3F_+BG<#?li3_L8HOG55qw2S~i-u{JK5+<=P^#*B4fKhQu!+MFE4SDC zAtOlYpRO*_&_@F@PE2(vdajz}=bHD4BE~2x%CG=y`iEXwp;$1DZmN?@%Exk#Y)05;EaOpMVT!TvNISH`e+T?sSu1)v#%X75qfjA%V=`r55jORh|otALZgE z2DYh-{k@p<;`>=x;o7M8#;X3}vt?%?fe{n;;+mzeps=LFJ#YBvFwPa57i;NTccHah z=!8Jj%bEI@D4cd1qKpNx`zE1U40H8SXuuyTRMwT2cT7|r#%sBB+dn&3T)heMzE)IA zz1aF9g9l*G!8@swWTY~fX#K49OQ&ZN5BNjcj2_vTwPPl%^Gh*i#Mww{@ZT477VmA2 z#&J=sVVh)N$w{zww-sBw>@N6h|xRvVPc5kHrvI&O)Cp5|L?D*xdN zrmU3ke1rt=u`&{MjL!3-!my;umK%=T3}!_x$-~oZ-%TKvW}+jfV(PhuQ`^vgN=Pl(LOPmetTH7CXcQKz6t~Y=|k`UXFn7+P9-p{av#+G z%Q3MxJ@Q$h)tF@O&9=}$&aQtI!<0|=c)sGz)}gJV+q6e&_Qmu!r7``a;9_K}gHjiO zioD5=S0&Ay$8e{SOW=(ExlHM5-3!9Jmt0cKgF&hce~tMdKRbWZ;#spcz4>cQ&}U*A z)~UsZN}(KKMoW&@K1A}4Y;mKN7Vp@OB+WP*Z`JN6t_TB?LZwoK6L&UVe_Y*#Btr=Q zAyH2B*t+#Z%y}Ktw;Ymhy>(}UN`6&)?F`q=ox`|Q@?tURFSry{OJDDs=MgWJ>v|gs z&-C84Etp10(L=BN0hX=wQEOaW+!hA~sAh{|UaS!Yrz4Ao-sex!#)T1?XK!r03GkZ# zM)A8{Cb9v>DzP0l?HhngPnt3lZRg_~nl+`7@N@J52h zl-W;V@#51&yct1XZ8O_d?wW)ObmDqMZ_ueB40ci@x;4G`;akrRjd9UA`Jbg}bIQ7b z@hlx0-#VGfwvt(SUWyGxRx0wod!ic`V@{h>%N8ka$0?bZ9XUler^So}#DzU!i)&qs z9W!GcizLX7&PAG6`J#ALQ+6;8zch?v&V_aEvXi`kX`BO4=)H?%l_*^M;uWWF?U!u8 zs-0#MKhgD8k`N~fsOqGbtpWNW>0U9{xa3R6ANu?VR*z%|+s1?px0q^51MAueAZxd zl7Ha+KHp$-Ti8E==TeLsRM9%9{N7)ls5D7K^&2W;NCsOFBHqC#F5llrYoYeSeowYJS>5#bl=R&4Cgv9b-}g{utaAz1kCmDvp^*sNX2 zwN6lJlqFi@F1fuQJz8kc34CY0q0rv*fzW?Joz@F^eQ z@zQ5Gv>KB-gGC=zdSym+$|hHd%C(^JiwQH@osMDO*$!XdlBmVvP!m%Ojm+6{eD-@c z`tHF&cOjcsJyNn7*SbWKOg_`C-*TAr67qkhDO0?_g4Mv@mR_c^e;$NzT6{_IE8#@| z{*V)LKYxK>=n~?(e38m-jY!31Ax>yb86Ee|Cd3&ZyQlqTS}jZ^S{gs($NbExtqgPf zK+H4X?!kWJ%AUKEVzCZr>V}si2*t2(oiLQ&>$wh48&eo#bK zDrSFj%^ANrkHAN`P>(@Nl;<>ej-=5h|91xJUL#`p#IZtV37|C=M`~$0| zyQc<=0xj-L*N*UKDOIgiR&B~uL7;EjOED@^pgIDxbxAnH6^LOs?ny4iwk}C?W7m%Wb&ksNf4UB@ zC#ViU4sI*eI45Q#zmV3)326y?b;9`|y5$bAqxqg5Q#@e%q0B<2iuh19c!(WLiycQ7 z###rjIx1JYV=`Qw4ON;hdHsn2V=U@rX_J^yrW5C0%`^!8B9+JQc0{+wLTj*jbO}9; z>$Gpm83-tuwOq~v?B^P}zf(NbXhaLwVY|x5@?yPAVPA1;UQGM27-}~p!cY)*3+4mB zwEX?!lFbm!w>D0T3Lx%BlgW>m?=doqC%U@Eeqxh?_m_*Q4w%Pybu4O&jJ?4-nG%_W@j9Zp%yTm188@Otr*h|6p-&sPqwZznWHC!T?rL- zGXutxE$=oXEPTE(&G`?ld!Q6r>wM-?#aEpyBpAlGpcjAR$+yQ}o38XR3ye!1P{pg>}q-{&^#!P9rd!{UI&+WaY{SWI_dMKZx(jn zjKPc@7M0$alX>DwxJe&DwYT;2@;wd|?d#LA$hV#tQb4thAwK8}iioWB7-XwsGmdY& zR}2WDKLv@k_{hf3Z0x?kSc!2Odambbxx+m6@4k^rO-!QTim_!+55e- z!{#Zk`QiAexz=oDEmuV%$$fEJiSaKx(OfTZIa=n8GxcP=nZDeT0@JiW?hEAMlN&7H zM~&KQm1qVpC;w8X%hzrJ2|-E56KNMw0^HpEpmwV}Y@Rhw6Iq1Aw2QCW?n2GT!b5X@ z`%s+)q`o-!iEjzvI-3`~9y^^q8<6qg%fFpp=EmJl^JL!Cmqe7cShxdU*O$l^hxu?A zc1_}$uJUWGxasNdT#3`Q0B5{&Yp*p@k!I>5Y{q^IEq@6(-B0zulo>QMvl~xv0;n|wh1DQT~2+mZIACUMk=fmb^Y3A3x z&~{~RK%t1>usC24!c~hmL}g#R%mz8Rao5BB=_^dFzrH0CY&#S9F)qS8vOTz1c%V;> z&rgrmWg^r%yB{nkc4zzCdQ2Usie4hJ9sHwtKJA@ea5=A7%`+IKDgjFmAGL>>8w%6j zs1hx9-cm~oxu*wFi)oYw`T>=aGIb0f_IV}(z!~?K)e<7WnbH4OvMIsV z@*8^}z+HcE5C=Vp`b%9zC5&JqQh>VRvbA4#Mw#=<4;{KRE#q8|-Hu8%Af7_B#yAK5 zWUT&J%N|IJsdih8rb4=k0z%A-qOLFMk zc<21zKFnN;RpcrNZVnl8`S&YB`er!h;vX@WRavesoFe zhGL>J8n+XmqA`mLVXul{vrM7tjWU z4K+?FjH@()Ol!n}pqp(UyH-2#Z=J!TqroulTyt(&OvAJnsDyb$Eo^Rg=!N_X z-#VqC>CMctked=Go`xLHOObw+msPC2D@G@B`GvA^{2-ia*z6*}d45KXcC;7uU`+=w z%yJy288ALLxNdA`i<3#@nKq}*hXwqrTHD3!F+TXlc4)@$6EjR_t1U@D!JeE{C#cQ- zmned89R}i1#U?#z2q>yl>v&`1NeP8PNF3W0ih(kQ8=L0W0B}Rv96(RvU}wkNrvYbf zoKjnkOM`CNB-C_Ob$sh4vb`DyQi}H>e)7S|!Q9`i&Z0dww>3S0`ak@+Xg&7(Z2vP? zl&Dp&v7aD8tg(Il|5v<7b7Br!kW#U!cvB=!pX&?)Z{^EJ17+IiiyZKg{^SYo;h{!2hcIekj1B zlPR{M+y0pRRr{D1fO}vot*rR>>&Td#v5hy7OviuG6H@i57`epPu5ND0+K-)C;o`S; z?+Wu6O8#AGUFR`Jts|A?$vCwQeBb(Asf!VC1Iy`(&Cs8Z;;HOdz#~qJyy8!(?5`sN zqgD29F_C6K5#_u=EoC34#)JQG_B`ut=l7tp=5%r5yqW6U70`;B>!s9gyrOcZ)@&Xq zL4=_XH-_Weg@GfWpZ)%ik5?#tHUENwm2~BnIG3p^K6KLbtJ-a zi4)zQeVC&r-1`Pt>NNez=WOA8J`$nE?Nt}9YU^vQ)zY=QlO5qyVJeG=KZkXR z1MpuEGh=O>iX>KEB1_t&EghAmXJ3V6==<3ng<5g7R?C0)Tf$oIw@&1tuI=XJ?(xR; zkn;VYaTC)Lk^_Fy(tGQtx~b-)QH)l_u(gF;YrS*}^B%gqVwCz6+UzeC9soz~3ad2U zJm^f0F$?rUKN|!UdL+$=T&66$$&u9~T7FU@;j+57rIGX1LZ@T%`iC2h+ju4yC!L?D zz|1c-(Ucbrcek@L+D0MHpYoO$P;ziH&2fG~DrbG-bKSm-&SsP{>KHaMXrfQT6-1$l zZU|fCdb+lOs)_G-e+8#XE{o@>^k&f8uWQRd>}Rke)Vh1p=0)Wl9DZRT0i0h>{A!7q z>rH^w#CYrDBkDc~b(8fi+{{67&e$M;y|EU(1z^?tQI<9u8QDA-gv=CI+!6)A&A2e7 zh5yk#W-yTYdEJ$muZRs~N;R0K)xk7n{RoY2k`wrFGw0{+5JUspiw&uG89lMPKMvdw z**>`lgIh6Ha}(y=IH#lr1A$R~`jaY1FJbEjF$3FGy*h1t1+HN#()l*k^A3%_V*jE} zxUaJ=fkh@>toc}MS0mT9b_?ee-`|#|I$TfT{vx0>)ULV^&0FpdW+2|7%|O4HL|2YT z&_xPK7BC!`YT8arJ()kdbFCJtsCB71=6ZV!yl_8qJ>_wKR&&rs8*mFNGJGljqvucb zSZqM#z+Z;#GPsEJP?sup5~EP2H`u(Mu$j$8O;h9nkwW+!@mvp>{1yg1Y~rhDufeW& ziiwD+hxHAoR-||7Xk|=94(z8wx>(i?yH!eOWP?p^U$xcSs6Nd{4%xfjwQfjnGBs~R z(k&lyTS$LjNnxjx;L-8d++-@_MaY|p=R7K1%N1_U@$~RMe7;X*AFUyrx~q$l-g8G0ldiX7wnK~efjTB68D3QSt5{bc(x8;%+L`MQ?O$V+HaQd&@xD|e zy=!H6vk51NQ@C}U2bfjn5;RcwlA~5z!MYYFs`q?9Hdzy9vt!~Sj^`kZZ z!;ILo97?+R#mm?3Be0fqu?9bjI(y0Z3c637zy9Az{}~{C-rjD=7{obfOe0J;9yLri{hk zy4{BKN{pw8B^igEIfSjRl&e_(fJ#W4meh%KVP_`R*^;dH3|V7K?VU_UfVh1M_u|$rsRB=_#ED_v5deA1?K=Hzm{(GFuGcAOyj z+;Cm$X6cW`GzOvI-r+8e)8n%4ek-KOc^79z)qP`~(>9*lm5h&=eVEe%-iPl8xoQ#1 zPjiP4>9Gq5{TgZ{D*`VD8{H>nI)Ts5e9#)@=NvZMUIx5|rE%GCu)#nFD+2GpcIDc7 zHFySiECYKD92o6o%(L$z>^~V?3V|J8K8YiZTgm{cou@PEr1%5!-pEq8ovV|do2;9; zEcN1%JU2Q6-FdN|7}hz@&Dm~N7Xx(gRb~81C?!l*o z;mOn1ONqoJMJwdZH*mS;_Qm|@^j-RHgVU^W6nSL36%r@~UpR?IAD~E~&8Ys%XXxfb zN)n38dYK(Ez|D$E<~upS4fj|07$U%p^SEPo>Pq@M`Lu^{sw7FWPn%|J_Wk)+@3Klo zfwny1$XwXO2r6*A10HX}VicJED&;9;mFwvSzEsW*f-;8$>NNsBeykEH1-Y z600}H?oKfuuhTAo&olu)keR*NSQ{QQHLc7BdC4_mKLR(-i-o=8xsXI zL1Yn6XZ#xMyDnTBeq^BRbP4Jgrz=r5UYQ5er0E$ekryYQdVn-m#`1OT)lh&{EtzA3 zQ|#cRcRl2NWZD#l8<1nKX7`C<92n}HE7dq*kqW%h0&{})cCT1noJq48zMof*M0}`% zoG+~9@ z>EK0&p`Vx?lWEG`F*ZXL7n1ydlHdx}Fn=$*+Y0}uD4H&ndFsE1%Xp73H8?Ps;~jR1 z3TJd`Usb#5FNJUBT4n_}cvn4Hk0BJJ(kmC{9A z>v7?{pL#WVB3$*kkl^NvqTj~Tc<7P8FPQiEeBpA2!_gNEE4e7?F58U#kseCGW{#LB zJalo|H#7SGk&&ynu?y3g6Z6Mq&#`+)MtX(02Q&}wG^z(gWtNsVU0^axVO*Yyy?C0k z>rFqQ{ohXUCCB82#;@uu5R?fXfI?lNr0&|rWJ|C{bGgSx^CoYOzq>d0il{GN%3#5p z%Rj!D{`S5o_Pv$pH&topKD=*VINtMae$SH@tqM+qKky|_MpRnis`+sEc7uCW!tCuU zm-23%$pvq_U8u!@jiAAL$o>@m_nos(aZS?)ut1l&V&VAO2S!PuL+%1qWJ;NS4c-OoNK%-Rxj8}a{i zRbLTZm~!7)tTKXCNxe(rKr{D)Ihi_vKAka@@?udKqjqjFrR+!X_nfp}&K4FcWxC$> z#oaa?R$bPedog^D`$m`9EUsY&u^+OQ6LOPcu9A)9d)x|t zweyQ9*|D^lloyUJ(F3{io+^k~5-Ty>0@@d@c14!KN}T?J7u~^zT6_dvRx~s}ku1Ho z8({ATtgL0Y>=>!J6jYoat-BqkH+ZT3%a2K?;UL^2&a~OJ%=TRPp4OD6aIYy4ogGpQ zaU{vB1pn!^B{9Oe_1kX!l!<2g4dl*pDF}{ppckW#BCfxq^4qFt;{L9R!v?)l{7a(S8>23g7-T%0aovNTIDV>hGvIU_3+$`HDe@%z$+kodP z66X;pp7A$Vo|g@S+oIR3&w>Uvwzc~U?tc(uirc-(MDb5WDxl*Ou&vDBToIYbF(>N_ z9N2A}ty@Jkj^{Z+*MhMPG=%V)H{PfU$M<)ay+z|dWqTs{+RxGhV{ZDT7nwS(J_I^} ztP8!NmSxiA(&SU5Gpur?*b2a^R2ji9DZbkE5J7ewxyXi%){T|-hL6dfk$w7oGBJ1y zK@-7Yzl!^rI=vy*L5p0p-b5nu*KO;U4VYcfd+?pYfnX%@uYy2%0n0UNF(ER2al)Rl zo)_@CM-_a|XxAag71N4Lk%9sai5@)Ijj|TBQHi`jfv8B!)p0boBL{Qq!fGd{8h#u1 zZO5OIjVg3o`pIvL8l!@DBVMNW>-OspPuCw&uuRUCpXyh)s9va|pR4+(j`c;Fm2di0 z@2!9IS;Vt{?wd-Fc8&LgUhJtw8JIr+WPhljgrM*3yLeRAY&}h9FpWMtQvy6E`izXrT za9%%rW<;t?=aQ$sfyc{r7CBgJcI?5oO1__(P zHrHeK)j9HWLNm-?s^)B8Jifoc@aWGhd$Dkgea&TnaB4T;_owc+UUq0`%W8|G@$KPd zYhHi%Gr5Lax!-y8e_o&3ZGHMv7suD(D|?$rD%U__`!v4HR-0wJFX?%F*~(d#OTRcB zqRr(3Gqfhlj-=Ve>F~;*dwb^Au|r0ap5MJvi1*@fkxcaBKGiv$hu^jjc(r_3s8`R+ ze9&8JM*@W=g$LXiHj>QU`^Y%nx8`xHlE`_D)nY1og=fx(@ss^|?HZ8za_b4p?pX~< zIY^*15ENT&;Dan~6NFx0iEqJlTdh08WmG2TnFb0`@X9Q0UA%WMux}u(j#ae(GOOre zKYRLudwt#su;_NTw2Dvsvxs;ovvI0+Y0UV}##CTcq9KeUwlEG89$1*dOInkQ$lK(j zA{mmB4{c$z@EiW5c@feGo`k%ZMcAW9El|#jJ<;h*A(L(2wH@e>bU^Jw((v4^;}}Kw zXSA5kk4FyISJp>Ou%in8ysZ5UIlM}pCgip8G%WRKzTVb3%E`?9A}Gd?{{VP;Nwi-KV9%pYwH68P3bi*rEdKZS#IB=^jU=u|@URigQoJ zGUwYcmIpXpM^k@&W}p0X6XWkpkLlfTezl`XZ|zCMn>u~uV$inVEnEi~Un*=grX(Vf z#j*9Oxytt0n7H9T+>7CL9v{6qWl$q+0+xF|Nh}FWI~07Rr8IaXO)duKtAgi`w(R_1 z%cyW$PQm(T%i%w{=gv@K46dI!KZo8;&WzEBP-C4kv)~`-w`ldIShTIv8$NSk^h8$S z2(ftK%2Hc_KL3gd3t6`{hHF`ct))78Mcc&5T?_lybrSs2auE)}g#O8N=K_LAxF|FR!JuHEpoJDsrbrtM}~Cztbt z@I!!&s&3R79;4n@Z(uFf&~B^GH!(iW?Zcif`GKHU+$#eTmm1P2Hpr_-s06WDm21Q- zlYL2T>oytT39s#iJj}qSo=2k@8W>IY;(W1TmpbKP+uS&uQ{Gd2LV3o}XHizPOxZm#X?mbX!X z(~AWBS_97_KQEi#W@blea%&0y-ujlT*}ZqD7JY7yCuEZD9N1_TOE&hKQys8E&F)Ya z|0&WF?D}2dpw@u+7bBUV`P8PEN&3?|#t-WjKVRAPNa2Z%F9R7$32*b^*zY;)Z;Q+9FMV3I+qS@( z>OkXOy?+vCMA2*|G!<6oeCktzUojdqlzv9V+*SvyRTQ_ zuzgqITp9UP@P?nT_$eO|DayUMLe&tgIg;=p`d@=55E4FS1MBe!rMAm&UrFP*55RK z`BhBkt8PLa*qd!grXq#0LZU-bA@Lt;+}~X9VER{JE=rhe?z!%yO;g?(m1>RW#TDpX zULBMd=q+uu=zr{qN&d(WG3lS}oWHAF-*@JUCU=+|2l@}yd3H1V>)iU$5^TcSc%vSY z&B;^#sC2;yYhHM_mrPw2Y!nhb=ncU;L63Sbqk{ z_)d%e&MxWlhOGW2GlZQ8GDtlX^A0N6BR=He^pa4O#vv)=^XIMuQR@DrU@0*SsW_(v z*e1_B4`lQZJQZL)l2O$I8AZZxquvxd; zn$D62Noom%rN?A3xK^Xe+XgRtbD~;OkZn7N44)H0n!dMlt+ZZ<{5A!U7wDJ|99|I&5C3fZ@1NoDVyc3VwawRmL&{BV&5L#$)zmTs z71}*5X9v7Iyrt%Ixp3G=ig9Q5yNze^$$4_nB+R60q&pKkc&Tb+Xf6l9)@%tZYy zdF$=YfHD;(ynZ|ubvd$WQ8NhBxy{Z&2S&>Uc5)qfZ?OE>-EMyP_Qzd%Z`&fz&$u}U zzDHY|dl)U0ERdhmA#5H1=^uOlN9^m)rqGRRqJNS{*5<=@LpOwSl-mu-OtP9EpexCX zZ!j@Qm{gsw_}?|(YkEU+lU?0dU{35rn1Wiro^jdGz6&sX$46@0MH>Aj6@smhr?9mw zGAHT7ea6hS;McV0b>H@n%eUh{h&J=SET&05WI&fC+<*g^NY z!Fc1nKVcXnHfw7otmldv+hV;IeEhiK>~+?il3T{ph{XfGstHK6xMTBsvK>yWaAHehKi}(oT<~g{ZLQX}_?t`}JFnDEQr5scSbB_oT{w^eo>@b!3yy{XVmtvOq}ITR7T6ZZWid z{kO;S^i^VdQ4}bF7<@z_rTXQdR1kwZDXht0p<|?1WUPOtYAxLnWD1Q2>r^7=tW_np z6dYQcd)HD)T)SuxWO^0?a)D>jAz3sZfGW3+n3HQSn`7S6K)B!I(Ea+Qf8 zoWgMQRZBW-NQJUOHerua#W7Z~<)^5LiEvXJCQO%VO*zs$Id!%R+@Eb#Qd`!YY;}0y z-a7pj=h@%XBk!)P33W@Y6b33dV0eB4luu81anM`Xj2f@S9wj7K}e2 zq3Q8Noc-xD^PEE67j0`H_XgxCxL3!Ft0-(|6Z@l$P^gt^kwys6s%5HW1S|g1K+6ms z4#uw3;br6WK~QsPQ9*ZS<8V@YfeUL*ss~bwPIj*hSlJBcYS!c?sBjBn(du5~eF+VC z5&YwhmS0>J8&K-$?p-H6E$=qf;{Q$SOr06)@0WgTKzJfmQONG+7q~1Nde<6xwqq3e zf19|Y4JywCTxPn-weBY}P=ni|a7EAN(ow$$xwN1wjUTP|bXe>2d|J_L$1gEkrbw6! z?TbII{PT4`TV3Jh$c=i`I5}|TQ1Cc0#&y%_BI6AP5$G3*PFg8p{b$sp+HXUhv&D(r zzh79;TTq8TYb-6kVlCj&wJxc!=-9x!1I8qFf*6%%WKAXac!*UQVL# zpAay-b2iI2N)SOKZM=G|61YLAAH0!b1*en`1fv zcSiUP^u>>c+nQIq74Ad0H%eqwa2bJa!-@I38gkvbsB)E#cnGQtjh!nTv&oKlx%UE=nSjrYSXW3o?op~No|1hr_Mm33Gw)3{mMrsnXHqf>(lBsJ(Q?n=i}Fz1 zwbgEl9kY0b_Cd`m&Ag&HCOEer65;jE+kqzkD}Jd@?>I|&lyz46_T9?I)@~xU^;OQb zDlXyggNePCvUTU*cM@CsiV=OUmfX7YM4>Q0R5 zP-GOAC2}i_^~v&SGQXFN2D#-F(<1$iJGec%iEcT0vQqZ6Rz)`Jux+|HLJ0Qa^yBXv zOMkydb)a9zdH7;MPm0p%+Hhd2tqWN4Y$o(KnIX}=Fy~6|QBf{_3dvc;U**^Z@e++Y zcxkW8qN~_5a6lp^{J`Id!So~TR8j>Pz=LpFDsy42B;V(7 zpBF{2ElmxwLFUik&L(yng#&*ZH^qkPvDL3x+FW*B2FI2`@4|F{>^H?zNk1xl1&=A) zrr{}wv3hLar=^`u{ZowEdLH|h?zwAgi8t3h>dVI@Goh*Tc7TOXHh$F%DxpP!;)*}X zHN7tObqrMHNc+Yb^nO%h*1?;!XJNhB?T4_o>*aG;ySC%aAy0%^?=oZ8xY6q`b#I=| z35T2?O259I8Z@Y=QgJjcX(E~mWyZ`UjHO|@fKh9hupeXzIA2#M@T5TJGOl^Io584f zv*aXiw6@dcIiUg1@|x|o-Rr!>inC_%)?)mG&t~Mq69Lo4&EG}4a^=Tw(@`UdeK;$E z?s4WgU7CUH>L;5$G^T4ZxL<>$zq7?{mS{G`(oA_7GA=9C=hwxb1Y-w={5JJe8P=RU zeilChV7%=yM3bw%Id8S;f`YMffrGx3uYMvqf+6Sj37Bn}L7r_y|yOehQ9|IvAY@T1OAA9a3>@UW(5=#3!<*mbW; zIp9;4LC^A^!Tr8TUF~%eJ4-G14BaF zG@G`J8e7Jvt#k=hy|F`q)Z+o=AS1KlZ=y&}&LM?o(!B&icr6M;Pw=WCzjc3!Zdojd z!t^_Ddx*p}t()v`uwEryUhjKJgO9+*!)J zKE1~Ay1cT!E@Q-atAAkUU}gY$MeKei4Pvny-UzKd0Jw8i0|$4F_QdXfmDUq@Pe!JT`5P(t3l4D#};~rEKsZlp*)P%7anPyUvnfov^53+@y#bWxAqJD z$uLcsW)D5~XmE4fNkKzGlBtmN-y`;sXoG9K{*3zmE-@|b^7dtvCs2~7zRrd+8)Rm` zHd@=t!C$er0pR7C?ln>*0S|gd>ud+BTM$&6v^D5bDB^Q`d>7YENVw3;Hkw1l0F-?e z_8xPxV`4YHTwNTBJB)_N6k^o=X9ZG(5c4D>jQf|-mo^;^ULBh3o4UxWyX)GitOdl8 z>8MQ{L?rzd2Os~2b6mJNo>hQ+7*vpd{8Bp|WMl9qwLS;bn#WeWO9GxmyK_iSIo6G% zIJv%L=~McB*k#jMKr?o}RBX`Gp*DKJzo36vMj*ey8p)XI}iAtM0 zOL4-FIRpWPC8>{(W^G>QwnS7epx4rXq_C#9BxsghVWIJI+tp zELT@L#b6x9+mJUg^M3p#!g} z?s&8tZ?(nEtM#_zW1|URUTyq4sf`s#&?Hk@&o_E(PjF50{*A2~DZl$P(TGa&+jX4u z9+MOI$kw|nGuDdz>r=#N@MxAr~0?!=k}Dw0&XTnM&3}1 zgqoXZZu}vAA-vrFSxSX5vbBAzLaU_{cVC9$=WA8p)GtdABDWg$=WC3alX1%n4;Hm7 zmt9O85GGeH2q6oJh*&r{Scqhtxru<~?)kdm%z=c1LIo9hO`i%%*)owFkz+^*^=?Kf zEjBHF2sa5E-b3XI1zyeQIYoxoIU3=a+Gja`q#KL7OIDkJ;?UYudkIPhM!?-?#b1oA z*HYzS-+fc#X%Ba@(%O#mKa$yoVRI&PYJjfGMIR(E3Tg z0bNSYo{jzLvJJYN+3T9a2IsGGL&f+4ZVZM>zKa(Au+6}ZO!X7z($kkiSqd!nZRh20 z&%1xVAHMVI-5V)zaM5TS4Kp-gjL#*o_{wX8AJSacq=w^uY;$~ z0BU{a(VXI5AeG9LJmOaJ{xF6WwZ=`@CX^LT_e#uiOm0tu(yng zt82DKV}u6_!O{T&1P|_R3GVLFK!Urw1a}DTO^}A*?iSo3xVvlPF5iYc?>+DN#=YbI zDZ2XyW3N4HmaST~xb5@7&!YG1E#B&Fqe#Byy>JSXm!swyf2QK&B$wS_#nD+22~190 zq1?W}gVCnc3MW^GylI==F*L@hX_!}~2`7`;eNJh+$cbO+%biP5-X0Qqm-%MwkmfQg z$?ib0%8V{dH7H2?GM(~y99=I((VN8M#f`uiyhikNAOSQacDy^D^p16BcK`K7ythS- z&&Xxj{fUb502S^2QW4|(22mw&ho|8a6`8y$g}#ykJ98dQI~~C)n}tKE*B9C%AXAyz zg6TyGAs^&70f@>~0}@J{?!tsV>E)W2J7{(ZR?g%C{;i3l;<>;Oe>efu%xn5=eyr`_ z;B72^7s7qX_8#mEC^4yFD6Q?}T(uKV5qdJxbZ&D%6**XdDsGJPGmCs|Z~j|G;OHZe zntxi#6*wC|!W1R>r7LMZkInVz6X$!cTtt!sODS0-zBTjhJ-Zce-KF~kb7Ohu{Irva z!3XYL5s*71#%H&))vX6sewEL%%5BDo2}X3+P2XQG0@2OH8eu-;a%TLZtt9r2Zlwsm zya+KpX20l-tHqIuBfUYN1+|@(drfVK#{_D&!@|t?5cIeTYTSELPV z)gGesTMrX66mO&H{phR87eSm5UQC%(L9YlQP~#U_zaNU=hR$cP%6l-tS3bB4Eo>|b zm;sw+8%>%hHJsNC^XbMomknAQ%0H@S2eUx(gjorYqECuw8C_%eE;aI2;AHuo#8GU- zROHrmks|-}K0jo^~KO{wWT`@TatJtBG zlS2QYspf!*$&?)0FH)42S!<#HXz|;0!WbQ+m1NzMtlP09>pEH2U#MPwmvWG`g?52Q zvBuGHY6=?ObMc?Hhai$LuLwy@m%da)(G*%4o`Fn!dHq}QBdGu}eu8+3&5>)igHGP- zqIjy1b^^27QZ~V%7Nvr$SPpO-{f}bdpW;;K1zIyzGDz>59jlQ9a?GhPj8}gCTr%=* z*h< zl9v!wS<5Xy?1{y`lqmK-on| zGP`Qz(Q+`a{S(9XV&KX4GgA^>T-6T;Cy`d>|F8Rijaw%A=S$* zc+*Y_y}(#E^|$2@R-YX2cGsIWD0_ers?zg|*Ur2m(Kd*%dXxyI=y0&LL!Aw?*xAwR zse$M(;q8SyD&d?Q1hsid70V1NKCSNI*<|6E`XG@KG?*!vuwBcOo!Q_a2QEL2N^h?g zN?^8ZdmWl{7Msa-o=wR!^9r4HW&=fb;Hop+-a2%=H?D94mSJcDqLLFkFMXfId{OvT zzB>>|VP2hlw0m!r7$C*Mm(FSvr5wrTZ1=W*ymiNvHkn7Bl++UREqG{FOMA~?QOXcg zc7QG|F@svx-W6!9X{fyUMxQM+k$z2rNZq)~vOu?CGUeLVm0?A^{!_1M8me08D8Zg| zKKG*exbp0NkgMMEM)g|tGU*0ao7ds&pN8!pr7s4~>Va_gUmkw|YPu>w3i~v^>PKQ5 z@zIMQx1JA)0m%SEGSQ2z-L)$m0>Nzao)0zCu$sP^V;5&%TEN)~tam$2?TPDkHEH1# zny-*6k4Qw*ZRY`G{i45aWqg0oLPFyZp%0QrqWpq&CHJNp zs>Lox*gl+vft`M1A%%LQ{6jkWx|0J|hqbcSW_C0tOYl5DOWi~`?dA#*Iv2US}zk5RK z=guTnVm5=#0xh9brlKM(m2`CZaG%MKkyeqr?0BW7wU6ta{20wf`_+I=t4HTCv(dYj z-GwB4C9>0|Gc=#Jo*cMu_N?13!V8^{mX(-`t=dnYYa57QU6hlVzM7RFHBq>kp#Yok>2;r2R0-URydi!6L* zIfjC3;DQkoV6PKqL{zM8Y4rla@vAm8jpg&DNe(f&Sd}Gc^Ce&5O#~cQY!pk&bxwyC zas7FVmz_YXIXE-Yt4SD>{x%&+p3%tF#aLZ6|0=C$Ag-~VSh;WUaxT|>a?{hJYNIvC z+}I}u8<7G;*-dKy#TSbarF^C>tEcX?@J!d##KKuS z91=n1cVZPbo(!B%SdDIyP>UGIBCJDi3haOnECeQUwJC0rUXndqJ4YdjcH~$j4_d?DK6wX zj8R1xF$kx1AuOA|9Y59DmyGr9p{#b)=GwB{TX8yWko)cRX`Ezxj%6reCSgJM+fjZ9k5hMm!H(@?jYd6d9~1Osk0B*Q==GS*^((6 z9jgw8!@9F0f)XH)7?)RN+AAz(^DTBrX6>ul8al)aBR31@V4N#S$Jv!L$$e9Z*k&FX zULYePyYoT^TEgD>fnF?nsRP+H?Y+sgS)r;3=&QuG;D&fe?6dgky2;}_vgh$vxEJZ6 zcYT7seW@wHa#dR1@cio5;C8Xv*vUp>loh{IGuOof2jm!src}GSvRTq}Vvd9Uf6Edy zu9+ClSE{+6x@TF=+3M|%8w%@vcFS07z~!;h z#B$=b!LR(7FKv8Ikj`?bFqm7wb+yNE2iOm; zg$I*&l)+I~ALZJH1i4(cHK<)zXmL>!KPWB&jMe%;-a~R)y8|k>9#$jj)3h*TsD7Q-y*h$!{cbCrR#xhHR-{5 zUE-?$rCdo4IeOUptNvKJM2+U5qD@+K)Cs2Z_#WLYw?^(SD17rTaF1%Xm3=LUb9A5- zWMY-^!epEM+ipE7R5%><+rMrSHEMVLDCJ`$gi7}mUuvS*sLc#dKq5Jb)x>G3#A5Mw zEs!*oiO7__Z>XrbrlMdvAEbm9R)$~JLHWBoR3vQay)SR%YHw zinklNke(DaSmd$U*i4#sYH@=}cy|L7A+@kGcYAKquUP!W%8DoXPVRP5K(w=v+;r3T$Jql7q1kFM}qRWy7;;wb}S8PTvT` z2g#za*MS*BCY>sB{pl3IRPP+l1k>@8DYOUDh%s`Ud-^Bbb(9lD-_KwY$Ff~eC0Hyo zI{b%*H%0Kf%^GEU1H)8ed0MCFaLV9^`(1r3|E63>k?KdeE}xy%+~`lT67(IHNh&9+ zG~-eM)tIJ8C4eo7OA$?MX2=_wBDhd#sH|xgSIL4A75RI$?M^+nrTyj^o-KKm9-Et5 z!CBX!W!d)v0TD6ig@R;1a1Py%z;Th+lToEE&BeWR8fprkf3@kUEXHeE{nUAU&-YNv zk;1jEyK21u&H9MFVfn|=WA&b;{HYk&&;6Ld8n8Z=dPgH4;0f83KOO=-<-&A-hnKrD z$7;-WB2Yw;Ac#?7MVUPgD39tDjymD!;dq=`KG>6(5~Y{_)J%@f4=p1VLr2FWS#)h4 zx^Q#iRGV7tOj^ZRy*CZ#z$tXS9w;p`osV9L87#)s5!*0HE5^{ZTiHJanr>^jxSSH< zc02R3di8tOMAr-JDZn+?Rie4F)5nPSdC-V=JnKct7v7oHgS%>$i8I;FIk2STpld4q zMU@?kI@WB13D60IHgAIpqwb+=KGsor9;`*J{0aQg+IBwdY?5tFe6w4x4?OMW+y^bh zMg&#mP~D$xa;%qgDJVvdM~z{ytW-*Gx9FWou`iqW(kldt~VP(t|+5 z@+NXSVgBgOW8z-f>s^Zh`FZLbZWZ|)`v|={pD-*&x>5*rDC0Ff`si z(L2|3m{=8IGs2bxp-9`Rf6r-H!Bjoy!L-RDvf2Qevqws!@o3d()2?jb|7Es5ujJSs zJh>3nAKyFcB*p4_16Qf%^XcU0ONCE2WCW+BC`N|Hx=i825`S(F%K35p(V4%UwuT8v zzNB_Hi}+w?%V9Of-ThIV#`y+rdM|7;>zL$ZasizRY;U-(RWRgi`i@WS5*cBtI@Em|*u@J`#QSYiR~c^~5{TH3|M^?b*N!*KF?7 zn=3reyTt66yr>KpgURiAu^oh@Kfp!_S#USeh~Qk<-dNLTs-5*cHYVzbX*^!LtrGq3U&e}|mB>MUtL-8(+51`lVl|DxUd-1kuGzH7tY?>^Q+`GM#LmIX$IS%kztzRG_+Wzm)#a=959jaD=Gfw}h<#C+ zV+Jk)4Q*V{{AYb1PcUxyB?Y8_$@-xg$X`#t+OJkDlcX(#rL8fOviE?hwPuollMeAT zu0a2DSnY_jyj7fKVaJe7Za2>|V|ws#a7q0K?^^gh!Pvx2Pbne;v5*=hyql(T`k zfoalBR6f4+X;hcncppAu+=I1tmSTmTRHUGn-o5`WBS&)aC#W&m`v8qAE*BSeygTOo zmkPKNNcD0Bv|lH?V}D}jtev2dTxeS`V89%qN9;b5}RH#2iTUgJxY`a5taodGz86sy;zkDO7 zK)`7L`onuOpy}g8vtlS@K~xsVjOIwpMQB54Bc(%V)ez1af7i1oo z;kdQ-!e6@sH*+`$wB2fcj6dY*AL!t81G-6b!iWm)Qpkr-|IEHt_hq$Zr6##?#r{j# zt0&54AU;xd?_bK^`gZ$uv&i{9N(Ui5G-ILzVGnUPPFP0@&lgy>dxG1a@$m^mnFQgL z@RPD>t0D%}XI$fMFGb; zTQGw8W~iqQ5<+H|1r`XfISre>nQs`S6#GoExRIKqj07+%LHl$YGNon%5Q3a zRdVe~C4(p)6|pj@iJr)eiFNjk&bz8WqN&+OJMv#OKZPzzz3O_ z1w9}x5V#=JiU_~%gU8*G!vvK>|N0K>WVuH3u9|u4OsCv4ds2iAWAQhKmG|q*+NGY> zV!rR1P60@Yt@Fs&xMW7Z2P7$^xCWWqJt(0e@jjO1wp7X11++&eM0{v!f=Pk(k?C)1 z-`0}-e3YG4fq&nX?9;n?lHWA`eOJE}IMIp?8dELYPe-!|hvxK+TMQ4pUmlu|T=sT6 z3~skJ-4WD{!Wmwlr8qJLq=hcps9mKPc+2?;{q%`X4#`<%;A+0;FSG>@x%Wt&M~emI zuA7!trWp`40g(GA{_9<=F#`!Yn(veAf$m<5%m{z4Z^M)#ZH|^L? zgbxFiC%m@SimQ#x2-$eypqc{ywm_yqpN-$s8Ld{B_7TdUCs<2q{sL-)`RKgbpB&lL zyT5$kpuE4*j50Vt&oO#ydql6HW45s(vSO!eybMdF?MC8LL(ajG=oI*R+12Aj&2qb{ z$1OP`D>fd?_NE${-Kl+Gy}RX`!#&>N0h-~~OBfaC^SmMXTZ&kJ`14v(GhSq`z`w*n!;NjtcqtrL|!` z9;w@7ftF|z;3UAx;U|Vp{L9U7{x$x!B%?=(v-_{56+zM8I0%Nn?AMxuPdIxgeg0e1 z?IyG|CFCI|rRc^p2C0>6vrVe=qFjRT+osxn4Ug`GM>ynQP~^?^)a!~Nm%fSi>F&RZ@Z_N8ATxi5Rqvx_?*07Csp5={^wzySP=EZ0-t|bAos)qu}8W3Tx=JsWb5`#X3}LmTr$T6W0`{?4c$)AYgR(W!DA# z{ify18>%9g_M&u(FHBXCJFL0%K z?7DHKuF-rjBfFoM{7Qz;>8e8pX+|7s|AxW*r(r%VuSJWiW1s(1$ve$#3v|7LZfabG}+343TNH&kbvNU9-Mtv0D!F^{7y1 zWUu}+d^h*v!od#x=v&V^&Yh%N?b0FR(bOxHE6H)pJEN1IKGDMs^DX~*q9^4*eo_w5 zzj)+4;o<*;N65c;C>#xK;-t*s9xf)R8d+AQU_W z`FOHQK<1flSISj&L9({Orj4Bv0-`2(sC61irR_)f4X|TA0YqP>#|;pJvE6E<1@wEG zH2!Q;8z;y=a^uUn0>vkz5AfwwVyTzggkDi&@V0%U!{g%%v)tYE^2A3&&1+>=7G9P} zYz$k5ZZ4go7)MLXE7gp1>pPCtp6fPeH#M1_Ot?simjfyvF`6&3<{6x94=(S2I@*FW zgXk0fdsVAiftnxxYY9&_)(J!e`X|!#_TR`(^Q^h{@&X8sIDl#Qd@e?{mk+nGAJyPHzwj_TYyg7 z>xOZb&9k}q6MOXGiV;}MFKcn~o%NK-LkX0GRO`zh`MX}3@4ST3k`Q_~t2AE^y|d8u zsfBx7sFj6hT43?|l@<{`Y|nFO{d$w}$cg|rB`6^LUW?3Q7y2;JPT-~Obg7m$1@dA% zdZ7B(ZD2jR4JRP;0hYua^p|6rPaK2zb;AKhSms~#Rp}TXaOb!g2z98l*m>Ak32XC= z3DE{K+}j>yacR}y(yRA4nMDcn)hly9vwRlSZne;YH)W^9t3awmwZJ|{^k-qV10}gJ zWa^9wt#*BRZN&g)j%M_s2&*`XGOc!-lD*0b-bWf(6L=k`zi?Q*ef7;j^Rms|Y@$;F z(VaUP@IPjToYBuzIP0JFMZ0Dqul%v(y8mCxxC9nM2Eaw~-)sB7eq>a-X*E0n_dD~9 z-d^4l!_RB(h3i_{xu=C_pZ@@^Tp3zK z5s0#b*@Ed&%eY>bJ2rlBGrQwT&`?X}K>lWrAzO6$0Hg-_xYOM;$j!q)tIuq{bJRO@ z_4|YpRjfEdl)69KAbrrdRibqpp&cxQVH}xDwn?-<%&hM;M~6ZB;8OQZU#8;ey-9T1 zlYXcF%(2X?cnF&JSsOKlEbOX!${pF(vUK*n9dlezK>gH`B=%9f&TyTqoCM2;T``ZiDf}R@z!#8$Cfa* zFD|_LrCM_ugl2b#GMVSj9m_MO1w!9)Y*DAV-4?LMWZ2}|^ij-x5n*`EL~)+{jBI{pPHeOF<>C5D)uCGL(okL@ zs+3GYk&ZJYM%{)fKLEql)H_?Sg-3VwFhZg6L(ML&S^QAJIs;1jHUdM33Wrk*rKM}g z=UBxf{)r86-s?g1VHP0?0&C`~A@-aGaE3si9*gqB=X0 zSXzV0t8lv|c=d)&JN@1S?R}0G#pltJt~<5DK78>_6Out^cjMiv5KDu_YoPE#q!m_P zt<_LtevTU^xUJb_lFgcagNb?Z5Xe~aPu6|iRqxRqs&c%{k6#IhzJpsGy^52QL$ivU!OAfo!7!SLQ;R>8}LBE zEZv$ILR>(?$Z3NA*m1WZcxq5LRd*|ZoQaFy8dM9j7SnSewtakW9sbPU#*q3ORK(mK&?C{`vKb(wvL66gsLogU3FX}Cs?AkJR>4ec#F-cpr~)N zQ-jAdJdK~b$GuwvwT&Ks&slhze`#5W!Z;d+MV)rK-xB=3aUE;IgM-mMW~3JU21-=E zaKMS7e|T{#Bs1VmX9mjvQ8^rcQWuzP-2eK+nT0^5(ZjAx-Aj;02E?NgeaPd7ylO=- zOJbiQ@AZxyIug^AgIUlU*`hLjcwQXUgTQhn2Y_#!w|ZWQM(0bZ$g0kw7FOCe0F7>; zFY{NSr4rd-+9$Y>68LIQK%;LmYct)pJ<~~hy^iI=5b2K^FLgx{>v$;Y-#V+t4cttd zPNsF5mi_&U@Zvc4=D?mK(`*7KcHNeoBXOAEQ%Il^sF|sRR{Ntf?kUPCdRr@;|EL2r z)_h(8{k1zBIMYd?Z9x8q;gF^*g+`OU(^yXV=j$YP(Y4ARsG7s6u=&f))ja`Wc3r6i zK_!FM!pd6d5ste`$FbEaev%H;L7kn@wAswix_6|1$5%6{Y{D!hjWq4a$SY0d`%|s$ z;3R|?_m`$W;XIYXL&Yhrf=h{&RSMp-kM}Gs0~x_6vhrPu!By1oHNG@i`Yc^w3}dBx ziV2$P07#EEUT75P0$3wd4&?%S4t3$t(ZSChse`?^z+>7f^TQEu7--&wj5pGmp>u9) zgf1jynDF*}HzxSAjUQyhr}~;(v+CL*bRo-97|l_+V{1VZq?1cPqc|bbGvQmWG2u~D zuSDrxuMqQi4e02o_je8a@uC5}*=Nsoc6!_whS#YWCIE)j+$l4G~ z2{xmVM=@jV!r&^C1Xs>x&1yAH=T^0~JtFd}I#QVEDf^@pq?O;fr~ra)ocgEu`xpNj z5!6G5N{}|IG@nmFQ66j5F=f%gx=+8hfT&c%wAj2q&b{u_7h715k|UWr6&gy3qEzBV zM&xQrSr{ydsJCf0UuI81M*XlM@$B}EE6kqF*u}`D*S9t7EEB|Qst=S$Nx1aWUcN9)DdGSGD~fx!={?$jhop1C0(@7z^gt9 zV6<(nOXk_|6@F3cDa8#ku@^;)0YOe4`TUR5Diz7*O4e@8P0S68FGTpZ)gRF!YBhcY zcFoajP8#YS6ZNCL;@?Lz4rq=I zbz9b5RJ}{CUAyf~87vCCh0WGWMtQ+D>bTJn`x!ZG6Q(2{Nj6^>j}h%!1Be%bKb~0B z&F5GaXrG>Lc~zIkj11;YT?T`suM{g@->X_4^ zaF?Lv)vjU^DSz_QsZbrFW{JfZ{z20jgrA`SUbdT>o}b6|7jd1{1o@B;K9atd$ktj`}KNBFJxYgoxZY*pEqO52)CigdBX z15sV<16%c0NiBKlf2cK-e^Cp?R!%PlpO=TscIEmKp-hdTF?Mjb-XPM zF(AQ&>`Y-2sr01JE?oZT*?69!8*ZTb zWAFrkOUFn*=U8HW*|Y?{%*~$`)q!|o4CAdGllQgjWjY++;Nt=uZ6kX#VRFkHVm^DG^aZQ`c%?9f3q;q54 zE!4S+>+_E+D$235(aXEye4pd4dM?ZA`s>6YSxqBRPKG#4@hQNN3e@B)7NkUh{?EH- zCbYS0K+k24d9-Th%rS}eLZ;HN^U_LWSurdMea~@`D;tX-qu{)>$}m_F6FOW9iW5GV zNw;V9P3MP1nDz(nnI8Hmaqe`1_*lM@kR~cIt*i>kf7sW}hbCgu>3;y>0{j2Kpv&x| z^nO6+_)~-0*za73BE`qp01oh+{k?MW)N18N>K<8LVLC}>Z&9CJyE5|0pZOQ%)+7PQB#)_hrysuQ>lB#%>AP%cJq^; zN{&rZoX|FfHi}K zoM#`x<$->PJVwIE5fXby`kV#aQ=Svhy$tznnNk-t#^Aj#A%l7WF3+^OQP%MTj)5 zpd;o)36Wl zj;;VWG-s>%d#hUK^+t6qcT&@U-zYY22TS00?eH_=J2v#fMc_|4p(Hoo*mhBYL&^!K z%&u?R0-7YN&Wv@JbH^qBDQRd-^8R|ZPmi(es?VQIIHoPLUC?-DM8UZ&pv1A5l%c>m zrhVZ%uSMZTH?|hx%EMf(06&3Gw44x(RvE~fSJWlV2;JH?q9p>*z01v^w#{R0nSbwpvt zU1*`VG#)~&=G-evQ|G`+O)HX0|M+wT&dD}}zRrB|LZ^>u#+b_+==xUX-%wyT+sLUe zVv~!vM7Zh$a1Hx-H77a+CkGU$qJ1{@iSDAGe1hns%gX$sGP-TAEDC?)uT21-Rp0sEFUDtS2NAPCj@8d(l z>>29@mL)M=vGX*QxOXGFR(_FvqiF1;iP~g(=!hxmm(&8whJVTO*Mt>(=4X6pb@1v{ z)e0BZPUaP-#gSZ#nlP-n}D>-wTnbxzA+XBxEe2)U$eRtE^KeDe#*WyOp0tpAyn2ylNl%bS0&lN4puW9_N{HClh(IJt8U2Tmu*Xdm&NU)b zlbU9_j5z-Xgg}!E+sU+I^{aiU*=+-8*CLM;#&=*DTjXS@L<|oHCz9#3JXo%L-*rKr zj}_9GFe$;ZS$W6R3sI|~?)Jm|W|N0`7EH#=46Y-#5lG1T0hm9omqMf6sHMQK}j07+dVvp0@ zQQl_ogo=cL@m1t%hm!U{j%{9&3`3);S=$*^a=c2`FPm|Z_X9?JHh)N%rEiwYj0sr5 zTy~dv1>z>~nZ*MuB<>j(ZFMWOy z!y8rvB-o7S{Gu(cDO1b%sVd2Uck|(Tp2n<`wgw2xRV2bEiZ`EN2Ew|I(=LfQO(U0& zXlYrrZgdjTWI2M0EYLXkPku-6pP&$5h290+9L17p9r5M-m5L)J_FDhGB5)r2K2VPb zoCN3D5qjsF#1A4e)VRfo9~G<4qO5D`Z7V#APBWpY$fJuV6TdaIXf4o$6OWZ{_nEfp z-|TYB7`)ICMjYmSj!Ods21A3sQXo$q7*}#l|Mq zf&sbLA!%88fid9uildp&kKxFy0C6Jfzfrn?VIYO!%ecTC7$Vz-*>^#=q6#;I#2*Iy zJn=$0=DK%$wT`7t&)T69lY?8rxs_U#Stul6 z6jCyv@`|#Q+s)rQI>3xoAvxC5>y<>Gk;;nLUE@;mGhtetcoRgc;9MGMjfS`E<3ci7 ziJwXTrLnGC*_rXz(TJjzuIMAAw2mFdO5GkOks;~Vqdz>l@{%w5!F)ZEM9=gk08c|5`C3?%^NGTa!Yi>d+;3+UgKRTokAcEx5C_64Q zaieK>6cfz18|?Zp42+hBczlX!RSp7m8Z%Y4P>y4UYP_%YpWLOUfkV&KN}SOvPvy3O z0wK?5D1Rq}9yRBEquF$q1h3?`y`}uP9toXpRnFuQ6n9b(S*g^C zFPGL9A{1CK5J{G#Xh(nGZI-gt?$iLCSqp3O;u`MB<+>x)pp)Q^K?->FDZE){<%YL? zZSe?xxVs`qiXJI-cl>$cbXd7cG*>FIPRgFeUUSEGr`0QYLvgWvE)DIS@p)|O1nh9k zwze(C2JeG_a@R8R?xq4&xtNhBBcz&4EVU9_rnAprQ!FU%lneYLRE3u<6YoQD*d%?I z=}7fUk;MUKgu>MY_v;qKtA01J;i!<=INleKD~)0M`nvN!zDY3%!yaG-y?sZAP5%a( zcqMsz=|umjOU`=NSzw!~Fk+BLPWSrD2Kde30c(?MD>H_oe6?m=zl>t_xrPGs_G~JU z5X{^C{^UtjmHoaE8bND;LDLgIXLr^TJfE{$BgMZsz-?tWQtfpx5$dpRtLwT`OaGXc zXOKF}mrXknK`J@KQmeE1&@9nsdbxiXvqOQsKXGi~l^7e_=i=+)Kz|4&uC`ljGnSnZ zUwvTet?QXQH18K_v@)Ty3`D0WQ^xhq$XdEqwIIlRgl*7&yt=fy zPG0d5jvaK0d29TI9nQv^b0BFg9wWafa{)DUz^br1r>&b93{AjLp49%`uqR2QjWMOVPs9 z6*_HeIz{Kl)_&zUCyTe(%g(=5Vw$Pn%D*hO2p;Eg1DZH#k^0)IOZ0l{YkKq2OdBc551r@$y%o@;k(Pf_#FfzK3Rd8y@4^jyu9(@e=~N+kt<&GhSEI)`aQP*3 zT^PFZy)X>)oY=oS2FT3lrcPLl7R6x4lM&^XW0JEg&$KzNeV=#%I;=sa*fDr2R;_`! zeVw(!;jqecE|-tOu)Mv(N_~Ercg+cty|>1QO0WvltvyuCefZuci#p{E(20o?tf--q zRfDzva_hM%Us$`4%;I2g9Gq}&)`T^#;PXtE+=dU??(DUW_lsgX-s|yIMfGYQ(|^dd zkGo@@oK&lUa zcRZeUNBN2Po&eSc00xllmWkI}99+qJIH5z$>C;G3ySN!U7G3W!mAZOmX|vCjp76%f z=IqZcPqXZYg|)JO6nWt-ZRE~atrozk*_e73u)?IW^LJ~@#(eQe!Zh;bTMix?Yu z1Y-85w@pzt5O^CT^7v*fOBAa-uhGus*zoER4#3IBsh%5Agv;Ri)a-lBSMlE<7C#Z8d?s+(5>vXm3qym{f zbWBuHa?wtyLH2TCM8sjCu9R!Am_q&3?)pX@679-?6Fqj7XDzHmJt6|9`rv?&dqx8% z=zQVJhOv0HN;5=YoD1fsT6)akl)t+-$bZzl2|%5H@F^R{_1w~)!Hz6nY<>Lr1NU*< zdar>@7vBR=i4NPI5gw}-%_MzlWdLg4Z;3$Y98W2310aE5@33}I3Pu%=J?k=B%+EP ziyL{>?5<$*(^rP!hM`J1<%8U=W-!g6533ifiwuTU?x+M3?b;RnPCVvaL0Vmo1k;-< zMlgfre1dH3^7ACknO==P7cTB~tKSRKa_z&oA*TT)o_!RJwycZ(0p$G( ztudZeUb$0GoJpZs#-sfhlOmf1bgvhYT}b#e{a8bh-Ej@gYYQXweFxM-Kg8l2YbllP zJ9qRtNT7o2`MX60%?*UoG|I%nC#~5h$wTHrV?lpVCb=4U#+JsybuvFkKO4oUzwLLY zZY*pl;4u$BI4ilU{xI)q77<@r9UB2ny9&~Jp6x~Ur8CFD_r3piz;5cagM(?7bc{pA zgr5|Mt<$7ES8L&Ianes4`}xXBG4Y;YHsze&oE~W3g1WP7N9uaw$VHrin7#zB>aCSr zkrQNU6%LN5!&~1icFr=pF>S1OOzOn=gAkwo;$;N$yj^OjIfjNRE_rjZI^RBu3X9`< zze+jyI36+PAFKga&W9jXh||Feyu^WoA#HQ{@kCxSB1LZyPAO-kKcT@rD*rmFLu zx>vDIN(Fgz77Zm7>Qv%f@|$}TOtYcs^qSJH*O!)>m;?P8B3c?Xlr;iuYVD@pdCe%NE}IZZ$vm| z8}ymOl4Ww1f(E1BC_SmmJ>wvK|ErOq1>_V`_?*(Y({$l@n(mdjFH(9ih$p5W?1E~d zN^8E)GVuP69!dM=D8?Owm%d8Mg>QcV`E=k8HzP#Ur)K6CTq06p2=$kZ)-M}Gbb-LY z$`zB$cY3sXv}Lank>C?U8a@JJsRQ%Fz=IYjEbO(TUB_Esm4KH44g_q76XEKfLcB_> z?j7b+XXqSA1VI~ZkN~_P1%}4p%y;Kp5*P|pTARM8ijCj+Jn?UI`EiN&&JZE;_x#Il za&9?`+EJu_u#@Ecku`bDD6Hb*7Dwll8Pc;B*V|Kvsnr(YFP-F$kg0Xc4bx?Fd$)^H z9tdmu-L=eN1xMB9r3Hb6Hj-yK1~9Y{neh!PBEyG%m5Nu_&M*2UG&|(#WRj8 zD<>yOsIosT-2lMV_x;wIlJ>0G8gAt-bsNq(fed*yg83?gpsDEr*C$h@X}Z;;cB78; zRSaz9tkTXBc9V04Cy?Ncwjw}_C?z;;JIt-(-SXt4uAwxW$o!6}Q?_Og%gw+&hL;^$ z3JcP+)-E^Gh3#p}TdR8$qq8#m>{kxIHEvL0996r%U$)|Gcj9BB))ZPaWLxCbLI{3u zBbhiIFlAI{H|U$|Ss0jQ!hUXdO6#U|oy%4;F_(_X_}NaKaL8M{~GMd6J=DKa7G)7BbaQ1;2^lzccnZ`ouh$SxgyuRLsQ z!=|!qSW2CHBXJn5+GLb&Md1mDn$kA&o=@_-diGB0p;u9KZY&gGQ0GJCXucEW&*0@- z(xm3YNWnKLP0Dj|Qt>ico8<6GAG!y=-AwOz=)OMrl z6+H+3ApKRZtta)m{k(zm?7vmXoTHJad#X67$)_%$)fgr>`fUE6Gl_i5f;x)m(mmQe z*P}VO2qDA`-sF>%V&rpfLljrMy~`k)l0`z?_e>kBmv3CA_J1H_BCLn-kNEA|cf%c~ zhC^RWPJSMNySY8~m)QAXr(1u!r#!W;dkuUH`rbV42~1i|{7q^vT6d_pbe)8of0P=& z{^Anc7479VY-p?9U$m6F?!*O6tc^Zcy=3^N^MCmI>ZmB&Z*4_FK|mx%KtMo6M27BG zQo6fK7+{8O5D=6`=@yWNp`^PTq;u$op>wG3!S_Aqch*^FeSh%J1FSW(pKD)x-`9@2 zkr2_Yi1VnqPu;`xD$}>!J~fc&@j9Gif=g%4LrcP zV<-t(yyBSDhGF`B-2DYH^Ww{l$?#oo-!Zo@(Ha24Lerq3hD~s4+rC@C3W*Z9Af3AQ zdQTbb%~qH70rzJXt;3*AyS%Z6n6WuaK}yLA316ULHKP-rpBDJ3?=ROOCi5(n&Ni6Qam`CFG}<1Wk0(ilW^w}zwr3GRt>4MEm@_i$dm>KTfeWphdu zp?XUq^>_FG=i4j*-X=OLs9FSA!Tdy>y#9qfwf%SEE*(GNH5#se86=*qD7!xXIHe>s zEAeoz;anCJunqJYzCLYdt01^sf^d85>hqhRd9B@=-n}t(#}4PAQ>De-bY%LTfu+Bf z(d0=4!4pLJK7j+VUv1314}vK*FRp;Bh`12caI~_!d}`x2tJ-P7>{h!>+uN9w{P<>G z8=<0KXS!E$c|H*7wx~6fcM#OQz!?8{<7;e>@0Nd(Q*q^seE$*9D6=wyCzcA!TA^MZ zcv^pqAUw7GW+rNT;-qZ?_SAg|co$W4dc`hPDZt0R*$s3)E*5CGY=bNF{Q)`O`^OTW zFz;y71F*#7|5&2`uO;vSO8{K@UzUiAi7(=YWYr~4zQ0u0uPXFf7JI7YA-?t ze^psn8MtNL^rc+VpCFy@n(lbWWg-0j-gO)7@}2{cHxdjC?>;iv`=E4hx1|&ei?yoJ zncgDbF0!gAMuyl%WQHeSHv4@&qq-1o?95*6t!!NxP25!FuTh--;FaLb%IIOrUz1&# z>egzu1YH|>KhLt*T6K+uKV7YjdWL1qSGL)LV#tSfWp-yR&-sQx7HK6d7H2Vd7?bKS z>oAD>{>aT>a6GnPoJ|?(nCwDr^DZ^Q*KcW7eaj|=1#S~k&b_de+?I%yic?%5MGuT~ z-7IQfYo*MRT7JHH-FZ(GxJtsdToEu4E6DY89|yV#_|R{IX7~l1Ge~6Z{TdCh&?Vq= zpP1JZU~zHVm^ce#nVwuxg5{36msNOea#>^{)*Of_R(vNI^oAOM&TqoYrTnbhnTGRQ z$Kh2|0iEF!2EJtoH~Hkk>ri|`)SJaMMDwl@cxiUB`yrn03$W2wO%mr zEq#@)UU(^9(C6JE+=D^KV$^d2bpt**q5Bf)bfCZi45x1)#f2McC~#20PINsga7^(| zRykB2D)iND>6Yprh`sp>v3CH(uvtMD|2kXyUx;CJ>3jenCiJhfCB%@<$X#<}^tO(- z4>c3-1;@(2%4BK?Kb${pZ?&OHdVCctHBqz^;w|vNX83$scUkDY+&y=yA7~p4lw05{ zFfMGOv^IkDbM3e;!o_xbiXyx3`jk=t)`ihPU7@Ex9{-!+$uJyAkU@hCdV9I_T(XMn zHS)0J*fP7svCP?)ANFZa(Y}!)yCMbncZ6!6HiHdQZLm8|EuY^8V~*~>h~kXKIU|`( zIdJPJLVmjypa=b`c#vJ}Jxz#za(N_haG2{NU*`v0G%CxL(9c2ij}3C}YykP<*T?a^ zOK0Wbkk~H~9cvZ8KL0)rAYUQFGXdLu`3Fxm&E^iSPDFjEm5+7xQiL!Vmd``5odmiU%IleTbqs4RHyR&rAM*3R4;WnB?LWq7YrNOS!G!p#I#g#v8sM9ER7$q$Za&;(3sM% z9kaO$w&KiPI`cqaeg1C*{=XmIBn+tz4!8ZmW2^beF_aZ>T&Ma$iA5GmQ{aK9a z`@7&Syiq1=o@}a$qhHZy4YYCtV9#Zwzzbw4e+k2W@Plk=kc?p3_N0%Mgq=!isxait8lDVY5aki zy{qPBJQiuHcv&^(zkY+b^P5nWi0TFmfQsJ#6S@A+hpWH)&LQF;Wrn^D-v$@IKhTr* zqu8gHOPjobo(Dx{MxF;iKEX$|{ifqoAcnx;y3LQ!_ZGZ~;-xxwPChsv=(Iz#+5=$= zrLs-xMvw?YS!B69u^d3HT=$$obL9BD31>t#A?>$zpxK7tT1dZRId|cPwRwnd|MiVr zt)T3=u(|#7Vep7S#vdmANFipV?-yyN<6e3T)|Wcg4xAFF6gLcur3dV1Y}j*7;tX1h zHvNvX+LNN-KJBB_KKCGZ9MqyMwAmFWFQ&mSi+qdZ>2&j)n;@2w)(+J1u|u0*<&Cw( z)op2zcamN6CQiP=yZrV6Jq@Mco}QPOyKrWC_*z3%|>p3(I%-DhmN zbI+w+-x=p--F#@yORKsZLW?IT=V>vKM;4$Je2wYy<>S+b2%$~05qoW#hS90t6h5!% zUM!ZLK8u2pe%TVWsmu9MjKXxmN$tXz^VN2;g&xGV$iSh1)kqGfz|(43XOUZz`B|+h z;PgtbQ3CuqnnK!}iE(CndeG>eu+n8l$~onh_b?&!xe0PrRAfwgqT9;H%pTYOA2RB> z3vOh#FYG<>0fzejKJ+2go$(8~?yE02Al3Z?DvIOR(ZAXOwKcd*gN^jBdNqZ%f^X}k zCief@WsrBdWWeYh<7X+|%&Q_ZHTyAnYZHnc@HF@wPuTKvHBO^DX!ex?+Wdn)UDzdf z7rb+dD4)R0)P)a3I3bUI^UQUiSxo2?)|xe-2;CwEML@NBIU>w2b=Ep(HhAeCN5Ts0 zIhnuX)cMP=pg|uIKZ;tIYuTN`fQqo(sB91H8+m3D5}IB|K#f88MYf& z3I5T&+br6f`CmFhTY=s@!IOmeWviHHZG&DcI`VZ!z-3QuXUb>HzsuNV3sb|)>(7MxhUrS7tnI0w&o9eW!gSNVljeCK^sxqW?*_<)P9VI?w$37YpFJNGOX)7aY0^Mt3@(;H zVw9jUO)4m}k@0*B?4br)gKGlKv=|hfRkbmQYq{x@)ooV8RP*)NlXpLbQYa3=_1~~r ze@}oQ2OCC}W*#NuVEy3bS^?_C>AkDuFF1)^{;vP=nPwSy+T2#g6?0m0T41J6VL;j4 znS~~e?xrd?q9ONB% zpS9g`B=$}$y#ylvd4itXCw# z6SM}WzYT6oJEH9c>$?U=HJr=UEM6|%S`2ek6`iXC7tD;1h6AQ)`@8{1`)<_C+AzV^ z-~{?drCRu#0fF2pJP4WZfKgT3(BOH-XAxV5^Gmm)(?HCwT)nq|QGb9zK~=xuOuqnw zOLym>*zr=N#eY8Oe&x>6Tnn9@PbIfyqsBfI+KeDwV9ZgKG{veraT6!iFL@Euhj&Wi zbcW;P-XXd9jS9T#`^a8Mu6m(OOr|V^qJJ z{fz_Aj3?<<;ru*7cyhHDFfu_`xNiSV;DAu@iTe_DFvTokXBd!Vf2XHDi2k=7^>2v-_&t9fbU1rXkTd&*_ z6}EsCye=S6vb}u%GE)Q%6r7+D#x zvUg;DtZ6SWDJdjVRs5Ypb7)G6J`k?Vz^mq0W4_NOwK>nvV_8^NSQqN+jp;%NgS9t# z9aIeCKur&SFXn`fMMyI|E!{_E?^is1Bc-SioWE9jYV-+zB(|$NW;7(%cmki{sZ!`* z6v?2h!XhL7gt5(O>25X*W^Kap=-sliT;KPIo{wroZvuzSAp2YyTaHdWAXUlk2m)}( z@LC=ov*{;im?sZIm&WyQTMVq;0_yQ@lQ%h8Fm%SV&MVJLgT4`!`88N=RxXmTzwwvg zk!l1BB;zZxO8(S_kx=%*+v?tPt@s@Gtnoqjnyt09BazqUl^wYV4NrG5xJ!uv#^4P7 zS-{m}3EFbyw=OCD{lty+G*X*O9c9`NWJir-=i|>DEUzaA3uW#E;jH_Ngh&L6p5IE7 z+%LquA!z(*zkH;&?d2j(^cN+s>ta-HZMgFYH{XyWN<+?_5H2y1>qXynzWNSJy{F-x zGJNZe^5Risp2Adfsdm0dK5*ewc{m7Ce8n+Qupv*(9!pHQ|NL1X){y3Cta$ z6CcdV0I!UO`ns6csXxw2Dlm!JGr$Q_WC<>>BEs-_3eESz#+j#~uKulo8>S_nsP?)Z z24yG^Jg{ufkJT7!eE_u^eNAs6rhInQv%cc|QEEW0I|#wv;*Tu>Fvhr~?hEk0SlA2u`&~NUhJXVe;Ml%>7=TlYGrTH`(^8)+J*X?r4UtDG z+0$sz|fq?96?FZ*W1E}GeuMjrp(>L8w znDRU|%)ccdKG^}&JMLbV=EeJkD{8^Lpy+PNSYrBbV+(nBoax&RmD_1}EGE{+zteW? zVb%sCI83YRp@T8}u#F=PzB4nk)`$rla#;XUrjrJ#1>qdMGxw^tKxJ!!&8b9(#Iblj)GwV`Lb3p;X4g=Z67pPG3MM224h?G3elJ@rjc<%S@t2Js-lMCYI*K;!`Xtu4Qd`_TZagNt zwx-UeI7Z|3>WI8T3eM}Z-$5Wrgd>V)3WMC7;M{E}M z$BPUQGdV?=t%{rjte=qZ&BbY%=)!tR1kSSnqDNAu&AYdXn9Rk?fpabWnV>o8Rf|^j zm|rhr)tZeLrAI<%3ceFtof-?pV~&;0mm z`W~aM7oC(;&c>D5K!Pw~fD@>5(D>~6gVg8)>$>w#)VpPxOsyxBV9WR}MTS(ePfxRB z=&!AIXXuSPWL#?}juu4Z$-T=)W{la33T0yvcDuW!Lzfhi`V|)*H9KRDLt5FDmx&Zp z&ig+@*V};k!%F3p{`&I6mMmey3ze|xm_SXXMYl3Ny}1`LSYd9zVax5Tg0qU?u*XQR zwIq&pE*#=l^o-Ts(9N2B{}$fwsw32ho(LZYX|^n1EU&z4B5+vSOKFChFa-NTr`!W+ zj?$$Vv{=>su1pb|ZHp1HkA5UDxF)`la4BUv~X9-iE#PT<44 z1QW-nE8=35DFOO~?9(Akk;-(^2_^=w`gBvDxnAKinQ)og&gjKl-Vk1D=~l7w>crT+ zppHu~swd&VtuIb1>}&9ns{GEhy$k~xYSEN+gE#m~%ZUsc#>eB$luXm!LvwE$P=8!swM#6JDhU!2Nt=nvS#q z(#=)^rN4f$MMnK(qkLNAFeSQ*5zb__#E8$CaD20&m#2p{p_ZjfmzU)?NE8Vm$BxQ_ zB&}C-*&I)BJ1ylz=~eR_<4#2iOrhbx1IO9U`+fl z#j%97R(unvZdHE$6$56%j!*#zoXUHNp=;jTMw6y;q0E<*g!`U2a$UgG3S#aeNT++c zGyVZ9#J9xD`Y1>r~fM1;W%Q&(}VgS3%ted9%@QykrLhrQCvt zx@&9Dy{+b2*Lzo7lis9OCS-r4i4Cx&Vn4pe2v6pFscTS{dZx_hXZVl>h1v&HJ2%rI13|vAjpOdPiWZm z4PIWw&AeI`d_)3iqvU^6d1iR^KEyIwdOHjtzV9Eiqe{;Eiq|7O7wh(L_B^!wq(SCM>+b9_#Ne0 zZq)4|CP)Gnr3hmfnafyslQnLTv8lR{VN6Wy%D{)Blr5WtEw?;x7W9l zs(Ie4zhzLLW&Ra8Fy>Rss;A0Q9X0>*iB4A3=!ffNI)_2R)VGh|couerGFO=z&^pE9 zTEtZ0X6fMiZ?PZ^5Vqca`Z3~72`aL??%F%-nK8 z+J-k9qxw~k8?*V`-wo@4}DG>5`p@epxNhYlrhXt%^$4 zT=g?2UVl=@pS!qsLw;UFt@>@$CsEjurq*H>IZ5_Z#e`MzY~;_t`90+|Gmr#xiXsdn z+^WWeQ=e3V(NUi7!vpP<$u06qHLwR!QIEQl*-TU=P?bFc7Bdf8ay!xneSxK_%9?-D zzcSd?#Gipc7+gNpw%J!E+m@+I4gpu7@YUUphG~TI^-={3q|zo*=Cf?p*K3a*k8wR! zncNm11o^D?4nj^s^EOXy`ua=AN;k#cX z_O@Z|Nr49C2lugKllIKBCPlILH3S?6!Bf6w_0;cFj%tPki#`On@gEKCeaeA7m z3^!(EVnXum!|n+zwc+lXow))UI{hcEJI8Oktxlv#8YIs2X5(J1F7RGZoR=j8pQ*9| z&P7>4&NSdPG{Ee(MRcHlr-+E*6$A8>YyaQyd2Rs6{7UP0N%CXuP`jmOzrY@7R>{Qb zp#CxQsKJi;^3V5+qN^{AtzLt?={>PHBN^KE7ewN^UupD>PK)UGctchIcnkw5rF z8(n?ZC zd|UI@xz{({_s*fkxZ&}=?4R?vnpA$h_#pl&%?Fu!vAIq3PEPjc%eIKZ=2nvJkn9bp zN#R-#sQ<@Dg~0JcE_=JRyi+b?D*z44F64vGkOfgvimWX{}95-hA9Q{@Wmf!ii z29+!I@he`=OW63;1#!?h#a%KSY6X-P^Z$bAZ~e#ws2}Mh)7~Y?A)$x4PP?(A3@&k6 zd+!pDuFiMy+(IWm?(+2xLQjAD#Oo|okIWgcRU5H~?*+u`49Dw?84FF62x+-UAtsFW z6gR#)!=j~)Eg4tCM~CqJ@XkIJQd)*iQVBWIHYqDkN6zz9`DX9F1NGyxr|ptjmhB;} zX&y9QPwRZ=9gb%yO_5Lf$M7y>S3LQ6g1j-_8;d7 zUG_HVIeOy$;+7lOaa14wFmAd&Fuh@pIr95gDS9wn{Vo3~Vr7N_O@`4!9br-gG3t)O zW92+jms~8cFs*5P+4g66ec6T195y~k3PNupiTu5w19kP}8WE$2VxQC;( zUgq#?X>3@> z;Q3G)uQT%rx+UkoqU?`<3X5%@fS?r*dHGN6@!JrdW9K1XZFnj|>v*C7e9DCfkDkS* zx3#=TpBTp^Z4iP*>%DU+G^(+xR<{^5*hCdFm zQ&|fJ)3uxGztHzVepg;Kd6$l&VOph{Oq&|S^|Aw z22}n->0avIcF60KGl_}tb7|qQr5H-qy#X-zAfu2jaQy z_HayC^AB3(uhB!LGzSn(M_d(&8ywldu@W+ z8(vMN3XN+J9azTb5Hap(isg|=hwk`xx-H(%I*XAz|I=}9Hy&@znF4{^t2_N!mrR;# z*!yO;)TfSM4<`}d*cGU`!PcMZJXpw4XAAWs)3g?NgF@`kRiMY&sds!DaCb9y{Gk*k zng*kAXChW*a;Jb+i-svZHPx@na~c)?5uN@s;s=AAOzBPhNRFE|KMQnmjRv6$$D6N+ z`pQ@(!&#p+(ML76#8h9V!*PQ!fijfTecYuI5U^r1;obZablj50HTQCV{{~k7I385S zxB0rF&l|0*CMo$nZn38BW2AjbYXSVhSKm;0p(p3rR!#ZDoD81}Y&vn(d6PGFFmQOz z)8m4oqe7^Rlc16OPNe>h%{s9UP|=gS(>g7&349T#2)$r6Sz9N=@_#0%Cp^@+KXlH7 zM^6NZ!xQ-lUe21oN%~flLiLX$GDytqOr>;ZH>@$(s$Z66t1F(4$@dcmD<83aW%8^k z$OwLqZEsJi|1Hh%TfBD|x&-6t?~`Y!cSJF=;X@`xN5$4=tBD8nOT+c5nM|*nxlIwC)6(-HvmztnOm8buL)vT{Y0%hp6b{6}qXW1rb zh#w}&Xftpm*pq#fUY?Z9sYmpdj4exYV@aIOyl}%+o-jI-t?bnVuw8sKEN^wvwbbvX ziMmHHR0Hu4!sN`SAN&UCGA6V6^!|?dhACsbZto(*hxE+{ah?+4FL5T<_SuVL+@m^8 zKW~}QcR;gBJC0SEs5iFKs&;%;!;az>!RsyB{e9=!{w;rArP3ZCvkfJqoJgtc%iafN z;2d|%xShxA>&DWl2}PGe-Ns65ne%&RwwA=>#yHF#OCZy~GUsnkrhHDvT!*M#T;aICr_|7(S=4|8heNq*xTJDJpjQ$@SB&ekNI0Bf zJ#(kEj-&`>#HqR$s2e~XOc{2q^X56+e3Le^`Hvp6_fRWLpbIM)BX*9j(mxF`z5N4~ zdKCRlt@1P-$y;fGL{z zcBz`OVzn;)0*+^e`xq%O+TUmDG4*4!lqF%&%|0b{Za{C{T5QH~-+(DP+L?k+I?^G_ zHllH=Fq$Jtrcip9Jm6d-tGhm4e4hGy+4b*OfOp1pLaZ0cWkG8K$n0llo84Cr5s1!M zgsG*c!152?K?v97Ym)vr2h9dn`9wSvD zDub@Nl+Lw(O!>E<4EIUY9ZS>S_tbVbix0=2kIZ1Zi$kK1N;%)Y=HvMQy=tJnF^lEm z*Zb4Nxl8E0`r{H{txi_82Z>|tfH>{fzg1IAuUwOm@6+E-Fs>FlPxOGT+NNl~6ljGl zj=S0>U{U(p^dJQ^J@o%Ui31FE?=OL&vrbGrE`*wZ(b0^puNJSngP_?kRR+3`^8%Ir z6}`jR8vED3(7ta&3|>!Eu-RDvNW0Ug0i@+aAA(BR9Y>-b&gu}SZJQ0w>byX^-lxG3 z7^TMshyA%2Q0Lr7muD&G<(aVi)Rxu#h}i#`(y_K{?8604hv8Vpej>_xq#FAk4U35r)>IfiendTmH9Vr8KjV_vR@w z49dCNO%7%~B@#3qzh`h^H%je`l`lWUQ5%yrjYrU1FZL=X zGUk#i_M5IEq$FfYC7sDi$h>#p%`V!DF-nrnj2IVB&FHh#W|feSt1=fDgS@T^wZeQV ztpv_{FjK%`@xub6G#CNY)B%6U_spo$-0jDDH4*IZWLx}u0)Z%ad`g62r50;YqC?iV zh0RjekR^=1cBYAHswv&fQ?JUKm;a~;D^m}qo&P8r@!)SR^}qy91*GZ13x5xKkAeC( z(29)aL+gwfTJ1qug2%-#RKC;EIeU?16UNgc+E@3?>{Mk3W7^E@ ztn1!Hb1ZYH?#;NUY3*GMuRg@oUZA5*s`J0s+NQkb*n-_Aq zIpCHciG7tY^x3h4BLG-0Hc zE^OF~wJ|m*+X%^_9bTq=)FvpizzCGB?ceF_EvC!WkQPP%fEC%Qe#lepwe{>T5#arP zloECckWDjTe#vdI9gRC;hTg)itao94IL7`ghPP_KTd9-MsA`ebaj-DDHav2kt98dE z;kn}=ih(+SBk3r0vU*;(VpWF^;5B{C;e<-%$!)dyNpEho<#wx%3ft5*fw4e`%G!pk zmIHSYkJbc2#wgH>+6Aky!?DXK%?yEP}%}Wr-t1`CC z;4g1orb$t6;sG^E6x(DZn((A08R#dGzNjz!Rk}5K-5)dW%0*fVrvxg#W9OKh+TnV6 zi$4nX_>eOc2cq`ZR=VDUO0UoWjNK6Q0Tr7cQ#?=iNnR@2TJFavGK&d0lw@~riZlPF z%V}cMDe^TxK#bF1aSe*apS&296Lg9d=_p)+$_e;0iIZum zmF}gK9u%x9d?cIuPvrEUdOYX5cYNl2vB1;}n?v)OlW)y5Ai53%W6*e*I_qk%lPN?Qb_Y2m*ATEI5M75BWL{mQTi88%YlylrlN-poUl2-J@gD(P@tx$5 z(AiW#Zx|TFNsR4H$)J$#)jfxMG`$1LJGjJ8MYwHz)=CoY?mYNI0NpM@VY=%kP5XQR zj(4LCsSd{FUkk-|c0ffgrX#Dvf}ypgb8q6xvJ@r}Hd6?vfMD{au<^CW#YgVZ znd8qeU!zB}S9=U8-H59N-8_>G)uttS;)f;o<71uP)y_ajm%9+og+93JR%<&O%ced~52izaT zsRIz>l^ogVuZ{IElhl)g=*l=P{ehsNS$7rAWRJH`M1Vd4pxH++q8jss+VMioU2ui{ zMI=*~&VwfmZZKUy+LcwFD`V^o3l~Q1Z*O&g4 zJV0B2W@d*Jgp-yXQ_8oWheNYggY-424BjgJ>3e*=-xFws`wFdujM;(`Ro<-3PuxI) zU>9$8H5!hO>nEFpB{plv^)E2?neC5PT5-(r$yR9)sPQ9L=#@WHGFtGr6V50#sKafxp(#lmG?u<>)9aU#UF zOkT;-0GMOkjnqhyTPYE+uq%HaIoIk*mWFv2>oB}%Gwq@G%V0KJ>86)l=Q)?I#(rGQ z`f33>F_$|s=R*Ed?OPXk;Yl&VEe39X$mjEPQ^ubN`KI*=pEIaaIFeP+nXjY{n^K-U zFv$`OWvKqGmc{JZz9LuiP(`DQESp7JTL(GLuY1EF_X=5oa{a*dS{VetI^`OQ? z{u|nVM0@6Z203K&k|j89>{WcB*Q3O-=vOCh9dRs=kwaRCxkcuawMcA@tIv6rk{df} zIP>52-5=WB31r)VXeL&mm6YZf3PfcOpg|2FMxd8dqn!K0Hd*}k;M(oI*IzVWJOPGu z=Oz9Q=^}90)n=x#VDEtuT$^gTyLnujZboWqDd>mI#1Ug^&e1~CzU}ecu)EI=gF2``6D;d=fdq!PaeP4P2!NA`;&;N zaX#t7&0R|=X!^ADvQ#VG1GQE=thwY$PWg=p?)grmbc2&_ z{?e}xt$*-2zh~EA&%|`++6n=5#ycfa!o$j=@VY>b3cL6ryO8_B?2W;Q!t&8&O9;(l z@J@XWC^NWIch1vPJ;bSCyqw~I?9tXl<@{vKuY`0D`|UzTi=ay4-7eFvIfMsPImV$v z1umL$ejR#YgprOv0SY2|B&4VPPKD;&N)=;oJM+Y(;urvh!04JsOtU4`X}v zY*poEMNhUE7M2isU#y`EO{ zrIl)+LCK%xaXPC;03ti97Zt(NA1LA$&&VM!$=8YU&bU zgDLYM%jZ^ZHn&p`eAe!hu$4mwpFd{DXXUFDYI`{v2OAXM)|qm2G%rv~GOO!2r$_{c z?@m1L)(N~Mr?PtUgptVg_wK>^!8x^HRsg@5Xe4+u;qYJ;RpwXz z$*oxscgM)xAA z5-kZ2sP@f$j`XQ876GXa&dRS`zx*I7`oN?&v@yro0{`^6q)KfGqvooFlh4S)B1&TW zv+e@_o8``F4$=3bU#7*Tq{sZG+yC^8hD^8XwkuaJVd(RC#|!5g1S*ukhH50Xscc+| z4;n<_CSeXSGFesu9;oretw+-=ZlKT)_(|o5Wha789->U0g!%H@_@?(y5O4#t%Mng+8W|6VO&CHEN&rwg&LgP;B`ob%`Bo4f>+8m0El;V>t zj~c50nJkAGlQ7ZUj!SaN0`IEb@38L*B?f`Q^xgnX1%ddZXH|2wY2~fO5b0ZKJ7NeL z8Zw&Z8r6>QI%8@+V%J_&gLao!djmw54CqXEdbca^12 zo^-NItM^DJGghwcg?z51X70}w#MBw)V?kBxh#69GqTsGHI=^qyG#X3>vfN8agDetk z-9UhePMjQ1ce1zAkOo$U*5~-h%3GOY5?Q9n>}KKWpE`{k3h+kK{{=rNZqzmuv**Md z3o>-<>q+*wORWCr(zoxbmL4VfoPr-One*{Pn4;YGTG~kZ==y_(Pt7$F@7wcJ4`Y1@ zjW!{tyfEHBm7z)8$wxv8rvn!nUoXBygB1kgY%(DpV+Lz)b>}JsIVbI3 zw4tliG--wlJX&HG$z)Julp^NAiRig+M|L#{kL}H`CezS<=A%os?wRsn2Tc`y6s#a6 zj6_?1U>-c->?dlEB8+YNQP{I6 zek|L3{}P|_gzeQss#^a#Q)+i&*TIiU7&fnRIxURme~UloL|27BA>65r3CDF>zpj_j zT}6wY@>W|#F;nd*EEx_r^Q6+dulprz@}W5Q)!Owck2zd2mYB4!RdQSqv_rn3XPKp@ ztgE|kvOje`3!vgfE-dbDSZ~kEP7I9p;SEkn`Tj!#nCLwit0)Q|yX$M=>YL+#S>*m5 zjd)4?)bSQlfC~v|1ZRPEMht|($VXg{Z8A20?Yz1i2Zgt0cpf~ho@{U#AhQAnTbOrxKA&QxnKv~Bx()6RuauDwV^R++b#MzflA`&$25nxr6S+3NccMQG zIPt8!2@N~fS6r>BwAgw(5j*v>2jg_@Jmu~i%h!p5rwPMb!%y34-MG&u(KXN$0~!~< z2pmMB|BLa5Q8+yu8Gvn<|F><;*cIU3&jBBy{FkC%#OjxEGFSX@QaFh?BjlTLm2G6A z%yl?Xyh{E!aW1$#ufCZeXU@O)GSnnGQQJqilK8W(GZ(FSOZZOS8e07U6x66c`C&}r zJ}Qi$cFhB`zVn-I6KE7(VhPf}2+z|scH0y+K1QoQJN=ok9?U=Qaa`~r+c6|B1cAX8 zIW$NZZ{;&u(T7H$ULbl^r8@9H%9e2Rn!kSFKX06qi9SFH@{s^GY}(b%cB_ z2v7gIb=^`KEF$E=F%drdey(WO2Ga(icFzVei#lKEqtV~jN|S-F^4MqpKwgb6|VLY1Hk9?FROgMqZKydHxoq< zqqYa}$I~}4F4^OT97-lwxQ5D=wcTcF_$c`V8<_O?@n=ATiowV2gF2bP`}}daA154m zm%Ycq%&s66fOSV_-n58oUt3z+DTkwyB`YB$Jzs96PHv+{q1W&i0_s8Vs>nctC zoc7-;M`EVwNyzcKaM$|gl2LxT6)^0_NpcDODNpI)e-3ogJ2FxnY$^5Uyy+r;m}6Z& z5;j(W>Ou-U7pAH!%lGU29{I)6y4EKT zuee(;Lvl|-wz%X2JDx3)Lo@#!`4z<0`YfuR;-E`={47srfdZ11rz0uPFTq$rr^gTV zkiTM4`avBexMjmz#!TzdBllk*0-y%Ct?5r41@N=~V&o9aI|q-x`;7r}vle^3K`JI& z0(gLo#BTxW>`+}Lg8=TD>zel2tIB!lrs$I^`bMn+@Dti%p52CNVN6`39;+6zUUg7Ay`@Tqj ze&~G`6s|Na!}&)D59G$Sk$=Op(`V$$hywB;xOu~Mu8-L*B|P7H%KdX~sd#_sNyTaT zLuMRQgO-r?A{t_UK^D9EJ5CkG+fNJPaHcY$eq`3}PwNctvR`4IRs*}?vuKxuu9ffU z>R&+ByC}tGQOOhsUoMZ&)T^_A*?wx26PG%R(Nc>RM%PFV_j>4xg|kA9TIMH96c3 z-|6{*>9iX7ptfm6w?J2Qwh69=X#mQh|LNrOk;oDN)n(ZjoTa{lcZHz-7)%zB_~kjb z`^NF2vXfqCSa`nc)aN4S-+G65l2Z7V?)`nT(&7zEIJj1*?bcA#zD=SAaL?#7naR{l8grNLDkD6Z>OHKtdv z{OJ2ts_goY@y=w3QJQL3O&<@UZiFR;D=aRql?t%*6-Z)DV>U%{d~%?ag~hXeU!2lMLN<_tRj zkFU3iizE5ohZ7PYK!D%@LU4C?2_8JS4TC#_y9IZ54>Gtr4DRj{G`MSU3-4q%yZgO( zfBy@H?*8;eRo7ETo^$FPe=vz=p@{*-;V->U`n@$O!CGsc<~dpe zW1_3Y(XAmZYQzxx9If^OQm>s7?Ufm*#H2V4;Y8$hxc_eJY{Qvkq`+&B!OLgRj`2rd znErt*RKY0y+0}l(clzJxX2tsFHHPC5(>#fjJv<^iZjT|hD-G5uhmSF*6;H`utn~(6^xhB<<06;3Tz$e;VWL#(eai7T-S!P zQ@A<`sWYcZ&fSZnTvB5KdwaW-8M;xfI?-*+-dg6m4q0vWIttZBu4vpg3>xi1E#0A$ zcZ0j!gu9OPrUe#ymWD;^9(6~(p^7ip<~Hlp8#>C<7wNZD84RZwSojTgcsINy5)Pk) zEfd1>FCJ7FsywjB~!c%D(Wa)N=zZ0E2h4pc+YL z6?XRZmcP&e!XEA?Qyd=YSKna|5Wz6p9tnKx>ByzO{rL0Ad!yDOYG7~s*ODCM~}e&snYLUFpo{QApN z9h*17*c042i=AlmJlG#!^Z0lRtkkW&t2S;+_yNB)o`Dx*a5AvFVdT7lv=FRz5(xr^ zX!qT8;0NqWY@97J8yF9+q)3%zmDohcF2Me0K{@HavOqQ=m**z5K{^tjp*M8uOV%2A zz^tZ?;iNGHQWVM`&y7IO&&JWayA8EN=$A^QF-#7>2S?F&p(u68rtFQP1(@!hpO!kv9)(k;=lP_gO0vJ?CeAm(u~Q{-;@7xvxVlJ4 z;G5O{&7%BcS1}909pN2DJ~x@q8@llFTYyzk%E9pdF+$<|i?7 z10#MNe*^yN!Lf56_p)=Z7`GRv%dI3hKN=WBoAhw=F~2~0xE|r<%bDI2%z9+gpV>?0 zcGAg;5p;%`sfGLe;gbrIi{qt#Mp8snT?M$^^_$t*%}DIvvV%Mgeqr7DFGF+LiY_sh z*UNZBhq-&%ZtZ8s8^-x=_2%VLnY*-E<>C}3L?EA=!u=-L)xv1ckTxYWx$)$b$lHD8 zCMf)yhg7Io+JdQmr8DB91itaR=^b z|5A^?Wp--z|MM9@yD=Dw`;1%RE&a`!=X6lKI3JIr^L;`&#STzuA4Un$xOs}bjn&H> zBJ_NIb*%dnRE?P2;%AKKiUc380GIyai-lnG znkbTIV*B^!W|#9TLE`av0O_M69+@qc)e?~c)J$%hIkxXymlDR=vt@&`Fjw@IOI~eV z0jmR7dIB~jAx)XbS5ryDoeUrBq5eZID7%KWBM(qXXifW~0R(~tt$c@Dh7l}buG4Ba zFo{%Y>hMqcp|-7guCJdU%`8i*!)gA^jZcV^gaM>#Um5KUNV6&L@c0MV?J)n@B8Y#$ z{{Q!^O7xJ(+2PJJ`68ufveTe+3KpdLe5%sy<;ebWKV0G%CeAsA6{d)asNl7t(ya!h z!_{XI_5djuuvX8Yt483CVUN#mr==#yJw@U!Jjk(TD8Ya^})ZiNO6=n!U_?Vid@`8dZ~qy(^emCB`Lu#$W3<-_1w3e>VLrYIse z?>TL(zo5z1qae=jd>B6#G{JUQD|KD#WQ8ABfBk{SYTtU>WIL!4&JyO?aNm2cR_ukk z-ba_UQjK@FO8K1>#Qr+REzO&$;w_pv=2LTN7^ATxxYl#c66$p>w0$K3s*QU~Udq4i z(ak0TyT_gDsKQHemWb_!mP%b#GQ~_x!PS$ZMva$XdX6HVPIM? zI)KXI7ZGK4?9B3j?Tw=JoFOq$4k0u9W{dM*l@uu7F`oVYY<1$d|LHiAKRWKse};6* zo4>*P-q*oarRD_LDx3YL=4YzHJ`8(E5e?wm22jS2nI+>u<&@|R2?o9)E!Wd`jo-!bW6v12dRmN+6#{{Gq?SgVU z_DcN{?f6!x@s?NJuC}eef>dz)i|U^b_CLEF;hzuopBfYQt$TyNPnT>a#z@zbC+QNc z{LSGsz%#p7Sh&{&)gwiA^2SRK_t`4o%`1tUR-CPAHGzWtqkOL_5|Qhi%pkun-xIZ9 zz+tPN>1u=a))*fP;-wa}#&C+P@k%kQHaD$9x^a*~cGqod39d7(E*F_KreMIIj16CL z2&gPe6@4!LQ1=qs6SFT{SH+jdDhNWVu0lnkfL9b%J`^pcrgD~3;#Mde@kH-Uk=^$r zO2{mpK{~gc{Jb*Poiaj0fj!AdCZ^KEt+~bKOE`ytbTqDOJH&ty4E81s11T4MwUbJ$ zRrm7c(P}O5#A*;Kf*8Ou0-I_qvfoJOg1y_`8-%&)Mv?&_AmvaN*LFFF9K6A3Nn$m2 z1Ed8ZPDdmIT<>hjVss-$t02*Gy~g%!f+mU`Sb+ZB!&nwidj9tgso9!e*`NHgW=G5F zKG+K);Oqm~5YXt8ABrORKhI8k?ar!<2Zm&ow^a|%X>kYC5z9^v%kc(64yKo3CWY)< z!{p~Y$eWsG$^6TH-ANJR52nl8Z?fI!s6Wn)4t8#yQP+HXzz{fp7QTknxZ`$dT#c*BaI(W-mBvx4e8{W$~`+7qN=`D~r^PqVD?`{X3-Q)a28F zJ1xAxFl;C-be&IQ^f~FgfyqfJ4q3t3UL_1O_APnwzG&P{0o9@Sm+KwLRCt#9tuT%I z8aC`uF38UWwX!_Tw@@rZlk?A7Y`H;T=?u{ZE3%=JB{NI2l( z;Q|~4i+*MM6j;Y8u4W9>2dyb5(S5{teP<%QN?zxm{&+VEO}tZ1WQAe7Dra~>=&11O zzfXG5fH|0#ZljS!50K)qLrwc@Cv!_e1Hwffs5*+tRKPPdph}tnnQv^;c%{`hdB+`_ z9W);VX1@1HP%g4@DYmCal>?};q96@Lu?1Nkle|7t^bO%%d>gktI(}b?`$(D~@XGeI{HM#e&c8Jcf!M>*gP#!GUmdU&c2p;CEt$53dbBSy&5g^bk@Nd-z-hJstY(jUnHBge{iLN z6j8f3m5|xLCHb)OsfKCctA+wrg9EnB&~#grev5JO=R#v$tQ=hR08=Bn^wF+#)m87)|jfDC7IET6d4IQGSg<5aLQAm5z`+iRON#w zKN7`_zF|N4mN16MVv&n8j#T+Ru1uXup6?62CS^z0(DVIY5vG-Y8-t1mSDm4H~l4w*b)3dG}yE-nC>dZ$j_1LaoT~Lf~#XHBjTHMQZF7s9= zDI~me9orYV;k;HjS5~}4z&dSxryE23;B=nj%#xz&wH_$e)fz7iETQCT?zd=Bx0N_K zdO58dqP-ZY8Yq*(3XhteFsAufpTVF-Ghq4LH0q42waPex!HJY(F&6$&7qLx6<(R2Kjs>-^${rhZ0}{xOt-%J2jAgI9BeB3*2H{PyAwB^{W~vwgrEI`ZE#Y zb|nOC)x`FvoODZcg}@ZXlQ6(TH;#1@lX(`eOey#%^iSpHTGk4iEXZT=FRQ`BN>|^i zQ{c_}3n@ajFO+$jlg=+xxgd!b3$B3gT2w!6KWaTCYH- z$KdD6&Yx^u$KK57rHlxK9xfr1@R(`h9xUiEFk-pXLeT^e&>&YmoXYgR;S2eWQIb#^ z%aBE{LZnG^X6H&TK`TNT@u}zP*b|c_r2r-Ag1ec1FLV-{-lf^?G{DQwFPIhHo~)@j zK`D};y}BgbI@x8G^~37)uen4Xrvp%kc3iaX^oPN6Sj($mS=4!n_v&|p1&prKvQN6* zV*L17^bOw+1jlGGn=0qWO47#{5*l*Ce@tc+DAEDhKlAqSy-NrwrUZN(h^IEdlbO#X zH9H(t0g&vibt-A}p_&7(EZgRCU}Z;y8U|@JmQ@5Gzg@VHp~fj`Cd@+`M6pM}mV(GZ^D+sArmLuxi@g z`&gCaFhNx+sb1-y?HHWrQzyixvpJ(D{Y)_5^D`waI5;(#uuNL{qJhstidUQ@sU+1f zxH>620b)KH=Wu6UE`XRS*Jiiyc3Rh2f)IZVYKAzdhI7~y`(2x`y()P(+Cd}AT4`Qt z9@sDYnA!hpXyz_ce*dS(Hk6M+5=}{amvBj{lt6WFb4xb+l4X{RuRKbanq9FyNJ=0> zfx~*qoPh{`P(jcYqek89s~A$Qi_SM!`XHOhpGgM@a-b^BaW$`$Q9#{spkIFi01V@o zm(D&!?WE(9UQzd{XR+U2P{p?2qLI6Z@=Vnu<;lS@zC0nT%c?I?F4eN=1=8lk73!|{ zo61|3C9*taA(q{Pv0m#80I#WI&S@ASKe58%NRLMMa_t$v5##<^(gMHoDK4A zjp681L<&p$vvw1`y|+P(4fl^ja0J~c_LYKXiY$-h0_2R}0Les_G)$-{LoKs$rw&Ea zWWLwB;v{~A8jA_xj^$xy{f=->kPQX7av;$Sl3XRWUY`Jr2gRHvEwo@AAb{k~@l=-W z1q9j^e?zl7ud{Qeu+oaUf5o%I)-zeOzLrw zgSR=EZ{p(K60^82A>yD7&4^$XRm3k!`1BW1AV-rIZJc3x7D?U51cOFoVRKS#ze`~Q zU7H_oWV%mUidbz0H*NhBikQ9gRf`i}snE!O2s-@+TcTeJu91T6pdrL}mYCL^C2O-w z9TNyOFXuaFmv1b2uL+Hldh8S@4$MY!dmJ43h}KCQrY463e?RQ`>8xFIDd@(F`Ahb)GXGAj z7=g;(44LI|D5#S|s<>^fB-rPD8B#__DGlnH3+wb*jT(zvt*G50*zp%DF}&^L3$skx z%vQ5CcZ6<*It=6SAxt**EGR0{Y{6U(i<3`VF}g}NVAV)NEN4b?gD{0EyHMLgHy9h5 zndF>tk{4VF#XSI6w&^HQc6$d2_WzJg%tMVtX+v}j5>D*T17PBN(^ALr%EQ!&e?W$Cj%>P?7% zld+3{qhc(z2PsY?=LR9>6^Sv(X9=f`I|m;szH}nN^|z^`EyQR_Oi%wut8%yRRJ96BB4M zdMC5edP^_8S*`YHWI-ssHDqkrT7=v1V3)I)y5YfG_WcG4puM-GUzb;%RmOs>bMwX_ zg^>4SfyS5w50!FwcNUL_g*3)Rjx&1Gj!$PkD#xW_^=t{*l54MNyZTEwEVZ*fi$P6SJjC~H-gz&I~;Mn1BiywIXQ8u{H&JS7P2 zg<|(6z6yKrTOnb04y&;m~-? zaf6z<0gG|e7k1EGwnx;GOzM2xKwgOW!H*c)-9t^Gg+r@hl?>Xdho1#a^R&~nf1Z`@8d~U<46mu3~Ri)Wb53O-IHs7)MS%i=8`IY_j z&!kD70Fnqg5wc4wU-DF2?3V%@D>EI`Fn2hEUo3y&@I0~TUJQk5v@zl`nltzg8+cL^Xr>V6wu`UZj;9|`~wN!Oj zNk$U5-L`O5>^S*&9&4Io1!EY8PUGCSB(*4|eseWn4zJui!UZ*G39+G|^M8>gZ%TOC-cO>t=^5H4l}QWn zdh+u1Nr8eB8utH-fO^Gx@s}O(cYHfJMZQumga|E0;k@l4yDw@UG=R#Peg*a63+z@U zY$)f;==H=I7}n&f@7}~tLD#>te|Z@%hB3TTk0yp!YUl|yuP{my7`#$<1f6> zYBlw7^K}z$=Cd(R{!a2O9hIwh`O@=mI{|U>ZK*IDK9KzZ%TAAX^NO^jNja|1xeqm_ zkX-!mGLE)@hz2&bfhlzRL5rjMMZ(Y>^dng=t6XRLR~ro|I+IqPCz^2_&q5!~ug8MDx~Id=wx_*i5a4oBxn zUWbEn1zW~#pBnb`B@kwCD0YvT-CQMy%i@jVNzVK$WxYKo<$QlSsQzalXGZJ~!=;8X=p5q&_>lH{6idsS^7X#oJQa-)Xg# ziJEs6=V9dCCnyOEs5KRcnvcbi3v5jp$&>5d5162+<$IS=UlJQGvqX;nj%vkzaGpoD zDur6)`jR-e5*Zb{)grgOfqG(@j7;S-XJ|dCQXf}*KC8$aRd_S6{4(D}rwDIg``ZL+ zkwgUwcli?Em3?4J3d=aUz+vrdM!FLw7bM= z+7jOc;{U1vync1@7gGNY(T-$c{TuJ@eAGB+^{&j$^4PXtDTF!-hrjyN=>=(_@SRIrWPTW1a&_fO=YC&ASYc}o6|uwHvd~$?p-eecbG%mXF-v?? zKNRs2E z%jSeQ_9z$hLf%~ztWO-NiJv8mmMAVt?ib7^Hl(vcrLZY%6gG7XCX1KNo9L(b)EXOP z`L#fiKU+PxWO21dG#WD0nJW|zMq8fmRUMxyw0T%dQP_OI=QT@CHDXn5c~3-()mYm< zCe0jClwxxLBH)Eafs)^+H1i&ow3_g)tRju84Xq5uqftdn8JRw@sc{yoOVW9-%FnHH8$TC|==PW{#83BOIYX*kv%>AF%I5lj=WW11ks&)>28=_a38zp;jnVvRdHA> zH+{)tl~km`JURyTDHVNQR%0<5vL>hUnw?m`WfyP}r-%c`l(i}JEGB`ggl49)AjP5+ zt8pdG>XQ6E1+kXjVktkZ^o!?3E9C18QLQki=+`;qQn^oKB9jzC&{hz|EIqi)!vfx| z@Es%YZgd(V6CFc~@WWqyA50L2Hm7E<{w9SLoFfH=fz+KAi4<`O$=eB)@-d#tDGj~^ zG7+mwa^E7Um)NDqY#Dieo|v*Dj7c~MKbGS2@QS>5z6JSZBxyuDjm)L-R%kxlYxJ7? zK$-(XQc`QcPJn$%RJL74cOs{qZ=jiClUIhQ^YAIqJR>i)f3p){FHHUm&Mxo61*qJRK)eDf9HcrX#cMmY&pTB4;NhCtR|Vl+Lw7wteUt+H_X06knSAzEJPQUn=m2hy2DBmpQRjF(qN)7hOlE&3d%To!ES3I$NLS(c6oD~=jM~d;S9#X0v(LNIJd3CB;?I2 zCMz`xjEs87B|8-HmM>E3-JR`OF#r!c5cqM?_o4@c0#I*3<;hj9>YW{7QbDWkXRL-G zKOr;nC`hg(G4PJb7S}trILOZ_Tyi~GCh-?tgg=W4l)gs7nztir4!|2Wc2JUdPnA#H z9P@|zjyetIMEyWy@ATwIa86_(6>-N}r#RPEsHj;bL|rLG*PcF3^|n;ML_SGPCT?WC zJm=^*p8;Fy3ktiF6EcgvGoRfQo6C*SEZKR>tb9{3PCIz-FxLgrn0mN>z;{kII}GVY zj)626x+S*Ds4lqGqzv{3lnGeGMU9S?EUOUiZc7R`=z5v_UvO7d#9Z7@PE(ErNSq$< zyi@OE)O=UeeuCB*R}yC&9iL-;u4OYkneSfw8Nx`&x4E`%gk$JrHa=R$9V#B z4g9P^t%6+lO0z&qZXhMH7@=C7M{|ii5~)pSJ6+dT!#7+3=5x{^=Hk;K=6@5+P*IPS zZ7c~!dU$$r_H*cS=X!^#^?>fwM@rMgt9te`ffqtTvmM4<4>d+4r|J&HBL?%a@C!au zX~CB{Bu2@m>XRZlHs~UtoLb=*Xmm=mU|KGIqK(5fU;fPM1cWvPN}Ha|FnR=<(C-zS zNUNS#Pe-Crk(1D15v4cdTZYQ5wAR6I`WBdCd+MhHh)Xu__FwD3vzOwdc$zv%yJ_nqPXSEhvk|6r7X_EBdA?GOuRK@Wuj8 zJ8QR@NH_H2qA4x>!@9ydkhx>X^Hc9|FE|G5cfHxd-fQQ~wsE6V zd2Z|cwz9E>SA$&ZU@+CKUM`h5R!0hGv6DXxndci!gh6~C&fPb!h$%-0;ALdX^Y9VMBFzbIWF`!6ixkbj>Za$QH&3(gR-QV5c97` z=ZkOr1`AG_xwDm=a~+IWPe3|XM>!uo@e9W_?pif+FaUI9DfSk_Ka+NES~cf}&deD0 z*B1MSb(m|%o@OK_#euy~HctdiE)fa_g!mH9VPBC^6_xQcI(Dsgu`0fdvOoJdBqn6V zK~0d~(b%_Tav<3esiHxew`F7HvLjm76eUdcjofuCTo9J}uLt57H8r#%7S*AO<`^|` z_#zgu#fk@MWYuPKcSL1p`1i?5;cInEJuA?7)#*ID)sH&qDik@JnD&|^7aSXP&w zn1-+3mJREadCKtInt^D$ON@4J=)GMn!VKlACL%4Y6V>m0is~+{aIRGOArK9YS29aa zEchAqTD=i+ORW&xjCy>%GneBRxW5CParh|hI|xvbuDS6-qZS2vVTX8bgXNr)3dvP1 zwLudDva1D_wu3+UJ78za@Ff^M`m?~Q2k+KR!l!&TA#O&F?lmo;e2YQa3?9Z0m~=26 z`bKbmow@ZSz!6T52G?#pG~7djqe`7Br(C2XrCcQSQMpJ4R}^~Bw4WpS9*|I&pA4mH zrWYi>j3zJ;7M-r^Vs9B3oVu=+f@ZD{t8s;Up({~8^6-YN#DH_59pfToMZ!=s=KqxInnOunt|X1L?t!a-P6$1-Y)paO zo%Gq$PhAmQ!Q@zGnYVcxw7_Qtj#rXK`=?tw1{nHwRuZWjPZ3}k_H+PJpyZAh3$l}Q z_~ckj>Bb2-?*yBs{tBe0Eca3lHZ|^T3f!v#ue^V37#|f@m3^3u^OxAl{A1;kCVV-u zral#p{Ao65IkA2cAw+6*WzI270EihA4Zcd07g@HYi#8!_G`&Q%Ut+Ts-4CvrB@UIM z%!p!%Z_1R5(*ml}f-JHz#r;GifRC*eNI~*nKzelVkeQj06Gt>AVDnGh7SQBYR6dIr zoTS407C{`3G^+wg^zQ~~q=8vWsK30OmD2oMH1iXaDH*geD`Ln2bjrOEdqPWk*#6xm z6jm+m2e|GlT{qo5D>D&v@@iq2fp{2b$E`Nrq(IlBF`5%s!rlwIr%rj|Qa7jT7s)C= zZJvrvJ{i9mm45zZc1Me@u0WDEmFoj+}TaT2O|b+qorw6uQYqcSd( zR1a=KMBJ#v*|^jYEuvJv6rI)2 zrrUQxUeDn>FXIH#vm)&0!)0vmUNd@Tl#(O@sj8pcpni(gO(&egA}E{#>a-4{$SeUS z$WsjPKIkhFEq0{lgm=f??Q6EV3nY%6?iXz1;Ud_~iXHxJ7GXm3(8Hd<&JndYher4tFZ!x5KIe@=D9L z$5`VDfVnos1!ewO?Fsti9|ZsFq5hjVg)@?sK>3*GM-uQbF7}=e(E13@+TIu0y@pQ< zO#VuZt63Gep7={wO5Fctbg{x#Z>Tsn5tidkEN2VW;m1Kk^SBz&aV9!Smto2Iqjg|KK!h6 z#>M0ba~#h2&InZfMo3voC1BI%WT;np;Q(M5gP4oQ4OnGVk|AJYt% zNPR#|y$Fkh;M6=1pe;151z4!gIW*fOncRllR546K1PpINSe}R7fwbwQLU&*xn&90G zU=qgR;AE_Xpf+%wQI%>L&fcfu#L!&8G1+jSuOPs799&Ssxq?&~JR@;G3S{(z++L(F z0I^#)OPgwUk9;sB$~+^wYV|pP&|{-h@i#nQd{`;`@VWdTPGXf_QC?Cd$2zVRY8Kr# z=yW&TF%+^by3X48W=W{uu8tv%@JG(SsDb8tVYk7zF0iLN>3kci{vA;(*0HQi6zbID z_nD|&iXl}FcsN+B9jv(agC{3mj;%P}a7+~3LDPxufCb=H7yH2>IyIn3gk#F-Pggr;Y6|1P;w?|nHIWINGC$Ny8~ zdehSXikiTXkYHo)&&m);lfrA6!57oGg%ECYHGYg?wZ<}BlMimU@g+4`D#>4b?+Kb< zCUcuW{^Y=7vKiIA6T`PG{q|^#``EeZRK~8`xY7>J1ALjR81Le zPRpN|$uERG2|V9%xr9Gi^PxnAF}=t*7XmZo%HgGq=uSKFh>~}rqXtQR{KcY+qQy_6 zmC5CapKZCwTxcoQ<%texY(y29Y*+PN9Xhbp<5Hbn%=f?K}GXomW2$<2pGoHCaCKL)A~hc@fbn~J3P60-Y{ znwQh>2S0eqVkX_DV|=_D~!U@b}Sn?Tv|w(oTW z4OpT6w63qE8;fJox)=95M)jab_~G^Cbfj~$KQetLxMGI|>7HwR)a;X5A+x+uajfXc zW*wxQ4Vnh!4Xuk9B)8<~AW?99(&R6~xd*u%i9@LZ=!hz8J|S_`+sKLV15F$+EKh)+a7hKom1v^fz`yv`P^ zeqoq=d#*`^GjfXZZ-S@puhM^$5|MV~hnGR#!>|CKomv7DR5~{kr#Zf}HQcbTA_Z#v zaYL$d3y!^9m@xmc0QvnrutEp&9c z8ic85=kTM3aSrp1&EiLxkq|jXF|76T_c6z(BVd$VXk7>3Ixtt=IBbAwq+zB$U_mR1 zL^dvBjM*9V$s!1?&O5o4W6iH}E8r6pmb9D6L1dYS!mR}-QZ@bKO)l9)aqr;;;8Dve zgR0zQ2nTZg)wRtVwx}st<>C03I<-t*Dg3(^8x7LH3RjYZ)^D_G&@;@0X(*Zzw^~~2 zco~4h1x%11B9!F~ryrGRz~cK*4>T$9ZlD7E>iKf@`nnlqk=HKs8t?o=9o~87weuE_ z=>n8(k^3N<0n_;>g-w<}(V(fk1U}zRTt^L5IroR*+ z^>K5e999l=Apn=+1MT=qyjKh8Wph|O3B`e4)%A$7D`dUS$^f^Uej*L4sFT#=$7caF z#KOzW&qkn+=k8nTk71y4up`Be=$RemkKuH0TDB%AE*uQsy)i;$OBx}LHwz|Hm1d1} z;rc`{Im(6U;bi>j(kd7PhKW4+M*8tJWH?&ac`FLTRE}fqU|9qfq}RPpsoL}oY)9r@ zis|ZINe#_afkup=RBn)AVMLoKlN@VD*BpKjymJr$4m}^$zrucWHqgo+$#ukPvAWFK`Ayp|-TMEbGSN}f;8pDh4fCm3>t7T& zu2P?cFrK$QWu|PW)4Y2L&eXlp03Z_ycHM{pWyE!te|ABPMF*};5HKy z+ORQ*X8~M{YzS*umHCoyA?VZ+s6@jno|a?14z2Rzg6uBe#9K=sJ2FT!Rvv7$7D>O2 znZJWAo^QO3iGwpkZ8C3|z~i)t(!_h6d5%~20b14Nve#7F$dAFb`5JS(jPnOW{&D5C zXLVo$jLex(!J&YTyY+O0m1IF%#{4gP2xAG^s|C2vF_1FVmc}xo3>A{#ZcbJL0ro@M z!jHYC6<$yi2=MSeqh5MGQf}#qZNB@ZhpO(!Y7d@UvY3a)ctt21;jluc!URyui>_{n z5KyA%=pqrKh-)iLs#YND>X-tuLkko|<)mB7P#m1R)eufO;%_SXD${}$42q!pm8gG5 zA07<)(mX`H7N;!zf#fbz)3lQJ!u=D^1_2+BsjOc~V@wk@Mv3;*)@psB;*L+DA`}3M zYuM~7R1OI;6zXCNOI_-8@i`yNkB*8l=rgX&K=@CG;HWvHq2=#^{TWN%kI7{>zwEgl z>+#W>e95JO(+w{N7x&G%q+!1rPV)S&(ovD5Y8&N|*d<5d580rTxK&#$BxafpbZWqx>s{x0)_%9Ob9Ea-&}t?v^{hVZ+RPjlSg|NnaO z8Cw5``1eyu^r?*Bul)6gFnD_7mUYU`nY5JUK5f#F!aAOkv2yHWRU@}+JIWU|uh(2z z)SkW^Fb(XYeO=OTEx0gXc6#El7Y#kO&PuUvQEo<70%mBE&SvoVmdt-zrM-G&G8Mq8 zBNZ>>&VLnQvz>PX!@enaJ{M)SW&ZIlqt)tGey3b6Q8kj{gs08C6EQ~d9EbJ7=wd5# z#FLLvf>?`LdcU*lA*?X|Z30AkDsfdKF($F_N14XA~YOKA1Z$i?*u zjEvT|PJzRb@xuco9qd{uHc`*I^r}?ul&fxfyl?)x=Va5p<+$}jAWi?Z+tS1LjPJ)5 zhbH#s1?^Y+1Sa+Hns8RU5cZzO<;07$ZW%_)=0@hO+o{0e!`!zOrI2P0@#n?V!;(!0 z>r$x+xtZoBmd_V+p-aO`GpBUYD$-i10fAc7&NK1>2Gbjc5RXQMzza699y;rGCLzbus>32Jp>bywnyFf&5o?} zOm}FFtMvnU*K>s;bg%Mm;(j2gg(}NSvyim?Jm+!&OY9#SlSr5D(_ z+Gh>yj=7gcvp&b@U;740X$+-=ohTKCofa9=H9@w@&F`mb!Di+~!zXl*+|267>rgFP z>w}((=Bk50(DK2dTa`t5SyMh?&2{mo%Vyfc^7rCp?UtBL>*zgHc27p#RJRRqlNJ>R z{T-B7`f9-W2)~NC5TL7lZ`pRktkT+`dC?j0i)_$yj#S@u`@>#x@ZgP!zH?%7K-$`W!@N_=)N8ly0ZVQB+~#iVh4iHxwA<{Zx2sbH=y`YnNG|v?!;?peVhv=lT{BuSmabiJ3KpBq&*By zaIxJCkk!jKrO#Z!tEHd#IdVPC`&bq-yJxU#0XtKDKzpjt@(UjWH_H!=knM{Foztx7klp!P*ml2F)A{aNEcB9VhzWxf) zaS%|a9I?edFVoeKRdplK({r%3!jf3g4BlFSPuUrl2%gPHK8#E=-P4*N9Z}F_$jBU9 z{t)-XeX#9<3|2sM&#?#iGU6zcXi*{k-LM)`mCoNXUR@*CTD)@?Fa20$Ac_ZA{CXXx zP-Cw(212g?xb(+EC={wX5@J@P1$7(&W|> z_r!m4l-c~wxG8Wk8}TeE&v>pV%(&mx_N00H;!)T)2ALF-wED4L{AZ|E>gUwYwASY2 z=9GIqewJry$FIwJ5;)h@RF|hD;lRN}Wdl0IRUM@PsP`Pw&Cv?dE%BL}P+>M30qtrx zoZL4FlMU1y-Aq2dPI^5W$kWM^scs#ql73Uu~m_KHGSY;9x3H zl5_)^Mm0*^Z|@l4Z+HbKB++J3-Z;ql$W(0|nw?U3r=J-eHGDa?l#!v!I7{qVV7aH2 z?`|A_tiup|qay1{tI424qrrC(Ak&#%@U!WHklKj8x&1cN{JxrZ5h3u4*4V=Bv7p=S zw_Q{Q-sgLSr7`PBCW=!Eu9bh$^B09z{iFW)BqzW)urK!HDC-11NJ z|CV18;5d+4jvn&Ex_KhX zvz9E*B`SX!16S7Lcp5{k#Un61oN`HwHzFJluGZpPq)#RQVUZ1@IP*|_IQe+u#60Ch z+`8uPVcjY^Y-X|r{}U-A9=_Yn2}nhQE83BLQatG(ER2@zNnbWCtn;ac{e8^@x67oR zoB8=1Y2AB^MpICynalm}pRTgkR`;N6tl-s5g{4JLL%$D1XZh6z>04`QV>bmdbr&-t6RxTD|X^ zu`|vFP@6-e?|xnxbb-$9V&K)1qY+@d=3EzNQ7{@X!THYV3-!vee#`i1f=Y};lzIE)> zbf0BkXNoTR4L&|i&sR+Ws%u#;?gyu$eCC>ZS5GO4(s}yt=;w)@XY4%Dd+6thg#XAm z+W*GC@z3}X|64k)KTFq&q37{y<@JoiF|SO)zUG-tVJd@u>?fe?e7ikMTLHj^g1ZB8 zj4U_v9A?umxKWKapPNn!L9Q zfAdC^6}d_L!F@x`EQ>Nz=o+!-V2Q4(`otJP1vwA9w6v2`o2?;u5O;u{jT<^I=&|qy z{i^{`c3seFn!-4qDFP^a_Qqrx?=j@|mllJI8eVFmMQ68wX1TEr6rn8tjQ@X@4*8GB zKm8B2rvIRpGD3+XKp83p^}g-rE-)xVE5i3tdfhFo9v^|HInQc(L=!R2_iQ7u{@)p36pnGkkpQ&16P)4 z$*}orCThb^jAb7u0AV&MUgY#(?!|JHB} zbo0@HmS_gVg2+K~cw!;>C9a}`vPr^Y9kMt_%=~HPCN-0z2YwdK%zJ-EF&(Nqn7-i|*_7St5@gx9#OIFLPUSA{G>pXB%s z)C7n7|C;3=J&rWYo=2?Jqdp+tot@T4Z4Ep6>Y(q4=Q?9{u-9?icDN$~=Sn~6$4SM$ z@AQbstV8zP`VT(nGRvNfm!AOsKfc}qD(bKKAGZ(zL6KN#L13kpkVZfeknV=1ySqaP zrIBus?xkawE~SyqrMtW1|JILwp65CL^ZTAX#{=upbLY-$UNduN=3WqU|Hrrn@p24@ zCmoQAjHbDOhU4@xM-MVI*MZI2xfw_%2mudD(Q0GW+`~W|JpX*vmnFKVGfUYww>2$_{w%xm7bXQfu}3=E9M0()Bb{5NMeF&rRpmWtO0pp zS75mZUXU&4R$z$LcJN{tJD74UC1O;2xWl!xurb4IyW0u9>)^fOtW2WuSN#wFqyFcA zHU{w@^*{ZqeoL95LX1F>4hWe=YfGPZuz6MXvum-1PS z@D<7SnDH+g?gucQ5Hpaf`UcaZV5C_rSFyj_xmCz6$nhn}^#jGUKoKp#SmhX-nDulH zyGjFrM6T6!uF3v6*Yv_(;Dv!A>o(d9K>P7iwaXj2Hz6^b-@vADKB?~jZ~Zk?4iyM0 z5UgQzQ-v)Pmi=L_pO`V-cYkk2ryl$>{U6)+SiWb9ApEf7UYTG!PdLG0j>z)E22r@jlnM|?@!PZikjotHi%e) z>O%^ZFpQ)tXC$_7*&!Ey*iA4oaQauY0jOTwO`|q`UnA39$#t(Uo!d{f>})!q`jt(qVzlAAEH3+wZR7zF{#t>rXNDAVI1ZQP6xRge3e@qD-V15Tf#L zOxPUTZ}t~fq;X^4eo%)PDh=DJ`YA{^^K5+k8x%T~XgvK5TfgOt>%ab%gdX&l!9Dr^ zsx@1-LZgFzZE-xA&oSki6rx&xfIGYW`Yj z9x*4!M|Jl~FW50Ai*^iw6uWS=e8Qu4q+0E_Gpfw>{SDMw0H~(&%?!gi>5A+}mYB$Z zGazE*Jsz{IECpOs=gR4n?L_hg%Y4RH0)U~({y>q>XZgab*)|;cPsSDehm3N7eCtN5 zL_@#&AGXf;t5inN47$?cwNG|Xap<(Y2ib)3(6RO1EjHtv^Wv0XFrj}66f_|48=u(K zVDefSBIXi6n?jAdrgQIHE6cBmXWFNS7Sj)!aQutMbgSJ#{cLKOaVFH{c%2GwZk$*KF z`)JtSW4`Rrn8@+xfZi%i`A-Msif5gYV}+~198eeAlFg=+!&!a;>8hZ(dT}!f|Mgnd zTL1EHWX>*DA!q`2bA0gz>*#_VePyqz+@b8~C@8M*-67zI*dHgyeZbBozjyIdBIBEb_;_%ko{4LD z%jTw2NK~-H*cI!+TNKmi13bppfXK7bM^|8}zzL?PMhS>Ep-lASu0>J% zzKJW!w%4&LOlVWZ30Fqn{4PuLjOtTNGX3bya(e!HfZ9DaO)QHo>iP4sJ(>n?@F_Rn zS(LeF4Vqh`k8;SzclL;Ud!4#acgyTDOO5a-RpXG4^-e^!UtSuCm$Wz8*pf-KbrJI( zED6$X)!X6v=F@x2kJKdg2t=}Rs!O!sRC24@FH(d|j#oZwl~m(oi?}W_dk|ITZc*Ti zYg@NXCrBA|tfv8`2tG96QK1rJjo7Ylm1Kz~a(+-SB2Rz+eqnADE%^?07X9KQ#xIn; zVmdK>&plRP=-A4j^%i$kuv1$8W4wYpuoYZ~j1IAReawCjBE~M<634nEY=gC}>|~~G z2aMHLl&*LV<-pj_2{s&X#MSfC>z+*$*HTq6bgnUFX%`~n&Vep1Iwk7ZoQO0WD`FkI zxwZNE=h}EW#KnL%0XR&;F8m`Sl6u4BqV5hIR#-{aQ?W?pQ( zgT|+5=nlix&jPwPx83n)7foSL;Z@%U^d?AVE~@2qk7uOji?ken3@lD2$l1VF+E2IK zA;pa0&yPa{kRB9+Ya{kmCrt9oJ+ZHay^t=nA9Nu}y1Uus@#`+-sQtJc`padG7Z*Ha^x-3cp3eJsx(QN>&x22X;JM+1uDU;2P&!$X^qg8IQH~uza);knNf;I*_W(z z3qP+#to;zd2Z_2&(Jd*Uolnvlj66D1H>0e!{+wt`RaN?daQ(YO8Kr5wuU!?TS)89O zM^?V-0}RFGSL1F$3QL__uKv;C%6Qjbm0#6cS2=T5l}@>hJ&Namx@8H zKfxhRx=s8yjlx*n{cGtuaO)dbn`ysGLM~OTlbN4@RM~en-t;&$=EgFJ=;2K|=rx^R zV=zh{wddDY@s|%WC+(Xjy>4Zn{Ll{S-o&ZgVd#r2czXUu3L(SW&f0^d>0uSBmqDbA zO*YD?vGGpcNiv;dM(dZ0=*MIRvx<+tVD*uvG7X6FF^u^{^$gcE31)3j;wPgR+#vA| zk#8v1P-r8VRT850I<^4A>`Notx%3;ZC1}R(bHSI<0=)?}BXT|r8!q8uwl1Wh2D&UL zNQ3g@Ubzhj(N^T3-)C8=9of%*gA7R&cxnNmq~t3TfxQ~;4zvWLHw{&Jrfm1>jt}73 zI(&{c2m@VJG5qGbW6^|%1N6xybnJF0DtY=%DHF|VFYIhGlEZ`#+Ma~yi}dS36`eTcT2>cdyKGceuDjc7N!XGjB9uxTsmeN>Y%sSPLgv< zc0BE5c$tNXHhGHApM2;#nX}(XNCaVaf@(HyNs-KULJ#0s3zuQ&%6uv{H)W~48RyWR zLsIoit;v$b3SD+}x3nWa$6*V7muEotXI( zc8$t!4t)GtqA$Z@0=rjJrWJowZ}lx7_dtmtRA%mkx2}^zT&MV8W35SR!`jt`>km#z zGtM?6AyiHV=z_G)o8jiM>;QuEO(^+OE7t`W8ht=!KLU`EGg?r(*biB2ZGiZ3^$csn zQnXp2Rtu;h`aG#YM5#U7+J2{~(ZP2L3?a_7+Uu~@GL2cg*LL8eJK!s9UG#RRbnI9P-rNJ$k%lY zZjezd@v}Bag7~RX=ny;-13)I{H+U4?CC7qhHFvi=9rI_78W(z&{BWxTGP&^HuzV&4 ze^|j&7rxDz1;L{3O&^9g#HVDHC$skN81X41U&Wt&DuDYOcaMwBgI<^8MY0wbVlaQX zcpCjVuN&9avMBD9Q&gUh;~i1A-=Wpt0JlVg(@Xi^%+~L)dO5l2j2oz=`giwRTHH$)#`5=05L>k?mzwv3{dZ3XPv_swB={DY37v53(X_ zLO?ErOLh(m4EK>q?|XBjSeWxDth+|5~J5acel~s-*icyu8s59awhEX+Nk|fr*}jK8WS+djCdPT$sgUKY(x;3+dgB6 zOOS)XqZ{lAvU8eHVEJlJJwJ0&>Pq#jkmMPaYbgZAeBokiv88LULF34$ldT}eTl_Xe zEnldl(c!aL7L#(hZk3CFd7@8jk(EE(){vpGy53$Vz&Em1P&CwA@s5{8Ai|~NOMr>E z#}>P0(xq=@hQ!t+Ay2CGB8P@6_Xh?x)%Rs*E!m}7p~P$2l_y^OCfg$)4rzv+NhpWH zu+6s@`J_EwUrwZ7(pe?=s$cTJ-MAj82@_J8%F>*dp*{(U;ZLyI=06elu6lRWKR-|1 zz2hFYX3?)zkUX~&RaFsh_Fa6a;;+lDY?o#IIk2%(s6;LQtt2mF^fN=#)r=GhrP1#xU{J>H z0&uIDY|Er91$M!zh1IiAeYwuu)oqA;FkhWc+d838Sv8fZ>cfK(S(?$Cdu$@r0wy5k z1Je!urRYgs?-4#nXphv+ab3(O1Ko!MT|3DLlOh|$^qO!S3xW)EwS9-n^c`UM7s5R5W+@$2-mi)i`sq3{G6>YfQYhN z4pIL`n16iXj1)A61oyl@(ggy`o|JQe8r$#UH4&JfAzsTfE*sq{X1A_0+eW76rRtow zhW}+*PXA+a-W<}NAVcos-u966W$f>ow?w4#dZ_m;`mI6-8tbq2>vTI$BNJ-8Fo}j< zDX=YO3G@wCR4VrhI3?RV@>HEp#~Ayl)3HRS_Ih%teiMdB(+!M{P4NxBdY>hDFG;Q^ zlzqfjq4txb@;v+~8f!a9 zOcd?C3GFXi5Osv9-y@?Sfl!^OTKPIDxG|_)A^crM#)nMVPZHoD>ny_vALSCE7&#mc zD_%cSyr;(T>6#23LroJclXqK%v{x64`em3geLS%HpkLxmsYebkCRv#AIs^%J4u?hX zVdlhkYch1GTY3e>{3^Locm-e$7ek=-dKt!U)0(#)&4Cu*CA4`qIX*FUwjtPUa7d$2 zB$y%6-E{v$=r-o0ql-soxY|a}enXXAYLp14tAuB4ULAvf0hf~1@$rrv+HfIae%{$h z6l1jqWSC}Djo%S`_AYIQS2!*7Ocof2Do|bP0Wl;w#X57s5acq4mbSq+DT^XyBV&5U zH3`)y2DH+it820p%%=gx+d2EDc}yyl#)-9&KCuShWHXg0{Ad#y)%uiw%#425{GkjH z>!>=QcDG8bIVB_6U+i(5?^3CE2_vJ|@F1B{RO7-TF(rPfkr!Uqf4K1d9sxDAmipN* zb@bCW)%uyJd`7q8t#fJm_GlEV4?pFzX*2HeTk(QB=+V^;0X*U~<1NkNuSILh;tSqV z4|WW+zEI204T5X1YW_fWGKrCYDbEt1gjUu{>~D7AEElO?Bj!Fb|) zDX}oZ3@^{uj~{&gM8PCNXD|AD*PU!sy>Q!YMfI@gw}3ZrD{c>5QRF#AHiqc#5MC>m zxLy>XJQX=-J%@i!ag4A&F8YrKz%Jw83>tkQ|aV9;a+3$ilOL1)Z zEI!r0b@&G1^}{6PvvYE#GLH(Qxtw?r{+lZ0?74P{4^KW3y2`b0)TdIa)L$TCTkG44 zh|5!|ZKu_@mP;-W+Q*lorLISmIJpycSM3t<-g<5YD4^Nek~|-W?Gfi>*;&UVK7?25 z>d9*&2vDbP;8o=U$vbUq^a5OL0Q3z>*MK*qR;XD`-dfxAD zV>EFvg~CETe_ZDlhIzZL2UfAb-_VP#stE0WLn$BMI66Z#Lp7Z&Mu}@XcbS$Vz^Y-{ zr-{{2byvxgeT6)K{i@QzoeMq9u&Ti{FMYX)7=AGDu+I`P((j9v3F}r@hZ{{2Z^T9+`e)-CVF< zNYWA=IN@4~rRmAee4#4#S$0k#j>ajn-l_7Nr$l~&LU<~s@o4RO`f8*IN`2%$aZyQX z`0dgA4xaY8d$cChe(~&f%@7ra+_-H$Wg7a_&1D$~ZQ?AHohQ}2UClG@s}4Ro4ojC1 zmzs{i=fu|%9c%iZXzH*ujnRu`@FeCjD+J}8se**M{qoPuOe~Ze=#z@FC6U|u0qj2| zOqU;3Y3l;RGecVMe$1~-_f;+0M}PH{s9H$BJWh9WKBBc(XId)Xls z)~(e&FxjluuTC!aA~G*2lfb~)6_tFdTDH$}FNNi}H$*XL$B1s@hXUF*C$ev4i zNZEK2!Wy0+I`A-=eh&wutpch(>#nNS+OCDcKSZwDGjwOj;XZ>70w#l(0Nb2H8FJuJ z)$aPWZ4-0G3?M%mBEvK$gNzt{=t$p(nh5GhK~Z>atdF~8R!La0u4CnkIty02cVIL` zxbnc9fM)4aPVD}Hjipev-W~9Q5bavLaPhM4&Ov!Nt(f9aSRQ`G>53)#YsH>ES*GAF zxxj(ba+^JtP?;WlnEtji8?Q2-(Dh^j(~vw5GI`6rDupdr%_UMG8j^GnQ=L*oLP>|$ z50vVjQ}Wcewhqv0UlMdTS-itmE2t^)dwJPS|fp><(26kKh~EVP>*xL zUHv!WV0(%s{@bSqe9YsxqU6yJ3M{U^i32eR8tgmvKG0PES+>-bbz;kXZl%aov&IF3 zTED4RU89!;ML3)^bl{0;I(AR;l>oI;SJ}_=)a1Yb$GQ6DxC-6 z!^sw@wy!;ZsA`qtPH<^=*0GDj7Z{sPQ@3rdlPx|pBYj{ScU&jqn!4&6ww|BZUtSGW ze=kj&md`d;x4p|~s+W4*LveOx7&JH0KRLe3cu=KfCz%_VqA&Y-%ObHqr$Fk=cvb!t z?hqG|q&FNMbDcQn1dxFK+5AFT2$qNsPkT2*U}>-V`Qs<|GvA!flEQp_Gg(yIbwt92IhhF0Sld z2X2AU^Y%&XVm;#KfrLyG!tYubjTp@v=0cf7E-0AnOL!QcsDrYGW{9pU-k;*^dn5{5 z!r$f@LO6`l@>O`3dvryEv`Zv1DP-x!TV|$S$x*#e_bq_Oc(pSu_e}H)cDoRMzBOMy zV{Oo2RleDYa84E{dRyj^qI8#t(OcgxrNtMs>O^k;S%C6T5EX_#DV;GTe?oCgLRJOq zcC)}1exDclew&v_6ishu9!Y#8dlB7FNu$=`v(`1(K2X7#1BKXRs*f zqqj(?@@&<2x3Na9u|cmdhY$7LNm4_!z!=v+kPg+|3t;*j|CoFQ^G*Kxrx*u!-26s| zkULuuOamh$St4R(&-Kk#p22(*tF;9lPS**gwe+epk^9tRX73>oK<_@5600A_^n>g@ zbftovT>V!DovU5AN0zl6@=g;|>6k})YFu4ywzzV`M*OukHq%B{ms0pdjZbjKpe}Eu zsb0#OeebqY7xtpk-|zq{XIbBu24ffny$uoU%sf@-8ZA#^V7@4@tguc&ilr&}6LY;# zy|NEY?(dCuaF?HbE}T3aU36v^=e`pEcEMq|npg;GpzR>Z#Z41$h~y2WXH%5!=(h$t&iZrBdD zt4)pTNG}~z8;gb6LOB;bd;7$)Mjw62PUkp3=aCYCVrv{$XtfVs_9n1*_Ry0mmQq}Y zns$tA^WjHLQJBAf`X~|6E+hwoFkw$1McErQhG!E5iWV^{6eO+Y(3ro(gKsOAy}v3m zn3m1)X#DuFE9gG^E3#mX>}uPpSLJDuFhFlAb54FMZp2Y86QWMEl4#l)F3aS{I)AN_vAR^JeDqI(Qs+YXNEoO%GmUG6xmA?P~fleUM2)xf-c8)e_mm*vg#9jk5)ADPA-(9X+hbqNI zkf#EjsZw<&<9+f{J1-QY=6j0l>;bCL*jrfb&;$8%D^iv=h;SaiPt%m@iG;L?HY?Rg8d^ zX5*)0`)1o3@Q_D*OQ=us4*Gbx7^QP^ii(^Vu-hx5)&WQ>*n05vDLv9CMJ#x2a8K8z-N7O&e_aT661AA zkiv+=hdwNE^+E+xp2dBu&oYF?mxjP^&?*Or{+PoqsAXPi(#Gb*ATAK1XQ4x(q%$Y) zrYJ(5Rmcx_3#oHC6J`e5|2cWIUr1X=FWEraV~&E;QMx0CLJFRKs!dw^mF-~%3)3-A zXZp#%B;bZ*(Q8f7lKT-`U7}-m>Kbv>xQLfMx10%N`uvAoqw^kfSkV|I_-4@56_POd zjkV)R&g0SDSA3jtq+9vy(yG=!uh#lPjaP_I`CE2mRw$zuyDZ)G1511U(!_wy=7N~n zR|n^XStiv=Q9i7jppMN(JQvd#EGJB{%h zD{rn9Q>J%t^cpG6%xeU-^6{!Qpq7kQbJ&b*p`7(Em2Zx)79Z^oK1`YFhC-F5(-|i| zi+2+0HE!{RgE_9F6Au&ff_6~V-HEbryc`%@5Idh<@<>}CN-ls#beFK4#)LDS;cBhc zw^izNG1cKlBRq0KlQ8#Wz39qxM3}aDjo&G#>MtXs_oQBZ8wIlNi6%tdm%92a8K_9iwsyZ z-#;fUIzxk1p6eaY;49g(oK9;rm%;s#g@_ieay-rL?>D`=0_hsI>NSJ&2eCB`{x zM}F-5BHzw|dH;!H$t=hzVA_JRa!{-r}F`6v$M95FAYpp>>it8S|?!=1ND4>nl&Mj#bLR9RNy+JUhRf9%~m$T1Qkk4E)YDf&$E!N1+c>@Tw_7RCw5-B8o(}oujdFFe3ALi`^~5MI*v&P(e)o z*O#gi4Fwntt5e<$VQ*|d7I3vq|A6%1uDlsLD0kc&>so%-iY_nlvjRDTsaz*jCX6u6 zwha|%Fo(GVZP@y7<7gQMuTPI9#@PI0Xua1F``o}x5;>~m*m3zM8ltO8CWDv5kN?24 zb)xrGL_LFSH)T>emFj&2jGihkG$})SS5lKXIOj5OdqTfCD#~2Q6})iC5#rh1uC-}l zgFx??w)K146ySF_K#gh_RAV^XF(vKEW9>KJL;_rkTk z=sYYlkh;+)<#^LrYYL~9_tb@x6uG;@TDj1YO=Hz60j}q-MyUnB_&=r2{{rJ~;7Z87 zc(yUkp5ZW>2sNi=4C%!<>Yd^Yui$Z8%Ux!U)yUen`4w4Q=!`}RXqDDjm+eT_^?LnM zxzU5E3+BZBsWUhtX`DpY(*T+&8!8Yj1IF+wGH>t<4oQuDzndK-3jk3#D>n;YEV7+Q z4BnJUZCR;MgMprnQ1b6scHxW(D6MY(6=VY5I^xx*KTym{(It2!?_A*f6IYWFHLj(5 z5KZ1W8A)7c?oo8F=e@*3^I8?|0$**z_usN=BMrVOrOU-wQ;8elIE5<&x<;i{*Jpf5 z#S~w?$w>WSQ73XM1v?LmL8{tW9G*kLPe}P1R<}m!HodE8jADXF`CiE$=);N_rCW>e zgu^8y!i7!qQomdAk(uwCv%5{DU!Kg2jVho?UVS54g)_ciz7$z+VOSODN$p=ShPsDk z9G@3HJ!DwT#Apobk=dWlA>!D7AN{kE?PMlsr@Wdsk;J_8{OZ?fAgO%#o^PS5#b!@Md7wyn17KD;p!V*EU6}HbRj{ zZ5abJ2xQS033k5)pS~4mwQyp>jCJu>{0J07K+h@Vn+DN_9O{XG+9PSn<6vgM4zkbT z7hB`}jx?9LTmHnH&YWG#ivCTkORh887?s1+mV4W$@70U-KYi;=ms>iR1Cu{zD{|^y zUe;dBA2Ip;-HvGlk?C9|HK{v|Z z4$!1V8p})Jt3BK2$J=M5Fhd`@lKbWeZNjSas~>2;a{Rbs;!HKK2@2-ldmtq<`fIzA>=KhF3YLvp$L#|ncP6aqX?uwU8^m)Gn~-9 zPO5(`Gsbo5#&duT4fh zwg^Ux=Ql%?(?9y!M@D1Wq1CHUXtmPQGF6m{($-#8EX|wIaA5^8xbgibMnN*sx>cUR zLM4f>r_o~5XS`Y!DHW-eXf$5R^iTDSG!3;(GBQ9`0ugN!;xn2ig+pr*>YZt^4}N&t3V$8@xJp{ zyt2+;)5^|f>f31vp5Q|y-Ez;2`L>;pyoZRc&(7qgOcYNtiApG5+zg3 zUhB0967TRSYVk|dEAu|9lXB6)p4g)0T_EaP!bu9=?FI)#P)fQk_$3HEe_7wb)5b6E zYeV>`K=zH&-^ZsNMB4WJR1PCVS){b(|sg)bxT+`T{6? znDY{up6F5=bw|{Ol5!|p)wOBYC3oN+Dos%FU;FcKiodjPp$L>+0i~gtCSs) z2EwQZhP{q7N4$xPrX}4SyhkYT?sNQm1Wy`jjIhjNk{GLe^m!d|TxX1cxuQ!%m!(ulCMoJffEkGrKt9;Xu^x9*VG04 zL;jtF_3ALS8}|vy-lukFb}maFePUfY5OfKmK|9?mKKg|u{KCWgpY#c6nRHrbFV;Li zJOQqw+EoFjI6Hac3t~>R(@b5n4qD%hmh;1Gn=a%kn$y(IX$@lRjipdwkn8_*Yc zdnpnl^{X$?n_*DVjRV@aiZeYXh~JM4$YA>^S3>CMoi?eOG;O!+N4M=>hUY>E95C7| z{ykt^PPaacdB-|k2aIs|(dnRDm7pZFh^GPkgzK;;RU`K;dsPG3Tmijc7Em1V{%PjI z^|n|jO$>Cv+LUKdQv@7$P4v>FKZyX8=2raX%vJv6??e_zXW@Y8v0gAt3m>_7jEnb;y+3#oMj7{w#_Pds4n5rs{CW{+(Vz?cGKj% zY(1YtN$7d%1eTfkCBdSH;eN>i1G)uuK{%|ix7$C5XJJ;(|9=LP)gA6<>l1S> zEL$61MmvZmoPw6S8&XaT%uzS}$zF_1gJ}sS(S(7}`h@J*`b~X?(-0dj-!UJ-?gF|z zXfEJAjk2%J>sVJYf0z4D0F>ac08VX(y2hou`!2GMO8y%Ws=Tw^@&K-;x>j6^`VZi@ z=32obmHJn$){##SZzdtVl$8qTip=T|X~fe>V47Qjjl-yya^`}i^pPGvk(Z2YpH3ehq+PN3HbE!{|ayDXjIVjb-7 z`Riohv+-URHTv~AwsqcU;ONaL$hE7vRWu>>M?j2IEN#!x@?+RamWk93+GORG{LJV9 z*q8^-D7k3r$@Q`)Dn);TCW%44UydenhQ}FCyw7Dr@6^WI)YW&tSm(1Whq5h)C_aXg z9h(y~hmA5Co~Qv`cJ7Zg947~d5+#3q+E(AE#>e*<=7oXPF9<_gU%6TFCq7zaVV-gA zrQ}#usFcDFM-Ol# z{2N|%#B=zJab@9!cYlXm4^y7n9Y-0$(lnq??I~v3D+8t`Q|W*1CXUuFa|6{6n_F2k zufM&GlwxNa!dGkB*DUy>BiBNr2>?HJ&o^NcHTJG5BmGBet5yy!L^8yQR#vEtyK&pr z`&>OeDiyGQan$qwLDYXZ>Nw}{_HJJ~-wP(-27zq0-!}*(+o%jJU)(19vtnc0EF{@I z!=|91zqbG4QV_ zC>9d@FQKQqT$h&a?Aq`5+^#N?#`trlNr#YQhm#KVRx_zCmrl~{sKwg$I=4IS=f?Pgy?r<16E^r> zJU{={P}Rfg)+sM{-yivhXE&w0q{TYQZXTCy9Jhr>d3)CvowkiEw~VPb3-J+63*YY7 zp6o&%)6BV_Oy~5k`gt6*{9s%W)c@dlc7R%}FKpDdHC29ESy3}#uxqwndU;ZJYif7| z{Ed*unXTi}2S$%8UCT2tZd3LbQa$B5o zanXrB`<{o^qv6~r`m|bF!R`8zjBN{8-|^K=DR&qd>2j&3d*;mb-pRx2)G9W_%@tE> zrxEGI#_rd)FQ6W4vbQw3DC=>4Civ zNMps;!62PD4AM{~V#t)u4+DdH6F)@sH|B9P%zba!d|2joLoE)1z9xpcJM1fFgi|TU zXfTtqlWMw7=y&KFPf!iL8){w#(L6(;gS=wGd8w45{1%VqwsAo@x>z}-^ok-0vGwc< zOPsNF_hDnfz@sbB^H2yvIA-Rs`jdp0c{67H{yB%}fy?1ircFJwM?9c6WssJtt;>3r z0Vp=8S8%wz0411zcf0aUJ*<1BP56w6vkKmOmpW%;e+*T}H6dBYGt}I#5uQqBX)h$N zu#07P)SAI0TA9KmetUx|As@}rGn7WcB&)@Xf}==y=e2|N>L;jBGm~N}zj{;W6Ct~3 zqMQ97vib*+(Re83$2SF+Cz(JR~nPmi#>nyeIWm?XdgvaJLg1 z(F-)UVg?T?FojkP{%kh)JNt0Ur_!wwnCzf?i5R~>n@TSbA{_oXI@u2sF+TPB{r))6 zJ%wfxwGA=J%TIq;bw6nN5*w^5XK*+hDD83nUaEOW%THmL%N)n$ zNeGxdsbZ($)+wl=(rW-!Dfu-HW1}%k#Zut>8n4%8SZ^Z@xbrapmWr*buGeN4^oXK) zd~c#hmSi<Qy}KFEcgm}JYgs1l;&obUTa?(I|v<`+MLcmq+=|Uxeymi>Cx)Y7W?w4Jqqi7 z5P1`~|1t>gRgrH`=G$|%_Sykebgkyy^gTQld``1@2L~h3PB9~>ijC(oTyaQUd_;Xx zT^y;t2Njtw>Q9k(r2`ABGsG3Z50qRBbzxDn{Nw{m9tv9+Na|0#*}Hv49r*NZ3=0@r1!xJr+A9^txZnrx)M z*tta_+*&(Bz*|#fXT5pLXsj<*vZ?h`PRX{wzIG=CI& zx?j~KfifV3c=rbLHQTrbdI|iWk0I9o^(AKPSbFAWbbWb*JygTfGWz3ynTzT5zNGb& zW|9Y|Pp0|Sc0MJd?vT_t%=LXDLfs(}K{U9c?+^;nrqvilRX?6)wu=`wwvDBTyyKLn za1V}wWb1&wO8nUlot!M_PoKT&Axv)+#tgVov({_Iye{<{13Sf0+zQaw2frniJ5Fek z?2vx6eEE|r^4xUZp`!|a`USVyunbc5dc^~OQcWCWmDZ!M&TvMWxra}ggKqWC(Y3bD z?-|hgv0P2w?nEwRi(c+ajkfYA`;p-z>PIW6*{Ho(J(v=h5|1Ui_a$>iPu0+$uPOnu zb<-%zp^L#OS(9wnAOG1B%yEs|`|Ke0F%3yP_OV+Jf5;%_f=Qf7)5-fq;5uy7CWfEMu3<2|h~&%i2e zw_5~9`t#Rbv;>kz%C>G|toA0-E(7ahUnW%jEDt02Yv`!#vCqsQ=CBEr#Lb;gYZS;A zDWBu9%pp|c8si$}80=%n`+qiuA$Dx%=7?BGm$?3P(wk$}m4|xKPg-X1xFZZf1?5=1 z_2~B>_(n4s%wKQ0!@ejRDeOwWpAv_ufb^@13$Oi2`q~yG2L4)W7~_~XC0;LoeUr_Bgv!r$hHq8SdSicdvpo1<%a_mFZ@mI;mK_3s`Sp6dm zH*Gs4?;n~Q^6q83`+J}6|JOdnbmPB6d9_Z+9H%oV6a%S)aqwBqY)=L&TQcKV1y0;K z&Y+!girx{Z?Fl?Vb|TqoPvnlr$WF1Z+4Q}y94xS(Z(X!>5^aMz1p z>eH^%^m5kojSB&$(Mq8X)%VnbM%Ol}`Q?UVYLRZ@1Hhw#6iW;S-4KbvEwDyYTWVPi z3DN%H%6bT6om5QUi)EI$FgaK(e>2Tk@L%ReNesI43)5R4dmT9bsvd(K^p8k)6E`ir z|55#bcemNC+Od0|Zzf%?^AGYc#0O7%vpDW`77{_xvF)kg)HN6!ZlfKmIs!FLBtUW- zr>a(ZD3{k~JH}R>3@#PV?t(AOe&U`ieF;emcVhWMzA`KL(dB7}pjbK0t?D^pdu`OJ z@c|y|1{P5(*3~zf0oBIY;!f2`H8XB2NkfKZ95^nHZ(Y`fAPl~4^$>UVqeA2DbV`Wx zwj8AT^9RqP+$oz^xo3XTH<7mWF#!OO{}`h2-9OT(qXG*0E6qJ~$h}`cuEqj@{D%oM z0F7}sZY>8BP(r&Q1WyT;_aqq2@w(NFj2nz`SjJJb)y-to16_cMUJ%FG%sJL=Gp;(t~M5I(S*!}?eQx%q*e;m zD`OmIXyS!iC*TD>crNtjuPL`5UL0f|~-9Bt)njNd= z8@Hnz_d3(KoNz3%!OdXu=p#4xg)U)(zzuzll9r4$sS4X{(T!3?%Ex1OuD49dB?U*e zm`a}Cad=*G7CJhkGf&dOkTn)%d5`B)>K$j)I-KOYIVK0u{sXBy0*!5SGBIkmwXGt(4F`Wu&W= zhL|X!9t-W|7A*jb3;^;%w4 zT-l07%~A37AUuh9iJ@J7Mn*U=TX>?$mWO?zQVOmw{AzgtcQMLL_>Wf9KJ{k(wygM1 zfVm0$<0*_$*kd35av+8e0W3cHqiTj*{`J6)VcK;yr^&L@8GZuqb@oNnwenH|{^Z(~ zlZOI3a!YuC_bmO{u9?3xIHNgvzS$-`9?4%xO6a0ULaqIZM8sn7b8eUq-n?teOcnPaev7#hmdI0Su*qFWfUGutQOe*cdrd~VUaB8Lh@hT={o z@ExiD>q`c>`);be6wG;|8wh&4M5f1PgL+1z>@D~V$4cij9XKX~jUHM4FnnV=__nt; zUwT81FT`WVGOgm)<@>YAP-VpC=pbx;8|6lzh!BwbEE|g_tCiYN>)z*&Hinp#+H)*# zMQ{fDiny!A1W}?il{u$M>>)uhM9=LDay4PzqCn4Gt3;$ zcf)nLSDPS}wRbb{{upG8ww>fXfK+24fI$}hRXyM*(0(xv0$|$ezpC%QX;ezikVv#g z-t?ZWBiUj(u&0IO;o=}Sawiir#@-ulgwTjquIhUorX{BFnAA1ECY`s!E-&am^6#Q# zd>Xo=Se%DI`>5PpFmO5dfD=tBX^rmaGX3`9(X7uTOH|)IGZWjGs?h}17Pk26l?}Jh zg~ZUR8ALMCzI>*J)lR)(uslrN_YcLBnnQYj*`LW10Ep;+fXF+pkiHb@r?MS?_B}H6rC3u$|YYu3sgyEf`lrQmj6iRWf$u z5^H%Q@Gx;&2h5Q){nOw+^?gK9vr*32vC7?i&G)aSeLYDu(RSbN`pM5#sYADH zEOiKt@>K&bnI;PcV-w97!`nYb28g8pTQ*ccHjMws7Ws=hvq%6wW&EQ)$kyz2-sjBC zzv;i#uA#dhBO(>IdT4kjVe?3L0alKvHEh+KW6Y|T0nVEnzcy1l#d$9#YI~6e16Ra9 ztIxK_83&ZoT1%ahF`?X{GRcjRrJfH+FzkR z^dY1>6zT5n?#@GZ9YVUh^U%#V`1pI{TkBo(4>Pb>_w2a#-q+su+#*l)evYWJ1`|f= z{6`%Pb#}{?(cS_ZWnR>~i*3|qEXbvZVGHcAnb%2In@8c`e5?io{}#%~FomBhdU(&m zShT3wIzsgQO;X}%`{1ot0C`kikyR5}3GB!7p2n6xJ?X`x(Or90=ZqMC+HJDee`8j# zawJjEoz@<#muuk9eQ>3@g>t^?=$-{^$Ej z=W5z&+V7~DjP_WHp#FYTb9?MWIWi06q+f%^gc9G#B?v%*ZzC~i-JI!r&Wj-T{gHKD zUk@(6-<&-MhFZ_&fAHD#{-)4BS-{gs_y9WKDuU48Y{MDqMG|+&-GAZ~GknI6_c23Y ze9B-W*uNs0IUnJg@N{db8PFN3Uj3#dME^0G4)u`c_Hgj#S`KkV<)N^C+w)*ItDzoV)> z*yn!pZ%}Uo~Ia51Fi7Odd9~GOazT_~|F|&D-t=P+Vv68-6xr)~CopRXtO4n6vv_ z)4IE z6&YvU^000U{nhENXVu?1qf>7aJu8UIsJDM-p;wMPszr9AQ zzJ1|!ee^65u@%7nzt=pcj?MF@2=;Wvar+vq&L2l_oeG+?52t|K*jJJ}*J~FyDYD5t z^94?|-BJ@@=FnW;K9p0$V&DVALOF8#3GxIWKKQYXCrRJQIdv*^gd_K+D{kzqQ0 zaG}SNSYpZ;OTibZ$%5}L&!#1s9)=xBOrA@YhG8_O(e-tiA+#Y5_>z_I)K`Uq-fU_* zM(zq9dY$*d({?Xovbt4V>}JB&Ymyh05U8`CC)Z^9Y~E{4^g0xWNHHK=*bz=>VKrwz z7izms=R}JiHL>dR_wTbZ8I4Edlv7)&Px_Un_sE1;_IcJMaG#vL@FYaW z4*XGqd;g@p-KMGFU=H_16HIqaSp-eVGKu(y$q8d%UvCh@CD@)YZ9t4QU16Y^H-&J#@}; z!bwBRbfR+ai*#oRoHB20?4(oUJvPMfx~osp-mctXLPdtY0XyMwj6LDFx+!;ZuJ&k- zG{b`5RcFd?R2wW~##3<)>1lDj8QZ!%urf_Y*vNYBTxnCUZD=d$;}I``aX0f#u`3nY zyimx+!@*e85khAN_LC_-@!@;{wwuKZ;o^X>S2*tDrHVgRr0Ge2b+j;c|0Zo~sG+WB zZLO!SVQ4G;&D~h;hshUj%W9Qm5id}rL?Nklm`b(NW1eoHg2@+w0EcLEO0h))g@b53 zB2~R9T~-pE@v=(NjuVZ9Am7(g_?k8(ddxh#t)NP7kT_hI{u>iw?{9Dm1!H&p*FewC zlrb*T{ua;PeOrVvySoKup>MC#N6ODtUBoX$#Pp$w(_{K--;`Q28QxNz z7atc~ zURnT8ECjvmZiM+!T}G7`b(mZN_29Vp>Ne%Y&_##QWLCH3=NaA0*+ncI$D%&T^Eo)@ zHPn0bgMryb{h-aXkUnX1-5==_G0(V(6NZH^i~>rID#p~#wW((n9@*}$HF5|Agw;G`QqvybPa)A^>6EisZ!Z(S~yC;$mFe!-GZ z_0Q(Angr{}U~;!qL_}uAH^r6{_Se`lyc;y9kNfq5+qFy18*tKX3ffMS7Q8PgK+bBG zce5Flg^*$ox8q&CxS_xS@+d^whN)ZSr0KdfS>FYWB|S^shs38$_p0@B344Ot;$7R*OY=z{EWPQm+-TUJ=cV?dgrr70N! zSWpl9YaIVPIW;mT^`yQqJ(dUMJJwN0_YGcUU8f%zeZco>CE*1}c10U0ErM6mltrlH zY4l(jES04A^JJJT5k#z{v&6Gm<89w+U7qZmsu*OA%7 zZj;M3yBcQBvCy2gopNnnwolN4v%ZiZ2}C+8bF#y3&;?huzAy*(fS5GBoLctt`%Oc= zBN`nnRRD&?kV48netGyj7~t_;xf_(^x-6Kd@BQItY;Mc*1>&hwOEzFt+v2f{$}NFYj+C$nkJ%XP+O>C5j`numrA7yB$%qy&aqf?) zgiiWj_i)VEHr@JomY<7C`q(+N)H6d(ZSqcr{Qde*iboppQ^Vws_)POmi^s3vIjCn! zNa;$MvgYuLqOqs83qbAJ^9h&vRr?e8M>PzWYZR}r=4$sskw?J1W8XV$;$0(&iILUW zNvzWhzE9Wn88GCCi5pI>fAfRJ-4#*ke>^G>R-w>d_%g;RDM$6LD=J6PNo}^EKKGnBP{{6`xj<+5B zQxKNxX;9pweOZ0fUv7+WTI2oTJU}G9e5wpDfb>jfGbFIPwAsTzOtRZ=6zr0%pBPz_ zjAD*@w>jN#9S6>o*(iZS<1B57jR?bc|6vqe>-?wM<*_Y!OU2?dZlc!K%Jkbr*uo?g zEFxlT)o?f0zuU%irJM)c=13uUez;PPDV7P=XIC0u7igq4^m-v3IF^?lKix!#$Gp3c z?md`~|IJ*k8CwTf z2D5%UwXsdv0TPsP*>>iAaG9fj9}u<(FY?wXCY({?RnK4I>rwn=Ae!6y2BEc*>;J7Uo$FqyzK)o>$WNkN(>Gz3^m(8$@5~Q|#5)n<6k|h^iAr)>$CSTYEF+deZEc_Xiomy3Yz5*{b(w?AH{~ z_2H9#S%7PO@Yn^z2=dF6kzGc{D>(8#Q)WQFdIa^UsMB&ZE@(~tmKf~{oV7dePS@n1 zN2%UJ83BIa!BJ=TkxVmyEBbtk`NJnzt+BD}t3s_$lApVcZ_W6XmIPCnn~T*u!7q{( zwhjp-Ppa2lJDvv)(k$h@>9|Yut!;soxZS4ZJNH+wzEE@i-WDd7+69wx-E;_zy~cS> zFs+2y=y91lta4a(P;{*6Q82jFfbW86L+J@+LwjOffTjncS3C$Hl72n42=)L7VX_%8 z3DkW3(8!T>JeXj_Pn5Q|plS>KVf!>?VV6q)swHwi*$ZorgJ@`$r)`yPUdFudEok}`h$I5$T}d&s z_8-bZt@;dw76Xyyc%9yO#SZL=two5a?nnTVL$oZ-{uxA^7O;H11<$NEo4r3#?4Tk8 zS^cAdkE}1nfcwdPVmCJkwPS;6kv7?(oiW(uT{ACpOYvcs3>dtHglV+^o5_^JIA3Ef=)jf^4 ze&)B^Tpk_PXjyxAxtu1OmLk#?G%dRNc7d$3=bRr8k2RQZQ+HGtGP-l|La=}AbKz;a z(f+QgW6h2EI<}hTDPNGcz(?MeZ8BZg&rKJ~H)4{Ay`{!ou8DKd-IHr5ycaBDi1>a# z0iI*;@I}Z^I$|MF8Ie-=%lSMPDv2tXJ@UmY}9AY=CVq49JfsiTwvlCkI6|E8CM2RIYP?WN$`x0hNFgR9;0rPU~nS84~pf&C1YYRS~k)9hg zwh#UpH0-*z@Y+L3rXCm-$U5#T<=y=Nno0)FD%LmN*$|rQ{bN%#a>oJ|c>5}7;r5oU zte(jMXrBFr6$|7iSSe}SB>8J!Knn`6uVHyH>CON|q83psVB+ZP_7kqlMH9?Ipg(um z<6b|xhY>ZOQgfb+Z|uv(uqB`K&RN5Rk;9P+nU)=4NicM1QK<~KRJ73+7U}%fkSh+~ zVrEV^HV)|?X{TJ89`0<=IFbaj#z=6_SbF2Uc(F#WJ4pb};J; z`o?@1hKrq@803ad`~_(x0jTF(@c=7)Z3I3=H1xbU4dM0nhPwr_F)g4#{XEaLk;&yv z6$YKo?495(?+qW?%awHR+PPWD5Av#q9H!f|y;7!D%mhd@49M@~4(2fA6Eu8+y#i-H zXe9LCK|84tNQH^L4=yC#>xq=2@c2!8skh5)%!`h*5$eS#1iQY56cgAd{gUe#cPz-8 z5ACz8MWz1mNnDHxBla4?+E$BZoFpn*Ziyo&0rv|i6hKLJVkNN7z>6?O{3Hf{>EYH>Oj_177wEU&oYt+QZ_IjAuT&pH%)-v6DN_OE)rqF7nOJ zq(6u-jk`LlM_^b?&)S!OOfAQY8i9=SQ{SIOlf3UZf9R+EfV9}0r;&giu&B3CZMTcF zhET1WEsns_llXbE^QgU8-(8+&_X?(U?MLUeg{yI(xg#;)kwVjt2y)`1{6*5X*gj9K zV}tfoOC@kVr85W55(EE+r9t4Cj>T({B*MoMXj|dU^d@EVLD_Afl_3;pOYS1lPA1LR zYVhu;yPl^mt$UxV#a;St#$Icy{X#Fj=8#)`l?dzer-J=*k#*rmQz-l7W)U$eDwZa* zQKuE(qRlCWay!-C!K192vZLb#`W6yj2PNjSv#?0D;(e;mV`FgO9r)c^tI^aQ_jqIx zOBDj$AX2dgN1at*z0#7LDq8Zk!UPhzX>g}skGQn{R2<5MI`-@QS+GWE9hMA63_Q~{ zy>y!px;Xljan|QNp9NhGWAT_jztgXy`2;D3pnd-4D295E6&X4RUFFe<jr zG<)YGgn~1?2k%qGj=*GwXk&o_IxD$Gfo5ap?}3V`oPp+r{H}ClAZg)A)10bCtKQr6 z?#eC)zyD>$yPC^~uX@p)+rITKd~Df}>wQemcU;ttVVbaY_YH%GHD|tP@}I(_^I4zV zfIOxN$YTTuAbtMdZ_3a@pNz)ic#2j6<=_pJb4Hv-EcUv!RlLV{Zt(Z)v%Lr4y*c)Ul>_25M%CHliG6iN5_D z)ASc%axQ@2B}`gijvIh!HqOKsO<`v5v&CmOLuO^p?NTI4?e?VBgX8W`xVCXlt7Q(CQV?&_{2rYm%5)dhhHx9Ix3@~Ci>ib>> z&HpGh%|zp4;hQKC6s1M@&Ax89)u~V6(U~_$G;0U#6q}dpSk90JQMB~WRj8aQyM8fA z?kOv`a}d?lQuR$PsQ&!wIz73@C-d+efqFtK)2D$_BYsf$qx=2|g_7KsGse^WyDg4IC6m&< z&}=fgI@nO6^BPbAaU+;9kUW_}17HDd^V0uj1$I!_ao}yZuYH;}jFWwBPEkXdug?#c zT<(tZod29(R~k)D+X@*dxrp^KNF6o$qnCzi$&+b$sCRuazD5~VP0{hUG~6W zF%_4$1Sfp#J61SLNi44UqV~b7VYS40eL1z5CILKGBc`wk2qgt~l|S^Y;9WmZmesz{ zqNMcRx=?c*hpEOi?Sf#`2>r#5KF`zw{}I2~iSd`OqYBg|1j@G&>H0#F=$>MQv}9eJ z>!tIs`VmcwH3eWh*OJCl=SA75K|8f`!_C#t9J$mb;p3=j0*8MW75YfUz+T{3sb5hdo zdfY?l?3v9?3UN_t1RN=+1GqjYL?vhU*yeYKezv}uq zOyudh=YhQZb`@sU^`_FH%C5GCm6b_Zro}~h*pw0T^_*OlgW9cMCQa?e@f}R`rnX~# zP(vJnB86t4aJJE7ylkw7)$@va7^UlLfg$>zq(m0ImqM>P}_YGr8r)nl?f3|WA3i6WoxVDCQR(|l_VZ>eYxJ`Qyx?)CkConA1qtRB z%M@6zYB_t6=v;Vr^_WR2J5B`N!bwD##O7#eN|~t0{ctRCgAAsPve6IaM$pJtt2}XE z@pVg$tRHCx^1X-0?P&?`qOm!?7@e@_VWND8g>{(nidoxBITuN(XvO`cV|VjBRG3*d zoQo$Ck1=&OjLk8!3eUbyuSrI>;ERSko3@2h(~6T)ai)X#kd>Z)Yu1BdHuwAKp1j^R zv0A3vP;L}iPz1qTa}*>Ll}*01)W=UlXxOwS)D^>J4AeC;3kO@9d3kE798Z2rR0pcC zt-Pf}phg-991Q6@&dWqFfHy}GYMUoprQ`uoK`{a50mZQ3lnD=kx=43GtQjl&`S+Ve zB^?f+Hhoi$8<~nlo@F9l(P=o6Q8Z)n*I(6TAR&LAP5X)b-qx)BNWp* zB1|nvv5vki=GMg1nXvgm$&Rxr4VMUJesb2Tzs|Y<%*Mb21VpU;Q>9Rw8mY{UT0jX5 zi83}mTsa)IIP0wiAhrp0eGPLIGN48;%>-fk$2zr`u5PH!>Eg)Jq^ETv9tudg@0Oj? z5~UU9>WuV0Q_c{@(G&@Ap`Go9;Gx7Dr`mVrh!}WpS-<50OZhk#DIRRd8S%`UYraR1F`6PK=cuFJkH!|6 z4`4)&bEV~DDDD`_LA)pr@Axo_i4rLr)-l)_QrwI+8XrIUO<|@M$LD=8S(YF4ZXiM9 z!)$`a-9V=y0<|!yH}S>6w@3qF5u+RB&ov4D;iY7l-lZRA!%7>>(B;hTC?7bPFMcS!h2N)+HZ3~U=3p1k6{m7$I`y1T*(he5#-flYjWq0mPEE@m9 zjDT*=R|O@O89Opcd(fP%RV0^rvGcN%$v=+i*FLtY{1yPj@Z0Vi1ojC@@Tr92wPcvn zewRgnyV~swG_oNjd7R-Inq_EVS=AukBJ#4S6-iJjGu$vy#52 zW^e*9CWN%<0r&sAJ;w)F!)_iv39TA1XNfjy-Kp}z#3d(* z2K;_P9itrcZ~gl$?aoSeE92^L0b~Tn;~kAmTBZ;N7Im&7>bg!OXQR2s6&6H%vAYJWR_yi8J!&UHyA12c3R10;~SY3=PSAt zB~&$<^eRrp8-1ZJ{i|s1>W|vzs?@Fqi>S>RsK@au^@?a&db5^Ym+BP&m&mpcC=6^B zY0B#>S>*gH(7I#ppiNzDo+~sTgJFU}%b*=AwO>kHOf*zV@*+Pe(Ww5p4#mwE+nj>< zf+i&WpXAYe&T9__8}8N-K*?q_9oIF0C}_XO;b1Hgd{kYvC%fclX{UcOk(?u03w)@v z2R%~m38nwmZsX;MJJ@EOnNL8?$nbcwv&EN*04bKS_}I4sc_&@Z^oRoxg#R!p6@+#m_Sm^iXZm8hhXmGr{Hat@PS>!CbrnKd8Rzp+6{2EM*Mso>-@J2$tCtoE ze%g-`cJs}}Y~_|bMVL+3vgU&i^uQW~TF!Oc#^+EQ?^}ODkLH)&&AbCBUk>5BTN$Yp zsg>79U=;4EospcFk(d$Sb&ySvosLp24^0p)5ZxaSFVZg3P!DgGOF~*hT08KS1fq{S zxcM6^S<+aRd*Q#u@~>uczguu$a3@*{*@lr{t+UfK@hS3Y%G_hvYAX&Jt+v43n_nGu zEx|pwk2v@=3!WttF8Vct&lWOMSMMRCGQZuK&oDAvED?nG*&5u=XqG9TH8EdNJKp}h zp}HeWGg^WA9*>n-(z+8~SaisAfB4T??a9PSR3N#4ta&OhbbX!}yu{`i?ct=s7RK6?Lo3vj#WYqwVfd%}mh4v9xXFHv6dCxs!+ep2s88 zK70SY^LbY0&-1a37Oifk;=3-3%>bg(yEfh3grm<~x&C`H)*?=`i7m_EhvTK{p2nLg zg7cm8gZi~hPwDjC64YOFhi)8=)D0GMi#^)zyIn?%pzA??ciXtttVZV(intV`mlHx~ zX({j{aDxW8OgM1^%sM3!1O&ku0oa(|(%U^sos#1ICvV0)rE1-Vl&R-^&H~XWr^tMK3(t}3!5ynsGbtni9frK1a zaky^qxG!)_xerWgpYx6x^Ed4pCDvonx~p-T^KTpR-VW37hqR}4Vm=6~ z1_jOV1v&-aDD6H5n;Z10Osh_QMpQ%O9&iTJZxkaM6nih;Y++paw+_R(;~iIeE_Klz z77HehxMjKsbGSN^HmKM}Za0tI24wDq6i*yiH%3l*xEw@vRNXVu;!-5mEAh|Cumr2P z4Nq#{9@s5?4qZCNlG)>`*sQw^>scmxB$X|FJgbqpYvA~RR`7!R(49ZaZY=%v;$p+m zsLEJCr5U43&BxQOTzikLD2@2z8|}QYXK8T%OBL0ufY_(>JnNtRFHL|=S~YY^x}MoQ z|EGx9ra3LQD%Oo`O2ptocf)m219T^WY*E~qceB>RE?*DuQJ1dMJ*EzbZqJ-A7jELW zJbRYuQz6JpXB)wk8r{ks&{0llEw=yz9X!UzKKqLbZIz-cr|9r}BM+Me6^@`fl={c5 zP`Aa@5|KzWDPGpCV>3Y(WGNQbo`jC$yY@=NVS1iQKPQ7eO-J$MP~7S&2;JO3LL=h! z-m+WF{rSU!z6Zgg-u2x_y45zfUZDET&E(F#orQLwMbW-1ww$)mk>U4R1 zw)jx)F_lbY!Si9I{Nravk+ST`CNrC z%);8YSWlEo(0NzUt?8&Pw>2=+h37!Or1iVyheXOT&SKHm^Vk`k7y`qK_qY|_Zz|;; ze;;Gup2i2>_L~)raU!+|ejQS8u4bsJ)tA2`Q#~ra2mNUGx46~z=suD=cENDX-6}?V zEF>`PJff0tKcH2C{o;H!@_U!WSyl$l7;dxT1%Nz(&jw$8%wx!Sea`C1uERk43|Re} zR*h%B{{5MaDRi>nN`;7iLenk4DJ2n>>wuG@3+JKAT}N{bIw;Rr(;NH<$H$({de#@c1f=}J=K0HiLdIu`jrObu z{F1@b^S@Qb2%a-J*s_^+A4Sp61}!842 zbk1i;$5#fk9cEZEHaxV$gJyO0-|VSg;LS*#5(sryl$Moma{?Pr83-*8X8kRf@GK{U z)*Ps=I6jUfugYw+C))QdsyCuNF0h^9!|+iVjXSk#(AicU`qR-oCe!||+jHaDW(5i$ zU&O&zI{%4RhZA@exc^?|{ePCOik*l1D2i*^{iwrXGs9`o9m!#mw5VyEGSRsz=9Bv# z5VSkm*q&=Wxc)-mA;FT7XJXMgY`@gukn$GMax(1(5#P+jH@>arlVJ6zo&__@V^OxN zcX0KHnqwiu3f-xp<~p4j{h9b+Ew*ztgD_aGi9B{0)Z-9xgx{j@^|A<4ZH@lqBFJXm zFSY)}7c5KqfcA=Yb@=Unf-a+L)ldeE8?cCHOjAP7;|5HhgwBNXY_s9>v~L28lCuu? z(e_ZyyxYS)upYN14l?r6^-_!V6lKwuk|5XEK@3ZZu zzH?_5i2kueG_~&$ETKo z_Gn9N(V4pbmGpt`s~s9|UheY&AbRpXEhrB0f296=vx4wHssHDtt<0PoK8dgh&8;fj zrPCHEJjc@#aleS@F$m%?JI~bdp;DvN&p3TWhvZOd`v|- zxL<7Ji7GiDN4$S>lx)?gdh#nEp0oeIAGu<#PRRmxOFJyaqD5b}&%jCx68w>i2W{?t z(+GYJZCHwdY)>O@5$k(Q2u0p|bAV+Xz^YqJ%)jjhWt;TbBXM`Bb1$(PI0`Dmst1Rd zYnMlZSMMT1s@=hh1)%~Q)9?Hbi>W_6YQZ_5&;1Io<$8g08l(Q>0uLs(VbY~`J(Wiqno-@c;eDXlat>;2bbzecf(E3Dz~s+u!aigb+mYKdp%l zFiJZ&N6Oj#n>I}Lb{k&YY z3oLeQHzUnEK;{+KA^x}YGVL6JmRK!BZ7oM-Rke#P_)dGr!t3;R`5KO6hq-&@wL^NO zizz?xV7QFDY5{n#S+)^Be(*m#IC*X}3Iq_qnl%gl-;Z!T6JQ9XJQO0p=9}opQrs4U zN9#lwi+vt0|H^L;6C}q~C?saVJ7+6h%9!TiR>t^kvkiZOyCtt*PRjY=Z1w4#4BLK* z_eJ|D6bL>4xhI;=a9q0c8!36f?Ck*;l^kep@6OmN$}aZS;w`9Vh^p;9l9GNx?~$ga z`)3Ws9{0El(6-x=!u!J}375U?hUYSkyx-dq*jA19W9IypDpJS&Qk&m$tYQ=ZfP?)X zP zIx0=D{g%kuT<6qFV7b_pIzDmW%YI}Jp9Y0YHXvGpS}Q~1Yyu&LRQIk@H5=22wRt!< z00w=$0}L_j-W%DT%NLE1lx2zk^vP{5fIw9*as3uIaF@>2@^|I&J=6jFf!;T8O#RAY zYb#FC@?a+92yH3#d{G`Dytd&kycTfzbSa47q4s=GjgcgmyT`;1J`5bHHtIJ_$E65OhDWL=|Ah!cYHi)`qYl`b;ABh7aF4B@jS4`H!&Qd6Ao~5~iL%Z66J_w56J?i? zohI)ZqdUj0qJwz0)kk~Ijw(QH3R>R6cWS=>mi&fks^T9w{KJbM!k+apubv{uX2bYD zU}IU20%?$2bbDCJWa4^^#Rjsi^&R3l@L7@GWRTHWxE=%WAhqm z7EMNo_3do9TBdW8xfF7tJSxLIn8Sk`&sUOrK|~8b8_)a9dqMYF+Y0s4^PdkTFDCVZ zuM(T9DxPwsJhaEAwRKbD&Q!SnV%{JAz0G;a}GQ0<2;j5#~4>A zw{!)PJqyxU5xqJQ&kAclfj$12ETXe3H{7h3x&S`uZe|pTcO6MNYxZX;25ND}-a%+z ze`x-ONl!AWSxo;6?tR0X#({{gx0l4c5Kg*G*}RJ6gL7&r|H4f$1p$^`i$%0-)(4xu z{@TQR$>aBCsBbO*oRC>3)~~WQ=MXbjWiQdz4+8(xDj_eVGSR=()K4D|gLc;ILr%ni zqQGIE;^f8=S41@bGPSG`W5Wr|#P5F2^`HMhs!+6j`PSUTZyg*kX0}OYP0!ev z&)fUGjJeH%g4SbDC`%FHz5#1hOr)8jq=7Yis>fbAub;{cWvp_+w=o+F!G6}Y4dkll`tUB#I zPZf1_r;W~hJ(qM*-C^Re#X|LIvsaz9h@j<-xbh*n>&9H^$b)bW`fI)X4zec>4 z?e1>zE$baB&LG^gD{~<8L;}#`iCB21@wrta=BXl~0}?q;M3IR}wjQwFbwb1T0yi_F zdpT!=#qb5?L;cE|fwJALTamiJgqZU=*x#mt ztt0z`2M1p?3)A-woxcBY+^I|>jkx{6Ax5Gsngq{i-&Bu-D6Wz?v6zi4Kgo=}*@sJM z2!$#xUy@3KL5zN|cQ``qw4(AftDQCp}a)GR%f>2{5a|Bb=?Xv^4JYM((pZZK` z3^Rb+;0Ej2UgPA3AFvsX;mSr4YW5Hi*yM+j%z=l5DK<>@GQ4J8+|X&x_wVRxw5U&Z z%uxeM#!j-B6YEdNnseC5stl%)uW2jNY23RR3Yz&$hbVHexr?Zie#CuSe4mcJJwxZU z^+nZ2-pO6rPD5nbZ!al$AkIVO+jsGN$pTC}_#kUsij){#ybXJJDj{YQag%SE9o3xyRy8KYUS3mfi$DSSHWoLoGXPnKiXAn*w5wU&ROi0VByHZhSoD>AY%#e z8*POYeHO}S6Q@zTgRgu|;%K1M)NCV4#fzJl)l)*JRm?j{$TRBaa^D%|N4Fa?&W1cW z@s|iIs{_Uw?xc|B)N;a$9TJv98jxL(-i|l(K2QF7kWP;lEO$_mC~$>o8@o%WFu-N|rVfn&n; z(-wqYGft+@xjK3!X^1w2f!WqJ^MkL8Zs&45=3+D}RsAnRN~jj>*|}wU#PY_EGk5tL zNGUneY-XE3t-@k0ukSj8U26F6;6Y1!q>(;V%#yzByyvS8nM+!?n}T}Y1Jh}~8t5}+ zyGAj@Wu(P*T!-5Y1MY4V9+x2v+x&Mb)Ye3^wOE6%?v$U~sKEoFvf^bN;xh&^9fpCY z*b)OUHm%;T$Th*C<>&mGB^z$GB3-1REp&>Xf&~wIrsmH!NK2cpM{0pDWjJbH!$~(* zoSFgOm$X`)_~1i9c@nuGsq1rEC%?`)f=;7&egz!*4CzwYnVA-^{xoWbq~I1M>R-J6 zd4@1bewQ^JJpvk&D98@d@eL7VpR13TH9mo{O=fFCb&joAVy6Duwf>{n zNvT+)y`#vGiB9qLW?p?4rJn4lo3o+Wg5TlD-x*B0Y}l<4Wacml$N9FyWjVjcrfx9j zU?M0jxa)a`L?(7HEA}9aCqEZZlDnz1k#!Nu2*LOhK%h(@@|mtChuT#)!9eea2ppG? zczzLUHha#1$6%M?kR4eWX$exPgv0jT7-mQ_-Lre+AR&Fxk0jg4{;UB9P+`WeVP~2nI#Qdoq6O!yI-d-!_{0}oySw*3GaT$B2-;Z&f z?Oi9zK>C8ArLq&~yPRp5oT0Hf(@>qhQiVj74Lbe6dppSoweV0D|q4)6@ z1@?Yzhfi7DoLnU^S~TB8$wsSZyz^Ks+_-P-x3WI?BwFve#P!c&5T8F;%}C!4Ue`vGe}^DLoGv$vkn3H}zeBQd?5-g}*)d`G zu(#!F`c?_`XrW{D<9?QYXYVz*76gJFlPuDO?^c3``D#~sqXg|2e?ohvG|uleiA2}; zZV$QRWaHI5D@st0UfMiw0qF9-k#6%T(gni(<Q=rIZE?MJovj(6KcgWh*hk*bUs(NAB! z3c1GycdZ5Enm#Z81e`Y1OTax?iq9=c!Z;Y0YM7py$!XR))~dG`KDAnCZB2`zF6c84 z5~RVfwq7d?OPqFw1GiRk7V%Kidbqz(jw25Lu6=FyT^qT6YaJcLHvU4d5I$MiRlZ(b zhhM5jSxB!1XYBoaO!U(G^tC!=wt6&qDhnfUa<)E4iuQ^Rk>CH!;t;g@%W z@IRsYI^oduInu}o==j>e7`mZ=ePF5S+uGESLJPl6`@|Yl%Yp!o)UT|owCHN^@tAC@ zRt?^GFyWkUe4D@UU@?E_*BPI5$|GGXOPXTRxKdcQS#)=khc-p)(R=^cAGuiHGI!J& zTv=nr>xg(&43Lyhsf_oNZ+5-2j@xm7NPtsaTdzJiLl^?6T zGjE;GVQj8a)@p8;oUqyINYTdbWdF{+5>&s=LY|$@;8*b|3VFCPWLkN+sj=o93JC(N zYZ$cCH#ItD5AzeaBVjyN(OEjZi&SotZS_;+pV&p8R z$vQg(Hg8uVGH7PmHBUkKUF$Qy;FHZ<5w9+w244|Rf?u%KYlvq9JE9SvIw@P2_pLa& zZG;+C0UsUo$66P5)v5`PR}OO6&#*;gTSa7+ds5-&1p#h*`fN}hnl@3iM)%YQ?bGw! zJ(-7{rC~l5oMJ7G6@9&;hgrx`vlmQBrsdh@3->+Gzgee^E1LESs7Cxu@mPf^~>oqL=5T09&Sbph7cwWCw@PZr(EJ6%WU}x}?PhKt8 z5NbDi;E-!M+uIY42K6YT7f>XLEBu^{6bH>PIzyth|zCI|EG_iPzmNFL5 zo6n4yp@W>!P`pmcuQ#T%26}_jxT=vMYeVWOoCh?6egzJ=_!|)i)z_Y(zydqm;kn1m|2rJcUpe?JG z!)>k>i!GQMs9h?bJQ4Nk%-VMBV)G?kU2ti6V*HO?SXXmIF99XJ`C|4|z;GWMC{Xw0 zNSwALc=RNxhlA)N2pqM?jIE>{_mxZouQ`U}kY~qO2RcnJS+}`_^W=q*93 zhP#&}iT;@q45Wj<_my$X^}bEe3RTuz1*Kj5i9(=UMan=6eQZj5QlX2h*PObbEGc0w zpgxZV+rj}NwpEYDjE|M%ial!g=Wef4ofaQ6dwqJe`k>Sj!fH5x@9_d<1dvkw^F8>u z0rV%X<*)A%?OSe|btm&{E@3tsxwvC*C6-zkqYMlO6gUvn-;8@b5u~Qw82&1T&%+(v z>;Y7DLV4#ubvk@@)2S3%pJvDt?no^pk89CQH2neeox`hm9E~cRJJbO_3+p?r6AF^^ zx&h)ikVcqoi0M~$jn58-Lw$z67?MKv%qTQZDbv|Za27KH57^dA-ciOnT|$8k|NU9| zE>d%~ud(!fs!Xo{ACWMh6+!_c2$?90f&~mLq?LHW4gWu~-a0JGaQgy1ieLbubO?x) zbO}f+9Yc3_cQ+_0E!`lUL&E?=C|yItPy^E44FdPWxaW87{cq+G@R_~$T6^vF?)l!! zGvSE>9K3=FvE*i4#Z>vXiALC6<0%LwQsuM+l~>C9vqBVl%7+bHPvgnJZy#7b?QE%P za(C6IYndH#pwnr`FlXqo!bqv3)|8^!eUesryeGw|_$7F5+QW*WCOuK6&AAR1sL0r+ z*#p9K7kfPo;&5cCHDZgWmgbIuQOK!|&jk_Djxgy(WvRKJVr+Ye7+Dt3~_oT;UInw1i8m979fwnt09ZwxuQRbGT=!+JgsRK1VM0^ zVlj!>aBXHHij+g~snSenI(9#{+X$z`>nO!|=sA{0MH&k1@(a6xSp*r(!e7s=@EZw$ zmrt%1j*w$2<1bMBQTXTNOHugjo^QV1?-v_1+lAAfw*3nVw;55U!7o@e*4q$bjVytm?S!RTmVY_ORvBC zr^NnGg^XiHppjJ_V!|1F*!Fyl= zUiPQ32h8v#Ia9KS_wVj|G#cDMO>q46?IQMDw1vZ%W5GqL;h z@8%cPBub%U{X=-k8Nn&M6tCf67AtFO`Q$fhUFO5JrFYhlrX)dzj%tE=?MjwU>JhY6;vjuD;A=x!PPxv2VFyU#4dG@W|Sp6yhmZXPpA;nK|o zpX{tLS2d&2dw|`JyTUs;c2zpJr}bg)VTAVS)%FC(`)%ebLPnYAx#VLTEci1qYSD?( zZZQ46o&1i_Vce*X`SzogLr^kIYQ0-+Mngvq^0N02P%S5`pXxHyx#L++e1nH_RxB0w zPLBSMF;rDM>@xq&WgCm}(J57;tn=I_>h^aFngPj4qCgKM#4)H!7H#lB7_k}So?O*O z8ezL!>qEIflu8`IFHNoZ<*;}RRlkO~?sO?*xMhnr$udbo4kESxFER@Oxe8{yYufo9 zsXm)Xs`@knbe{Ei!DDeVqL|^D@(3@ZEL5OobL{jd!_V_aNtX77$1fiIIZyj(@SlO& z_v7wg{#d_2fBfmy=5jV`dEGn@E#TfG^Y^TVTCVz4z9lOO@(pM+twZc$3Nv zh(gBzIogeQMwmj!e!`ydMbt-&nLx7+lJCuG1_-JX9#bYH4Z*b{#?DSv6R^`tbie@K!R!}9Ev9>IxK+^6__apO0}AE={0 zk?EpC8RnYkqOnq4Q`(@TzKatB_K=)7@v1W|nFgeT^8J(p`Pp zRylT9-pVQ3cA=eeB=)C8bn7{#D{(AnK1lC0H)1P7_ClWqNvwj&jM!k)dTjboWS1Vw z<-s*J6ka}*>f>?0rk#O@_nB-`$~UEsUnYjWMYR3@P^qC~>?iJiTI4JS*}q)J663&E z^MaL~aFPIfUJr)$%PP~G48d^hBc#_C@g{sKU^V>$JTNWpX0Lq}-`l)#b3w{`*6oKKZc(R7chg8wr7|=Q zTGd_drPS>of#P!|@1$W6oWJ;N-E=s+If$pK_YV!8GN!2l*>iegi3L@pAoaEcH17@QT#> z9IBv%0?f${TqCN)Ku-5l9*{KGi6?ttB4sKT^G-=8$9@dNf_@_O96z^iDOuEtVb9D- zTi(O;ds>8Tl(0-)LdYUKT_YPiqMln?m_sem?xmcqqx%7SDu&VeMAylM%x`5zW;7939%5 zJ0>Y+6U>M$|;p&PPRt6mpS@AsnKKfNMT^S zLUKYyNn?e)LN>j)e&)j(L1imxlLu#n4vHopts>+)$5=r*bA$iM6Q`1-1oc2Hp zOZ)V)h1GW6IO&OQGQ?uovXeT#1UWL+$a13NIn>_iWyGbr^$*c})z)d?oB!p`Pw|30 zQv1pBIjX-=sW32CxXm0;g?P0P z)>AMjlK^Vw%MtuJDmiF)<*6eFii)tI=_k2At#f}$WD|G3GHjHg$q?^;F1hQq_Pv|s z+lS5UAIE1_bOMUiBY1$^&@?wvGPe z^&RJP-NWU$_H~HUqn69zJz_}lx|N65xs~ryCQ+oF(;LeBHVF?R7ha-F1avI~4u~Xi zI?1M(K5x&5JlR{ZePf}=)uCg<1+Vn!I+bT-IRm>@E77JTwQn~?c&($#l|(sGZKD#z zI5d@cr-A;-zB%<$4JT6vd!`LOFm-zMwu7r-oJN>*i(cys*KV7pQ)*lFv7m-}(1W4A zfR1NX%~ybDfc4J`vV!P0g59?AwQE?~o6PZeK&60FAi^QO5=cJ|zAe8);;_Y8nkw!m<799;Co1;!iJ*YBoi*Z>MbNg}js%>u;R2 z*!gp~O1aD4GYHUu-xu)~^OsITsEb||Xu5SX{%yYhgX34)dSV)f8M|U|?X!{(3=8sj zH;d_+3x3s%2e=2G$OG*60etdTDxm^fW&Gpn^)G&(Mgk5(0^V(+J>vAojG@(UbLgzh zcQ?9Q7q>V`bUa*^$>MqYwJR`pUJ$-HJlZA0EZ61%-gRLG66Dd;vP8Vpz9qa(>JLwc zX{oea%eCUwDabea!jq&VXzLW*#%lOxQg|$-I5efq43MX-%v4cWXT4k{RzB{&LIfx# z`idA>NC@X_e~F&n&%>6deyLqpZD%hxW6U$Vt(bzT3WI~VV|H;gmpMw8--#NnK8CPF z1T<5nIXD3QaUEDTA!7U5E~)q=SmYfesCp|KdefC|ZHfr;pXm(UX$tK~82*SsK4w1( zWvcoC9kL(od39p;6f8b4h(+f&g9x%V?M{c@Y-Qt7>Aj2yD3?_H0)wuBf!!EJgo>BU zUI3OpLq1DA16?bNV7EO{B$!Ooax}U;q=Zp=^RzO(mTIw0&Nh}&Qoc1YdXpP-BuPUd zdTP~H&)(_dF6O0|tv*`=dMqqXZ3I)212_VmndGr1oe$VmiQ!&bT2PKFTh)g)U}UM< zblD7uwC^9;qLxL_NYf0|A3pjop8uQjEKx5Kp(%#Dh7zod6BjstV=FoHQJyP8gNmFlc* z&7>}KFx@{C+?;&tzGa|OnL8%A$2Fd=5QWYVzZmeHKoa4l+C_0sIkn@0ZRb~5?LH|y zp)TJ=IljbUx+^t@$m07*qX zv5K|2POReP$Ohk{S*}G6&SWz*jk_E1jZ2sj7=$tEm&@B|J9)@|D1d22m1_ys;kA$N zs>TqGhH4(N7M+4GIyraOjIo}hZqQ4sCz(jUpN1W<|FAw(=4yz43MLcU8#{ZIF1x>m zR75GV`Cr>XXb(&l|CFoYQ2B?oR|H}pU<`2j-%);xJy6!E%Y#9kG!MzZltl<1G^}Zj z|6?DWyoDQwSK!VMN_n3$2sO)Zf1GtTwQk7KhYg@!*>?lsP%A^w~*cR8;R z+GCPphgl4G_`SR5f+m8q9?^*RjVo1kdUYhT4Ec48GaX-w6MIA?v+)$_tO3BvCc-Ex93 z<{$bDeQFqxTmJ^wzW2oa`=ryP&~f;0dwzZF`=4{Eg4c4ucN080et$Osfx)YMe)rwJ zQDRhjR62$NLBSSl6fAWdkr;oFKEW-dXtoQ8~R>3_Hl8^Ms=@`U6Z@mjmddKh&g8{IR~1l7Pyx= z#;6B;;e(&#_TNG-(1YQm+KzKIKVb_Iq3BX7X(M#HE;gI);9}KVb%&2&n?=fal+~^V zIJSlfBF$Z@&U+@k$ThE#(vylm?VwQh*N&`xWJ;QoLM}B_6 z2XnL9Q!HSgGc)KqLVCfAa1jtsd&JTFz`v}bpR>+b4%Oig?F-!AE~`b6@`pM`IW9oi z=dBllulYfC(sqbe$`Q(u)$eOasj+FXX&5$=E|M#Ga9|c&!|){G9$VHVehsdw%$6 zge`zaoVR=ZkYdD+N!cVu>WMBF|f$4Z0hG4>6Vx^FelgDMY%|nt-qJ0wen1>j!GY zW6)eu&=ywaf!UH!FGmpO` z)k)BfnX|;{%gLbT=yfxK9*kIO&Ur{dwS#sXbu#U!+2Xv%jzfKA%C%>nqe-E0QnHxA zPY;g`T5WUCZ_r8DzzxB@v8XS==s(TXZ>Z2xD@&wo6v-B8CEP38R&$eRupbPrBw4VI z+DJM1z`Idh&S$8FxeLgB*pX`HJ4Ov>i&gWRs#Vj(^s%+3D~M=>$8NT-^7guJva52X z`&ooK=ju$pD+o%Cpq${_{LvA-_L-spPo4aFXdtwOJ)+ZKAc7tACI|phUOs=%iS&OQb|LWmVI7wxEH+ zqsxm!y~%bIX|rr|){yVaJ88V$PN;RraZXVbB3`qh+Ro>!R#xz=KM)9RGuj9PCd1W) zoh)&}cF}#XE3}w|oWg{|eAwsA;+EX>j^~LStjG4mon?O+td|i(1+MhRew_PM^>}Xw zNGuZhmU0`*U0r21;&8{8-{h$`ue2?NS?&4tAD6#nhcz%R^ew^a`I`NE@#?ns#$BdU zt5a~w2iB{Y=!-@Pd&&&RD@ukR?euXXK^+*rGphL#^pzo{Nn#2f7RqT_+drR9yMr4m&}`UcjVs&YJm{R4 zf2oSAW#WjZtL5;q`l`0oiPo7;=Wgtq(?k*HEi;YRc7Vv9r+_v0#RvwE%l_QyD&8Vu zY!66L3yZB|DESTBtMik;0nK@tu)X;=pdSAM8nEio2vjd4Jv@vA@n$4j7oC&Vx$s;1 z;q04CJyYI^vfguQKD{R$*N?)lV=o70*)&$mVU#=Et=K!<&{sR$)dmzUKd$2};EU+F z+98a&+5%p}wcE3@UAMcOX*x*PwZ5nmOc%!tJ9zIf+k2ka_H4X4g@lCr)Gjvp1;7M{ zf|yagy+;;G4|8x2=Z4wH=LBV+H6Y6d+o5KlOn^bD(e=(I$o0-HT=mXAG>en94d5e) z=|S>2H7fp+H`5Hud~NsE;Snj-NRO(ro;a_zduw%~eYuPn3ygR{B1E z4u;2G1U@1fWPY=P#@;A=lXHjGMw6Y@Bxn#~GkGD8%Ez)~o@E+%ONHcsDn0nYCTpVm z^BgyD-_F&pwcU1y=Fdssf6**ZId$DGhg-b6)gJ#Q=qv>aC;PgQ?&aIZN2QzZ*tMGs z`KozbR{Sp9Ozao@hnbkyV^o(89?~wJrA&#x$u107C5-=@Y{GxZ)^7l!ga_P=dYb(g zj1ej{k&nv;MR}HQT3#&`=@OR|9P5gXo7y;5Tuxe@e>=wL238V^9uZWO&E zxf-I#B}`U5;X5E_v*ayA9t!o6IS3zK&<8@$3i&kW2bHZy*2M@P0AmW-m4Ha+iibsP zZjL3>XI7D=B{@wXy@=#VhEu!j3eBp!^qP{3g-rtT`Ir0WyVNWdu2ZwTL~}UMaEO|8GB2qcm)oP`x9Ytf!!P7^i5Cy(@kMF^dPSl-1SDk8RZ;R zdYSTsLJ?B2jM92->0VWh1_iZk`1w3wZ{1WTyn)=0DWO^`JsZJ#CtJb}kgl z_5?D#paVproZ_pdnFERg>MznMFt4Ls$#?8IT2_)NhDeSHuuQV2q6cM12NiG3M*L2) zgYA}fqb4e2bp!d_%Y}Afo+SvyGV$RM{l8Dw<`ALr`;O&pkuaNRvyt*3qS^|r9Nga* z<9dwzgH3t6nJKR=_fGg7uUk_3p%OGMq{a%0e)xdAG29nTwbE8FCJ_(KFx9Pff5OSY3;TrOJN2YsZ{8crgEcEcVe>pPlA4j6~ zv-kZ)#$Q!qqqHq&KNIEBbxe5&VVh?WaWmy*k?>Dy0v>?S8Ect3&$1iol&Ex``xuKf z^qeo{wUsLPYN6brM*qkrkP+aAF)?aqbX&ZgNa|fl=hwU370ESpgWkN;$hN&dZh$h` z!b>Urc=ekne^bO8;~ZbQEG+h|_~4^wAtLOk-m4#y0yBPmH{lziX8ntaJ%&YG7Gu0Q zKg0pKW6Ah`=4jza5L)N=Grp}FC5$_hIZv0ou}Eb+UQaL}*mnm{o;PsdMs4A;m?tJ> zkTsFdv#cIQUJPqjkY@-i*YN^T8OZTX_;N4o`R#z`cKH9@y0kpvKR-pffY__~gwjgt8{DDoJaN(KjQ{@#~`y7g9`CGS=t zo+To@C4fQR{bdlRb_c%HKQOx6|Hq@~{ijxcbj4lkzpCP@Ve_#>0~%_^g@_!pvuRU> zw$xC&gf=027!%`_YOPM5{=9OdcI>{a$M+Xs&Y$9+Qy(WoOyza~;H)S70LWy@*^0sZ zPrcYv?9Q|X*3eD*17xDJD;Vt~a)q{wH$x+krXOH1E^d$F2yT^D$|`)>`V2vL!50l& zZt|J5)kI#kUL2rNaezke!(!0KSRd%pj%7ov$-@d6WPA)6WXfxF_C(2a_pSOwHsAdM z+t7jPNU6F0zylfF>nkZAbv~(m=y46<7IfPp({V5ej2qsU1FRRuE;_ZC|8(MsQdAZB6QyPni9J7Jgiv znNx8u*2HWEa@Nqj((g1_&v%^QRU(LrU|3ROG#F(B4Y^%&rViZgep$FYz3Jvz(p(2> z$MD5py1fFzVXaoSj+KCV{_dY9B~93#4qEl#zmFT%zaJ!S$qjeuvW>b}7<7X_3LZZd zgB^oVzw|p(MuQQ z4L|LT+_KNReQeabSH{E>W#s#@E|fgcF1y!XYb<|(U9y6XP@i%Dvh*vnk<;fV@Qnq@ zDPbxv#A36cL4;{FiUR5$-6C#E+UL19vMX1INDlY)LuL9Jay0PR^^YK-oGVs@Hhq)W z?AqaI;2CSoBay+z%7LMiC)LGnFda7={x_8Mt zR!{8LnOc;$o7k@;;lEADN3E>#Nk4nSeNc%01x3H1>kU0z4H!WAtp7GOwnCwa!|!Wu z5d0zAeQ~~dg1b%53>M8Z--8@yT+lq_u7;1xG?#Ux$^_jVeZx1E?ls1GP_0y*cD~~l zg65pUlfr|V7ZYnczMeDC0xnQ)R})-SG8|B|4S`5Nt4fPp@}_}XnC66MaP~Rocl}U~ zQ3*rza}CD>b~pEddGcvyVKkg{kk0H^3NrfYzd%Vkf6M5JHx~j;BSZ8!c^6f(dgW~F zfy<-1d0a0=y?))SA)T3T`>^x}$2QJiv{!U|J2@3d%$z97oy{B1 zo#MUjDou5;uG$^j12xl4NY|Dqu1yZ$29_LeDKjyaO+LCup`J^+mh?=#<;NLkryD&N zO0HO!h`R|!>?1((iTIHH2x1GD3n!!*{8{z1pMoCP*i$9wqocqDz*oJzJ^#GyU(C3h zKBrZo+5qTVRm%pb3LIPV*mZl3)viS2cxlOFqU2Zu+Xl81m(>Wk0jGO4JXO06*3~wH zEm>2gDtFz=Z)BvRjfqDseyLd=)d;+cGaEKYeg z|5AYJFZ_n6epi%-3lOTDzwkQ;xIDsGt;`e{3OKn(7_v>Z0*JSt#32kLUzZ zf{!Xmx_Iv@fDI_CdFds zw=5}pkp0P*iK3nOw=CfYwY^GKHVS#ZPe56I_a8(7*W>kAdSDJ!)(-hHU!gXt7hX$^7scORje9xWrL=B(&88H2~Q zP?KfO>oJ8IIEIo~z2i~#3!iCoaK5dVk#6RosL)Zaov-hq;bNxv!M;$c(k!J>CY@@i z@iPYhRose}S)1{>cpqd-Wy;7yOvK%EMrp%jM%dj`Y}#l`r3Z2*?l2ulB{BHpBV$ND z^31D-I>t@l`pD`>@b51QjF#U_TzO1<{ct+NUHeUs10o3Lk<7~iEwG7}fjfrzWb)bI zZDh)gbhDwb2v@@=zuMgCXTu%%()-?dPM-~ba#2t0kR)EtM$=sFvJ-gF&XCd1!O+90 z2##{Z+aF^(j+BI0Dyf)N4n8}l<1l!FCh7f#%_=nxJJp(%+)E10lhTQn&tQ>pZ2yJ(Y(x;6ngj5Bl@Tld=dSHIm zjA`NgAnTR&gvU^l0XgmvU)iv59fF-~A)Ks2YN%ot3(^}vYOL{Mf0D{n3wwKn)>aL3 zx6{~CIq=bBvE{hZRvf;GdI7hvCvwn*FgN9_2|nJb_}-MaV}j+w#Ae4t8<7G~euU?% zR8RB8dGP)%Z}ak8)z=R>OFALEj+s4_cx6LeHTKI{TyWU&LqRs{TkUtJd&P>^b3W>- zWvj~2P6|z!J=1L}xh~-Fd zGpBK9ayqo{BoEqh;V|m|L>HH-*L^WBXFE3CfuZlTm|YW_+nB-wM?-1*wC^Kjuu^Zv zE2#pb+!#TrqR*~z3W^Ona?CWENv$%J%5^fy!g%`7t?{D)ab zL}+{px3MJp65>}IgF`$KGTIf?oML3buN~P|xmah-y=vFlqE}|mekP5i*MMAssXI>P z@|)f!F#f`}FojBXOhoqI_GaMAp3Z2YhRta>Cw7qZ4*#cf?VV?Pj00bR5eePB~37GmQ$~EPG7oJPpKc zc9=!W>@h2+-tgAjt@4qnD^wwR(=e5$z;V zstoCY)ykDe9LDV&Mp~%U^f9uGm&&-r>^*yC6YmPjDxM%Og4st&xfCnLT5}FzsYtzp zyuMN-mUtNCkRwv2>lSKQ^syjZAy&Rp%qq>ZMZG%P3aTHX=n!C=;hvsSp=o}W&Z?B` zMBDyLBH7Qc|1>V8DMu0=TjQ&ZOfOeL4Bzu2^+~kPIt>=hf?>k81)wm&T9XEyGLfL& zkyBJAO0H_R1?$X{pJ7aSKt;IjY1psL#~E5ecJ&>J;<2=M#?+b|`&(|UP9<0>V@tf> z)E2vy0u_98)6QzOt*p6r5;D}11ygsNS27f=N`B! zII{4${oa)Y`a}IXo~uvt?Z~P99*=7SRnzhIOP*G{v4;(H+m1foVxR$DrqKe)u+C~( za+Rh?Fpkclf`w!?eC^lS8If-sp_(zg$eNb|(YaIvCrn6MZFiHqs}Rq8@0zRNFBwp- z;~uLJ$EncETQ0>t-OE}a7857kNH>%gCq2b@{SIcaS%wn-s-M{x{^*94DzlrGHS*9Y zjFUmm%sjSaSlZz%q-M~-anrV7F{c$tEWx!h%%_xPJ$wO8^Wz2%r6 zEd#`jpKUIxQ_5wOj)|p4)=~xSN&Jq4lYBV?Yh91E(pk9vlk6t_z<&Bx4aIWanXqnF z25|?QL`pQNYOx@R6K+cKb`%|$MLZrecIs*W&ZsxJ0dF<<{fxE4C}0toTyE8lYFj(J z+O;admtA-Qb-H2gGj_%-TiI!*@Uv+Fv*70?0;fAT^;cOaaP>-bA zx_A%n5ZT_HbtZ1QY*#_{5Dj#L#FLF>1Kl=@Izbg$FK&q6AN`&t@t$)|>NmW;;;~!# zTYfe>nhJo7{$y{xcbV6Kdksk<&U8MWnKQH$Lb76A*9uNQ>$--^idC4Oj-7A zZCtd=sS>m5J{fA=!ooO@wLJcU{_Od=lv8gVE%T}wyr{M}U&@SX!Ow zJM|)0BBXWx)jl;x3;zD1*a{_&;aSC4x&@vX&luJF9uA=bg{t8GLC&xfdzn%2S4&Iz zx6Bc)$KfoD3N|Zj{U=yjH|_L}8@7d6Ijs-GG~zg~II|LU>*XtrFvNHU5j5)H=O;%I zZZX=27022ItfK`w@yzs-{>%fcj|ot=iIBi1MH9)mQw5iS-5x7yD(1EQte1rBYwk9O zSr2Ru*W3x&K|WBcc$G-w(qP?s?-cXT8i?<7dIWVnbp5aRvD|UCKe{QG6K>`xhPVuL zLs>4fcSFx3{!=@?l@_x+$2AkfC)(+=s6MGw`{kO}WDS_)&|7n@=l~=w)j(xN# zs7dp)d6=J8s!gDyLauBJK_bI+j-q+$%U5yciUm|VxIIR4gsakh*!T*0ooiRY%3Fo+ zE=1$Ddutmy@sj$S8Y$i&Mt3`|XI4_;-p?TCPpgD5vSfVxK~m{pz378Q>T|2y?G!a7 zx{4lr=QF`t&>`1~2DbST=~mN-%(JBLOa~)z>1g$(c=vj!N!e!-#@-#vv7Vf%JCpsI zDt|azIS|p-Ot0UtvELf~=kN_+BCLe4+gl>@-&Q7SuqVIw`b+Fzi~Rj*#_Mq@RQxNI zgzKvs0gp6H8Jao_8F# zEFR#eq_1T2syWd%cx(G#ih!4ZPF|(sfb^?uQSe-f z4lgM9i}_xo;}Nn;4KZprREm)GrxYhDC??<9L6`UpH=s)+9pRtk3=D!~C_w`sf(YW? zv@anp20p7+YhejKT->KpvUKU|ak5a+TxskMye#03!_VgqxP*4AuUM$`JGt~JS*~eN z@B}_;q(W{X%o2#3wb%48mWQ>dF-1# z47{7o=NY!bRf>5s&{BhH^%hU9Qn5<;)HG1R$0ALydfhUS99E%k%Bx=q*D`HXbI9f& zD{=%Nvim6JO;e+7sP)uL^SDpMHP$*Bj@3jvSuz-&iF3lXe!gC(fI7trf@wKa>$Iza zhpgm^M&7b$Id8#TYl;T+kgfnzIH#1fLA5R%@KaE5zjv^OCL<_wCq zq+>n%5^u}EauTXy#W;q6CSML7`A`*z@_IU8cgd{a{Z-(sNg@0hzOsh;B-v+|%|;vr z#A)%giu^otU}C2~P1DmdvuC!6??8)rtwku2w`)Y)SDrXHgr9EFfhjPb+pTLuSf)Nuc+(mB7B{_WL8&;1@V@V@ zzsXM97H&$Vr+#mSbp?4#M}?Hfk5yOS+pXheODo^A@XM`Zighhiwe2>b-{erR@$8@2+@o{>ht4+3hrgYTuj-U3=W;wtkzGaa~lJ9Ss6Z1+fs5p4*DK| z7W7Spubt!V6bo|x0SC?*btmFY*sk*0X4u+S4aQeAm&5Z@g#r9iplS^bemj zYpiA(sq#7-1c9hoSp3s!h_DAnRLV~=!W>D>*h}OaWVMQVzm3z*vQf2hBFaFm>d|C! z=FGGz=6X+3aQaWicHemM8J1R|OU9>Mbr8ir)_4%+c#ff1zG{Z06CLZC9uv|FH7o9l z-3hggrye&r1iGH<3z62@lL8i}8%Anu{jcsMAUFey@R&1V87PWaOc2}|^z_+a1z6ei ziC&+m-V@ODC&eZZY5*c;i^{04<|S}BfwaRK8|(;6Yt~r$+43Awyfa;m~TW52PgIZjIMEF~9HOVZL4QR#-qqV8g~Z>{+4>?@^B8ZzV$Lm)cWbnPm6`ggeR z4Ds#>qe&{;VR;Q{!B@+w!?dcwWiyyqwybOfI5o<4omXyl*umX11{@-<6Ib)re!l0w z7Y`Qg46za{FE;i^eG)xi{himk@|ziTeHLFrZwo(e`t>Uyyi9^Y(e}AR`Tpg%2o~?@ zhVNeu$PGICD^|vfU46i#4&v4-zczxQiZ|`}e&U$~kaODyLr!!uO|@4PN2Rh_Wzt0w zsYtf+iNZrkKVQqyz}vV&dWSe2rN3G;jh%-m*^~~Sq5^mQnqgHvokga$b?^G#tLbGD zuTqQz>KQKF#44X0sPm&~@>m6Uq6QDN7;h`0==Osr5cj}_xp`y1agi`YqoVk+`kJ__ zvL#hLVH|&NncG&bwt)eP^C)m)iJxGqlk>y$Oc$nL^N!wL92w4P)qkpiP2r)u9TAom zgCvv|6A`AgP3fVD-FM1r^@A!{sU35*>y}Q>%OpZHgUe0XY}EIt%=kHrJD&8!i+P-V z0=8deZvLZ>;1<%GjDieHEhCzJxcv=YzSVac9VN(<# zlp^IIO1y+`1kg^=&%nbDZYNqfiRY?!Px@q%p#w#((^~$cuB7G8l#O}~c1)UTWsCnt z_Y2^7_3VlIQxeDogjdWq#bVAUgvt$rW6D-v1S-Ct5g2#!KcSjr-L6@C1p500$ zPZhZ2En7vG!3`XLB}Fgwn8w7qMC)shI#|!7-Il0F(j3UYfm3)+X~hbz=YIPsocM4| zIPsg_Iz_A?2Oib?PttZ`uN1G!KbbqO%rxdkt0GCyQx;f!UqLLobec|`0i9^xR6)Ge z@a+{RZ-ln6o!A!vWF>#S#xi-9l(N~SC-CxGxUqZM z}-%N`}FeU`2js*Z=yA-ytno(yhPPnek1a zu{W5eNon7J@2hKomh8+=n-QJryMCT$w@;xZMoTE{SpiKlQaOn?8FG*s#O9TEvf=rBbu*ZU`vnqQ@Bw=PYdmYv{Kh?AC^ZPTYq zw$V8p+2pXYxF@C+zkG2;0bT0#d?(j-$3y7y(t*r>*h#zXby8XXO3r2@R|2!PYBixW z96Y%bqhek$0ASwuirE-R=FpFZsLci$S=ACq_$%u`-2Zj=`cud%dM4=Lr7C988@usl z@_?}er(;FQ8QqN|I`@7e8ghOaxR8t@I$dC2vv9i`#hH)^t5Yc9sj)wcz?UuB`}Xs3 ztdEcD_IzL>|LyJfwN@Q0=9=QPSf4rujw{!fnOvu37B_ukQw($cgb9`v|E5s>b_eu7 z>kw^=U8W&K?KgT6+YtieTSl|{vOcfn%OJk}VggOKrinE^>VDh5=meS}J6bKt5{sGT zeD9AsfLVu{-V|^8oZ6Nk0Z2GASv%)LDx(w0@7VJ{ zUjwP`2XH?794idY zpj<%J^l*|^H!IZ~YnAwgg%Ze3MCKm!Ut?WGW^rWa@hnbwXiD)ekv3&baCm)n3g1dy)}x$3ZH)vpUO|JbCN znr1IwDpt}YW1J+An696?gT0A`xq-9$)gs+kEnD%a?qd#`>4KZ%XZXf)Db}Zb0s->6 zK8JcSwvVD#)11r~uX5tKsRA!e`Uh`LNK#XO7VAz@TB|MLH2d~n<{239#Tw5hYBAH) z$O>5klfj?7_i$35dei>O58T){{EK@&;I!6Xq=lLD-kn@P5A&FvQedrPh{~ z-Yh#o_v@|$ZMoGdtrmhc&k5rso#Dmhyxt~(A~q3Sj|Fs6q1G6g1o*6rBYVWvHdOUh z4L_&%ZBwvSwmzAX?k!h(kv|!VOY!qFD-20rA5hyKs@WYSxW*}Sq_m~lJXUn8Egr_!Igm*e zI+dwQC;PeuY^@#Y)rQ^j6REFw#$IvY_zWpbY`K2!j)pGLIioD#2#n`yFFa{uhHWwX zs=<~ja~mhNK&XW!>PO&49FF99`CQ9T*p;!%X3I;L%`4+jSg!QEJV$aP&JlPVhk5K_ zEq9R(398EXwqNWePs_c?fuq?KC3J_QJ#p7gjPaiy@^|azyM8!~xU%_*#i?W;#+;u5 zeZU479+5%tiDEz%Y`OqqK$!zH{TA*|Lqrw$j}jO|n2m*|7#6-b@V$qYjDN`Lq+}V= zYV4=s>W=Be+TTvFP*^fcBs9{@o9=c)L8fExbd!M!;h!$HNTKeI{wQ2n&~jnyCk#(< zc07OHoALbxzvFC%rDJ-GAH=z7kL|wF7=A?&z_%|-_LaNLTMUn@vV~LaoOM7ZA->S5NstP_*!hE{-WRhiD zUt*!6f87jsO?5r(xlWmIU;8=TsB7^qE#^n{bu5w)h3EHb|J12DddmYV*r8;)ihI)9 z>)FAzt;A&PnLHlqOHtvAt$0ov5qF}vHUh0R_unQ|T9a}C3bpNz0JQh+bPH%Q^Q^R@ z)V7__%fR*kvzlFn(=V`-Q7})3Xu+yr2O;5SeN|=SWZK85QKvnX;im_v5Lt4o z|C;NsRlmoi3qI7w8aNtxhVPa)1#(-Bd4B=4ejwopJ&)+SODBgcoHDcWCkw-$zUkdD zGe41rQ{L-8mrb(IKoj`>iH68$5lA{WB;eZ_}bDCDSsCw;U-*zkUv;1RDSR=tMvM3_6$y&CA z7r%Bdk&P}q>}Y_!B&tbZ-=ZlMWNP*)X{unssc&cM<`@cJlWB%-#fe#+H99n-#9v3j z8^gmYH0u-j=vD9jv42Lp!zsq!X<)fQ=&Yiv_VBCuXO)T4)Q3Uie6TIXO+l>pjPvYfnhSY!8#?znNj=41vB0|lb7+yfmMq8;=VrNdVsMzlxxYJ4%a2pJI&_E$s_v1&y3j)k zmG6(jQQ{49u*DpZ(VBoEjcQ-R6rIC8aSDfY72&JOdy_+2YHEtt&_=1}B<0+9pqBg5 ztdWuRZ98uMc2xsl2>`oZ)65J&uI)ncVlfKbe#Awf;3QQ&xPOIStVvQS(q-qO`nd>H zAZ6e$8EBK?o{#>Ez)yfOl4i{v>N20XgY{JO25N?20`^MBM~}tkRl$#QS}v79^sj3a zyBKb_n$Dw?EK}CgbsClin6}?p#H3G>-mHx&mNe$FmlVulVP$#?l~};+*K>zjuWURU ziGg*Ks^Hb^KRa%A^@qd>VSkpmfDZ4Ou2?nze7U2Mf6{QB5&mewRwZ4KdJIZoF|KB6 zKV9~e({y(VK~8|#v$8+UaZc!#U|G2|uo?d|*|2ZIozz#>jpJCQDb<3{Ex}OT3@}KI zA^oh6KrNE~qS9ucI35rFWTjoJ4A@mD@qbDHkhV3DUqH75M2>j&-W}NGl$rN?O5jBW z>N&sw+@BukC%IzR=S71OHisE3{L!~js!%`wwqNAl2E3cLby-9*jPeMB|SW!%b#CUV8&$DD&P`}PK!S}gT>|$5F3q*`Y!+>PyKw%`KDd34<$1s zf4d6`t@0|k|sdD0hf?nz=!#KCc_%!BmOhzDUZuS+G@&o66U3_i<=8)e{gl@-5m9+P z^5}qm(0i|zRNy~D;i0nSTApa`RPcP20vHR!C$^klZT|iSd_U33v{0uuWoqe0kY(l- z!2f{U`2tXGi#~QoxWD`N-12?@ZcE*Hr07;OUj+Aajpv?7akfCGm4(cl($Dz~^Juu; z8#rAbVqH>kB0{6}$0z99F@u%vD&D)+q)?6(sar>Hli;moZpj%;6#|c~ArP7xFVr$f z+Iq3JPhuKfqRucDqVj!d?xy{f0=iToon8-J&XP&z59<9sNZX3`qV^$h1BAfRB2lvT zjnB+C^qRJbZwdt}VD%y-l&sCh007K3dCx&`_WocRqE1nOtu=W;(`HIRG(^vf{p7>&XOwCbwtn2()-ZCm($6N`7kF!3w0z7Z||Do$E zprUHKwx35uRK!9$C8bqTQjqTMp&O(b8Xi(4r5mI{U}%P67*NUqq`ReKXc#*FGs^S+ z@ArS}UrT2#j|V+_U;Enoy6?01+(aX6s8k2DD=*;;UGqc(%l)$~$uaiE_@$o* zK=vkJios8-PQ9d2CeUZ{saISJ0oLnEZ62dC901kq3ccZf^Yc}!lXC0tk&ycAbK#mJ zPbx!T1Wo+U@i;S&In}kVPxe03O2-BT0Uxx$Z&O<74yjnoAO8In5%W4WhF1C+=E`Tx zy|m31etpg>)9_0K?%=_B>9G#PY}&_etjeHzv{J}x{1h$d>dnu250Q1IS1)R)`cG}v z)<8yP84DZRHh;9;TQh<)6lH^Bs8mCC{)oV){5 z%AYV{9?7fOM%!SyeE0!J{0@thCJeDVnxj^m7OpyM&mui4HP~3kqKw+uzq8fs2dko1 zINtBlLNsf%uSt^X>EM~2{3wr+Xuc*eBC~Tc0wu4XqO;9cxzlHeY#q&2KQFKM_-ho_ z-OQ*0D$8 z7p51s$_#bc7aw*N?DE;aH9&VQelh2`;fm`ex#PB>M9L%FBim5ocC2|-MtK@q{%=aA%m;|=Fe#+^AS(H4TSJ#pO`Zi=sE-@d2w87OP#Nt*7L8;m?hKdx3n z{gWXH($boo(2?|8s>7`is%ES)-k4NqZtnjpNViSv#Jc~VJ5HDXyn|dlUJ5v4o~G_- z(G0vx`x%9O#|iC6ML28#G%yo1k#H&nCn?N6F(o$i@rFYKEtn6Q5ZGHjrMJeicp`Kh zgDYgY@?@db)H9j5M-tZt}g zmQSccovCy|+{wEj!kR;qu8yo`nxSv4#`^LI=LuhE89dCT4$5x9q6W1#%S?&|m&@su zdzIg-3|Zixh||f_eX$a+OE@;zfL^r*j60&ZvP>>dsYF<{+yX;|-!K z*EYf=~^+ENadr%F%*AY5?vrPT77CR0r z_O@kYJtk0WEo|+~sV8;;*DIp8@$8tJyO z3bxgO7YL@E>D9>98xJ!3TQ}*y_J=xMO)Ngv>y8!uG=ULr%H?Qg-pT;W1PD|bh}{Ef48_T69anjUcG+|6y@ zX2TQjt1C=#AS5 zR@6kFyxHfFwVto*x-0s5?Qk%Za9X)b4VmfX*Lyp~TL(_xoPHSA z98wOxm~z~yCwZGD;gbVJ&G1e`^xk%{9ggkAgLnv4!F1NjV37Vq2I!jE4Zeo5CHj!Z zsa7uz>IV3B!r>u4TCH2J)3y`=nWm<r_?(_;Kt@^1l|cAet^VHiYF{crq%{Q-ZO@ zfwdooAtwA69~A6`&L2#ZOy2}S|2xPF0_2Dn6(Q%7n;i7TU++E3=aS=hY?f3lti~+f zT$b;=*!8JZgx2x1Mm~M`0d{}uS%W*ZvsSwmB)&TtUUD@kGc2W{sO^FSwdsgkS8SJU zlM(M?XntP0P@b|IGRii}op@x_+VsfYs1ORwhNUsP`PsRF^NUo$AdtcYeu?A+`bW9o z6uS8f;Zvjg_%9xQs22fE1qp|%g*|BASAtm7dOC%tR)tSf7j6Q5F^q5|t5cVq1$4WQ zZkp+Hfm=!NC2H5GRclgv%5o-kH>6pj!g^me#;Ho?eyag6NiM_TXbi1v&xE7r&R9Rb9_u8*@3pmnSpaf?kfkts6_=+#H3f1wf z3v%Z}7o=H38GN}jx0O@QY~bKSR9Re7eo2Pj)Ht>t$V39Ec|#0H@W2O3wipjKs8)>e z(IJLjiiN^iVes~g$lAy<*WtneN*D=@)B#ExW+9MrszqPk6 z$gu>@dU+~BB-SPT#*w>MCP@y#@6bW4zI8%JAKl>aP5xiM5a>LQQ01kr?o?@#&TxCS zhWuDadUkT~6xVXF6ig}KaHMSvZ+3UsnJ413KLdo{l5Y%{l6mR?EN&4VHQ*V2sfmPA zkqPuMdpzj%nNKasPqBO()s+otI;tM4*70!-=ZisgJpg<{D-V3=hFdBwb*a!|I`=7h zeFYBI4^=83%x_PV9Qeci3w>LF@9@Ve31SlODs22Vqw-UwS4rBy#b!@F(zGWpAt{o& z=Y3tK%kJ!tpMye9&}c*!tCMW~t!a{rf3186$!dVaZ*15IW7t%2!=_*Udcv+C#*vXR z-y?ughBp!O#FJekY!j&5b%G6e6Uz%t2Su*`Xs?y~t#w$@v%*AdlJp?9v!#+8l(GcU z&lP=ymV+Iz>it1zD*XRtrEI~qCT>rz-aSVa=F(xbc#+%aF5HLT=8=JxE|D|4RUQ9xy8=VoE)&(mDCxRzuBdd zw<+pJt~im9Cq3b6)_y#J<$1Y@&Ko_3i7QNq+bJ_R&FIN_QIE}>4u8^vIeAo_zCo4| zs(#jWZEQ=?tJgJ|I~YC~mG@>onlF+8W=Qef05&YA(S6ga_O16OZHAH_QC#&b`$hFOyAj#W#CwA{-@Q;pDnZ zU{LlBou}D?pJS$)rRYVkO{*@28V%t8YGqvvtY;7qyylfyE8v;=quy4(+2ODGF*Sd3 z6lZ^G6U0ROGvfm_$@`^XvU7yN-G%r{2SE2eT zc*X=j)=@$q>bQNUI!kJ{+EPI#_N5#c9=%_`Rot?!SSX0cyfw>~2rh^MCu-E0M;)8Y zMfnLNLGIa!JOhjZmX)`?B=M#O_|}G&5d$6;0vX4i&`S*aIn+ec>-7J zu}HcaAS$%qWgru1qOVAV6psc<88~Fxy2Y4-^%cGOuP(>UqcrNk1O<;==r!H!J|!r4 zabFgzWxIUe{OC-9TzzRe)PU}uyE}kl)4KaXxXI#994cE-5_~$D;myBStCU~Fc%^$E zSK}r$^Z_@x08IFPqr$!eZU|8TF4o`pCOJ4k-ji#OOYt(9ToUZT3y1M}OrCwb9P0$HEs{pdI@ukeE3=wNjV?V0i}??=YUp@CQ5%3R3#CsAIO=>AY~>&n zy^>(^sd)q8Js^O7jYdDbp}|AIR#Cv#lYec+#KAov4*nHf)%O+;mgDsIMxwlU%s(dO zemuCkjMAS{(7IEOD5A_wD2ge~tXd-|1HCyN#Nx=5^JCJd)?iYZO#opeXu#$MuvlIg zDtc_P;bDFH^-i~VrlJpDMisLpP&IpR>A>w+vNt#SeuqIB4-f`8nwWxcrm`W%wgg0j zX5Fo~W`r=b%h^HkGPQa@tv6bLvcsDMZ6=0|=Va5C6q5yalFidARckao8zNTB!!g~r z`j3uK7B=4!5C8Rnfo$UA*0IBplR({K8-LTJbh%hpd*t4nl;!S?c6tTm21+0mUMAlZ zil6V;)+Di=M1<*Y2muYZ=}W@Vcmd~48z+Cf?dm$^+ScK>sJ(a(F>W>{1&`;5f3rH1 z=4HUGNbk3@2-%|U-Jsqixx$|q=?L5O7Ps}9f$)( ztm7Nvdg9J_YM|!k=5v5E`IZ`LLyyEDEaH=RFp54gJN0{-z*1z_j?Aef5+BBO!Mses z7gxdzH5W?kOzKJV@|zXRms@@~SY|eMXdFqyomsm#$7MZ!73hT^^Vy8i4N#|ERvlYt zj_fzQS&;p@3z)|C8RyLL2Kaga_-G*Vef4f=0VW&^NyXBXcW<5fHo@t1{2g)rh}Yd- z(2BzNIc&emJXT_VAuZglIFH%lr8(M;P?N?2&x$a$X{uwgWw!0p=2wEECv5o4iS7ai z^R`_OTdXd~6pq!_Ml>AcGk|j%yi?e381!=^9FPDdUbRJx@Os_0rH5T*#-A_+-}>(* z4Ll1RFH+{zD%pniiMEk(xNews7ctX%xE*U#aZG7S&3=_~o(FO??$PY|BeH2Pcc9!G zv@%$M$C^w#Ll*MJ*h7 z8Y-0i2lQYxI8~b9vR39bvp9%@(ZIYX-|1=iue){(efB>;x;b-eV>Q%!@nPwT5iwX5 zlsuOn-Q#vfuBzQ*u++saDj3--_^ORG=87mk$v3)5)hEym6NYoqp6;CWQ=W#zVQ9 zkdM{n8HVcV5E;(^ZkseKtyp?RM5JFBpFvrMeN?0Sr$D#NQqvf?Y;m%Sf1zq{g;NOh zeUsd69*a1&Y>gbV&mWyXo8mVG&o?XO6DttIOWA8<+}c(&?qP5_8!k7TP;W9bN6c40eMLVGWVOiaxOgRzxD7{<9(AG|*al^uVPyst;$fbwl=c~-bsnMRpme)pBCl}oD5H3| zY^i>e9DlfJ3^*WHvtByWmL^FlUn9r$-B%s*a_dyrcLCtw3U37tNpkxbQ;1%yd5~j8 zaA90HT%F2dXntlcIYWT79-7WXsO`*XAi=SIB-&outRYPhoO)l4a!zO=Jk18@Jj$e; z0RmTI$tHEPpUu(jB&qUH%_5aJoit-|ebwXf?Rcdt3}16r?j1Bm(f-P2Bm>yndUAMe zK}GlhBlVp``uiU*<=`&e0#-%$&WNqeGOvcmAfJX_F=@ZAZ)r_IT@J0Uj!m6(uhvwn z-P6Txq=c{b@0ROF37`vk6g~DN8{4gx#QZcEYRRObv|d?&5Hjlzc4^QOfB3aw{){c{$D{^h ze9`ijAxS}O?~D>%GUS=1xMxzf+zu!JTqTieU7!Nd^LMEfOR-EaB)6y0#PQ&To z(WJ4@FvM>fbH!&P+=86VT_0w^Iml%0{qb0`g5anh{}uUpWjv4(+Wu~7I(Ium?amh(IE%fbP7_P*7e6;!+cI1%CuK!*_OfC-ochxr1}qC zIxTt~7Gz^Y4fEIdP)gc6lOep= zSxHVQITj6X@Sgj7>rUp`vprSaJ0;W+76PZ|Nx{$|b(aOo9v!broo{?!Qf_;IZLt-7 zLf@{ZNbDm#O0LB+M-Dc5>61!gA%r$hZBeV+3nrB2GWLXuKDd4%PM`B5o52AN;&>JX zDnXTTg$j*oM%b$UF6oeu)yjD;e@i;2ST;swm5EoV&>OJs_s)fk@_SJ(WvFG_W{TFy zGH3 zn6I}_o}G?MlA1CP?^f4+O8A-1#%0(Z6Pb=Cyu4n;kd$f3WU*iCZjdxjLh{ET*D)oa-B6GNqrNPl=JVubaOllvR5$Ftu!NLY$ZW-9WW3u7Jew!%l7rt=33a zA~T`+#zp}pjq-Gd4$ITrofT^m`lRkr9qP=ybrX968B~T2(>myjdZQ=Xhdb5bY-F4Al0ffF5PO3{%i!A=?NR?iz#0=vB76oR(y@)>WcGP zar9-PrO|3xT(>x6y}fIBf3&uOP?pvvC>h?668yC=G^wI?bblH7W6jMK`=JN0appPF zGFz)@+V(H_lXcj`x*gIqJs9EzTb#F(ueA$;#8FLyj^f z*^sQPkmMS$40JFlmr^cn5SXVjk9E;&<)@hL;2CBV==8N3Pmz1nAoQ`0op&q=nTE;-x6Q)zWIDP@QE%T^aqZq%s%oRGF+#3r@14hiv zf z(jnL|!7@dx(#2m7qOx)f?8mv4RO@AAep4D|lLuv^?zM{kyx-c;l-52=ty~&oOR=y* zVx5?yPd(e!p~;+;OKGx-vLVdfv9VY!p`^)b)tu_mpw7&puwGfQrpSq5QT{nQI79ww z35X;`edX-Om*<&Jlr`5tkjxziMzgME8A7S&;fbG;?rH9h0efTI10-!DQp9Z&v~l)i z^Xt#cRbRFP{X72@*ou*4Gm_?*@$Ff5NfbOwtpZFfu%)#DPKjeA#cUblXjInLRX>-8 zoH5HP3t|zwT185ERA9XcJDu8Yl}|?&&H4KA>hiykSATKd6}GO}Dt}_*y(Qr@+1wn7 z-s}+2uG1Ldlq{FEd0_s5|MkXDdu_O#U{=HVE4SMKcR|=r+5aYQhR-+{bboVq4>8$% zJ(6cOW{h^4On;|jiJ{3GDS)yY)x3C{!UQYc-Y!Hxj+kXee})uc)N{M7IReki>ur$g?2 z;zzTVq7tOcuY<>^NaT?(pGvB__lSOCY^<5hFT&R4^=N4D;Pv7^nK<%>mV`8C7JjNu zC=2{lTUH1Mwb@Nv`al#E4PRL&6@b?=ajG`2>=MN*;Ihe9Uv-MLNSXa2Me9Ur5Ybs5 zO7$Un45or7l3F2Is6d&@EVYg@|yz20aj}H2Fq^UE**%+g-CFk<)#GAvho|m?dI~QG{fp?a`olUfNzOqG#w7$ z*VK?Aq{>yWBS&|(!Xig{kGD>6S7q7{O(=e9J1w56oTgg_3Vt*`EV&cRgq!ntM?YPM zX=SNXm9nHTL#egd?>bKt0M3;h84SFNBfuDN@3-7L#sUC_G>Howj`836&0Pe!lO?b? zk{(XI*#FjK?Z2$4sB6c^ff^`O<{|XrU;9BMsdj(z%!drI^Or0uoDojDN6=40J#l5L^OlJ$3zm@8&bY`SG}LjIJ3^)Fd8xGp zBc8k}E{}-Yx1!EQB=e-7^PDK<**W%w!;^}0BWqJ~{YmA=@_CTm zG070-xM+Zg#5s(l#`$e7GqzZqX72^DT!LrMyJW@YSL3{pG`^MS+`${mb%SM-_fgRlScAE^+~N| zC!UPAP?6Q0F}%l{X&3uvgIq}@^$7BBtoJsA0Qt0Gv-aMvnZ~s3_G#SOVbC=osHj5k z0V3;+$fgcHNw za=-X6pr~$BgOq63SD4hbUIr`7#&(MX15Q>sanSythXA0T>3#!>OcGIb$Ib13KCAND z_yK#Fn|XGk2=~>Ikb>asR+k1cjy0a?Y59}wOFKfC!Qghk*k!ZgknO0?J;4}neD`>x zAbE=3L=lSKLj$0ZDWMwM^IQ3-bKC7b?+XK@)1uu_=KJ5zw&AWQsTBD%%e9uD8B4Zm{FWNrj-+f(vXpot_T4-Y)Bp$KAfm zY1TTGEvNRizo%A8rQzF5@NAh%rNOoPD@o=UPNtvnC2WfL3n+`L z@7SE0^X77~&9km7)`fyI?_pBvy#UMGFyv*dZ!|@+&skRf`EyTd3^%Fn<=C4TLeg#Z z|8ZrWIy-P9kn?Nc^Xv-e!Gn~JJ48_*KXp8U%~ zEBa4KUlFHQFLYR=4E{W8P7yb54*OY&;$X=4TGJ{bmH?^K!$?hH^7`T*gjcVQhNfw)V(PtumO zXO9gau|b9u-%LLA;zWv5?!6NrY$nN7F82>@yt`+1F(m>(*udo15x~DX_V~Kt)Frc2 z&+f}e`m|AN)$?fkYxLv2SU!F19d?>W+vEq|=Ol{uTN+ZL%h@!0kxKM})qB^k%y&eV zi0x-mYL97)czqG*|jFES(O()~zp`Xvs!3zS7Zk8tRtjK*Mk z(+Bh5O|2AMF=9F26l8WBae`UskbIj)N}&qh>3*Bi`ofc`RN+Ej=x zmAu>-9l$k5AYpP}wYh*YJpr!$ppc*VRaX5-YIwo+&131oCF`1d3ZsTL`EEGG?YgU5 zuYlw8x;mOwEN8F=i%PmJgrYc~eLaK{AI$%neMFDF!>7OZEN=HN6id5N>+|Yft*+&j zP95xu+K@Ds`U4j}K&<%SEQt>63}B3dMMyG6yhz>e+N z^uer}y{|S-hvhA1qg{8m4Mj6EW?9sTt;9Sf?8gQ&wpd~$JS#txKJ935WZ8Os7NRUi zK5&<13#fLSFK%Zvsxt;D%vL=R14NHIm!Pj41$g<4cP#jHGMN*%OZ}Zx=jj zEh6$s1m_~}Eplrh#-(z>iMjmiGhIshF5#RThd={RBUy~{&JR02)fcuz3Cz&V$)0wZ zluFM9y4A7j8fJ^}^NXvga7Mh@3cscbyZJ4PAu##8bp&_6+$|vQ?fC1xoD@u*E zESSmldJL2*fK#lWam3-0apx|6Tf;S8F2G%YE)hWxo`=6fH{E7uU`J5o_RakIbs2Tu z5gX{`cj|R9wW_MA@5|l8S(<1+^{J2M@urClx;kTb<>HK3(HI^7CdMM#oLEBro;}}t z`y`jY51`c2exwP95)eb2$fdj6RI=!JPYM_PB()cO9?S}a8IAd$<H4MLYEE!hl7UoW|XQSJ7D0+ovwc*%DlWZ6^eZMN_;N?{R{lN)P16Mj~knP~6U6)*I@40nW_wae~Ii9?4G_ z+Q%@7tvc3Lloa04=60~5#n$C?vZ6nodM^FOFOKbtTQ}cd#AwUWDw}mCByeC^Z#iM< zezlp6V8zaXlvkFH&`^cNkcK|w$x8V^xM2NpM{IPio0lU`+nNoX#{F%6DkUG+w*Y&9 zz8Zf$!C;}!ec%7v1`Ij?v2MXUANWvZ1sVhnAerNyK5koBBgIs)v&kjk8-qM45aV~Y zq|1+X{J7Se^!`DIICErr6FK`Z+DyfO7U%A2hb45Z^?5j$eA2Ow64$#{0aTJvi&ZP>DtY`Z{)>48j}&sO}K0T0lF3H+9S8DvHhqA>gQnVjZ}g53w| zIgE|-^Z<)Ym4V{kGBu(H>evRa|EP!2Td8N8c zlj^;*qtwt)i(i_Cocj%8K;Ad*_k%2l54rBE$(q?`56cX?IA31z>kS+901|>Oyx(QU z0gn@;SrnTLNrM38swfXwc>x?6<#q|v46YF6=8KkP92$ejC~@}<6|2dyY38_(7_e>6 z(?4G6hjX1R`ul9LAd4a`vgA2Jdt)?Z5tXLBfkZx)gH+vn_R*WhA7s^SjFLL%9JYTJ zNLE|lIoioO+?cEy@f$sGe;bzckM5H;cVHMdAUXUhsE+y(S!}_v8** z5Tgy*_VE^oPwo%)->Cv&^MjyeXk*J+HmdoZ>zNIY~X*gpGLooTY(l z|DbiEkcYvtq+mw7VnN2gM%o?sdNeLe;;mOj-RWecpO@Y3{fJpMUE#8}PWCk3>gVr& zx{-BjI5;M~>`ih!dcxir=ekeZixOq9mnH81F4d4YcbDL~K!_KIe7v;1G>g z&u%)#@0Zk13Uz;H@Sw_eL2lH+ZM`(nH^NXoU&#%~cNTkKpO3PQV)X&dA%&UM(v4#5 ziOGM0iGBRHAK6g%xb=) z#31;$&;6wqKZY0gHV{KAa^#63Z};DvUU9Q-qS&TY&`!hQ85dPLzsl9&QCfF+9QZwa zO@tcLsu}xbYBrDO%!S1=&6rE;fjtoWnY8=G)(lV`EVjlTRf9ffuWWTHMW3(+vnOu1 zJ@k_zg$+KWeobTkE%T%X#Z=wZhe8<}MG^t>IAEo=kQ!|cTS3@N+~B7B%g+77fH0LQ z=hlGfq5g4*d*+zwxpl{lFpqFj(CXOAxUl0GB#}E)cn!CGj9wW%Zlv(WYHUKVJ>R=L zIg60civ#fLer9dOusfU-`Y}xl8@XIpsbVdDy?UJ!&#~m(bo_{rE1ai=NPY|-0?d&G zvT(!$GsGjDoS>xSDu0DNC+D7+)soshMa>s|i&YP0lvj(P4;m2_nHCGEU})JbMLT(Q zFAjfb)NC2PW);5L5QIJBa`$*ntov432ZF!xoU}U5;`VW(YA?ke-^i^v0|?B;R;uaUnb5EBBQP_$v1+zbT-_U^_jj^sB=K! zb;YdO=8SYty#NJkum@UFoW@$mu``5llT zkv7k=A2)%wSctoXj_wZBSm&3x^Y?#kpNOZK8zN_$Y);+TctohntSU)r!-4%8G&YM3 zO#QBk47suf0%9*+9@x$(RE^Kpe(6C$Xp`nBmd*y8da7R-l2tAJx?5mrJc_nQGp?Rw z*?BbU7+l|wK3f)@i+OwXItrO|y1X-mgI~SiN*m4w!7mbOn&~_04JdD2ot^W1GrZ}a zu1;m6o4kGjS2W+`>yQaJ#_1Te-w=h^(tnJ_W=Z7)dV_v8SvbZkrnt@NAN7+|0PGgR>J zRa)S0B4|62QWBV}qOng}sOC1$XMOtKrY*%grS527*E(&N_WJhG?%0n`?)V&44B4Ec zkOY=R;hZ>7US(D_hW=Mo!CoH=)6!P!-q(qs6fO@-d;`E?8XGO7ICfvnzMA(S1XrVs zGexg{o^w#h`JHSTsN~0*#bqgYa_^l6ZS1jkAfA^JTK=+Muvz)AY<0e39|f7;3Bg4z zSgzWw*gPr1`%XA!>gM=I5Q2?`fl7Fu8NUF+9X& z$09e>*z{5zHM#J7t3)=|)%W~`^l{gq7^1uT*&+KSk-dYXNBVp)KA}b%NrnNj$puPr zfPPg&JS7uEZUr`%4?111ohK+94c5-oqqKH4ejn-@ir$O-&i#WMYvS(2)xPtWnSmZj z9_(?-iG>{pchr2DZ`8-SQ@&F^^6Blxhd!`Jc9MP@z?HW-I|0qDF|25wBqNJ3uLoPBDs@#IthdGhTxK3MdhN>nms1nbx%`I z)9#SmLH$6GT^oy{+g9T;<$am7>DAA+42CFs-CrFvi8dL}rt>@7mzWouvDL$NLo~jE z!+cv5n<4yWq%~Wg(+Ufx{K-G++#w0tpjQjmluC^e4(?BistJW5N4~IQBHCvHScllG z(Xbk`IdQFZV*}A_zzt+2k~69BTTk+y!gtJP0O5GP%(J*L&lQf*RtDU(5WMUBM5xKRn@oQ5I>{<>3f;wL+Vt--a7t|Ey@#J?JTeSI6i+ zE)-5ZGL^~Kij>MwT3kzv*_xi&M6&iOKpt(5CXbmI7(v^ZX}hGM@lLC=S67c~<wR z3;XmdiRrZIEm`;XWAas&Fo3Xe#tp)$Km?!;FvyqUSv_@*DG_;(UxhjeuPi@FlW4p& zFZ_7kd->>U^o30?Q9BzVU@O(F1PU6W7}$JPoISx+6>L5RpZD_{>Y8dZU_UXwIQXQ< zkR*fO{k#ikG(IKofv~8X5;}V#`q=uzYtP(kXLkrVtY1L;hjzzV4><*#N|Yd41!^1X zORIQ*g9vRG?3XXk)7)CE>>_QmVRS0*ooyy_XYG36!81CZ?JvPS#Hc6O*BR%6)5kjk zCyC8gngAP|?j#N+rRL;3^p0 zeG1!_*N6bN5MVdBV3TT<2inIPhZIK_3Va9OAA2xj1ea)Bwkw=KgYQ(8Xb?@H-I)*| zm<%Ue$Q+8g=(<7&q>#KPt+vkI8UHwJ+{-HPHV|kuPxqhk6u^h6Ge=B&@&~LCxSfYW z$62%a=5;nl;~7*bO`uCSQc%tz0)ruW9mnr1PObOP|01-1?8dISlxTQ)LWnmenH(qV z+OH&j10QCMlm)y9Su#f4z}N5_J`Aj-gg5X_tk*J(VNcx|jrMFE>X>+I+ZS~^Em|iS zB4k5U7+0DwnFMkQt`Z+-M|+n6nddFvk{(x1se4-HKG@L!_7@T zuL8}-GTugjUl$U>rnsIUJ{v@MzG!||{ykEk^^oCb5y2Uow^rG#-RoR}TwQmdF&Ond z0{Ah3TG2+C8jTZlGEeSo)EdyMV1g1+1y|}?cCmqRb0d zWT!=M^Hx|yCzU8p74Zb1TWayfS}&s#sDI}s%a<5s3$#iBAadM*_y&`xHUay?x&99(S6l3bO8LKWy?ds&$l%}|*Zs>=!tPfR@S+Jg5;Eat zG1L05jW<{~@J_5z-5uN zl4v&G*UWp`;I)Zbuv@huup~NG8D^TkHEmpW6SRYvs`CWX1X7xRyi{Ei=8_D=V-qB?EA=`M{>=kXt3l@{H|2C?X+tN8s(mn8;jI zkhK_7d#0QR=hnQ7>6qab7`H%wIEwIuYuD0zfZqIJqR!8Hv~Mktfw1=fKgGqHn$QX7 zKT!6^gHm z;7T_dIA6Vv2dQK;$+@w2z7|ZZc(`@_*uobH>B}eFFR@2_V>kEA(%w%huiN#OX=0q!g`O~1*K5wv zHNuv7CUlyw_R;izvdcauyU+kYLCXJ4odN(xXQ7zusIcGIQ4th5cBQJKL`(fnPo-$= znR7uQj%%e5j55%2!23^zu}!i}kixwIY^dQgNd5u|9N!QFz_^XjrW{1Ig3+TT$8xw! zV;_MaygU1`=d40!4c-FenBEv`+_ti-5z#n~t1|)^+MC*`EUU!L{a68R@vgkoU=kp8!Eum7YJEh<8pk_ablv)o7NU54b%zU$X*q;Fslw^IwKJlqr$_G3@fPXX>f^1v;R%rH>(J z9lo}H@_k~9gbYz(@b}TLvGa{SmOKKp(Ii#Wd%J9Mh~!Xz{LCtzO)v^8?PM!DJU8VV-#k? zO*{CpopAl>S_qy^Z>d1@=9?;6j!1K$$v1rKf2D|w^bZ1SHEbK}?ln{D)R)C@P+;!Z zyNeu!-LvZz!dz{svd3xY(~&daG@y?qVyTn!qKd7#c(6%#zPEdrWd6VGi3h3~1l(2C z&D>Z1wtBv8C3os^gO@uvP}e~6-)F=aB8f3%=a1T?O1uvsbB2NEiu?u{(E-eC#=;s& zoyCv8&;LR_Ik>k-ba|%>C`?J_W1DbJc%F>a|4ri~?IQ9+MCmh-V^r{k@i5NmNS6LM z`v&8w|2f(oKjO@dv|f8yb@${sUrC8OuV8&d#(HV~~NhgqG(AhPj)%@K!* zXCTG@L!}zkL%ax+pERZKH*bT<#;Yw12>If0rq4+J)ZtLz9pO93HAaVAL;fUcV@!tz z$`2#5ns1VX)>olTr#O^0FlXfV>8&$PKv^)1 z`868E8(veiTIwqs`;=-+u^txkpIG}K%bMKki!g`o{0D>Blb!#8iH;Nchpg~r!%>ZV zMPPF{Pd(c8PceNW`=@IJi7t=0;l`iVz|SZHVymYq5`1lgAC8%l`sKo3uh@Wh*<&-@ zY?2B!m<$ebtt>`u`3e#|EqcRn6brOMMsVx|r`zY}d#6ZD;pj=oX=zK54(6gi;C6<+>Ulkw*ulNv;+RHa_yi%3ya^QXc{1UwB9 zS@@}0p-84iFGs9FiB0jfF5P=Ub!KUvS*i}+L6)Cy#_7M!j&*jA%(S*o4FD?$zBcM< zA*x{}`DzWVA_bb6H$UfIsF%pa7Rob@@pcTc&VEq~EBnDqKUf+lEBvCXCT@sIXfiKF zk-_5#RE@mN{|uceH@_CkuI1X>jUJ57SDCvIT=aMRIXmMR5^L7Yw)zqpG3~;FOoX~# z_p(KmeMDigwr0v{JH9XwVIC3s^)pp+sH5>}o`*-VR@cu5zV*bwIiga-I=DV&J_uB0(HC1%*0@bU2Koq%@_TB|2y!xNPv?&S*E-K- z5r3$%$r8D!k<3UxHqS6v->zH;klTaL2Rk)i)wsEpIef{`F^`n`2Vt!d_%5{8TnAxT zf$QZ~DOVa3U9FubD&(h{uT>RQ9w%R+Q}FJ6;JZq>6w`dIGKau-%=Qcnx{o!n4XOV}y2|S+abE&p&bHB}pIQ(C{piFQ8D@RC8u_ zdG<0jo<7wT*9 z_kQ#B`zhJ54yFt0^uc%Tm8XMrxVH|iQ^aKwMseuZ8!lIy(<*ez8m&@ajQWFpqh{GC zwjufoO-Fxoe@`?rkLh@QcdtQOr+9;yX(G(SDR_|X*>aBh0zNfY0NT4pkK9%;BfYE< z_f~C7g0Wd_`y1R_w{WdfxW6|kbn+z_U&uc=HIEVMt(0hgvpMsf8y%2p^-M49O;bPO9amQyp_Eq;D z`y=~hE9Ya&eY?dj)A!=GZNSDQvCTUwqs3u>=2gAy}@qBUj%o2JTWzb|H9Zo z2)mZxcT5;!QWm0>{qCx>k_|@l-@(;q&KcsQ>q3Awf)4SXq3PB7ddyLs%0hKkJtKHBLVt&nDE{`MIf?Lv`%3JACK zf6Tn+4{k2jqMvTz3(_y6)=t&AderfH0Y5!M z(3V`U#~U5+bH^b?g1CKyVU5~0;?YO`5_6+wQ$uu}`oa*qHOx@~u=rPKj5sfLduurUQWS zOrUamO8R`?p&FyK93D{uz{3&=B*w5l>MUSR1R~sDRPwuNG2kxrCyaxFY+@hW_SKn( zK*3{SQ2M?KjlWH`$j1yNtkRIIsgw=(7U&j1@bqO#mN*q-J8(Dl|~QMK>d@b58D zKu|$iK#`E{E-j`-6wPECv-4NVt2U+z zZ6ids!Xc4S16}XI6TIbYMFC;OYyj{~?R)X$uvVy0O0#m5w_M954rgT(m=%!jO+5wC zZIFn#U!YqT^cLp-zDBWLh9b)M9|V|ei_!?^jIa6e8r{e(yip0u^d^^V174batlQIznE|$G zP9LGK#B>=Ic|k9v;R;`&uQ>-rS|%9>N~Mx2H0#?q*i_Za``U$#B?`yJp|8M2Bg5>n z`VH^P8Qab$8n6Pf#|;V$BZCYq@|(z+jo`mRKtJ~*5^bwm%!~`HIOChdoxTdMA*<|uHF>8AP!FKw(Po;VKPt$Aks3zOw@T`p;gVY>m%f8ZFv zAciI!y-1RNAfNxoy8O~C*MkhurTW2ZFMElwrfLqVFI`T4Igcx*s9o6;1dOtat9o}> zPmXrW*I(mrs}`C^d22GdyM0d2a_ed^yoEPTs8JPZUxw3Oe0Rn{BrV&HkVP*&*M^d2 z=u4pm5tBx2fhEbtmIVQ;Zr6k+ZF2rP%9K1OhSf0pOG!#reD@qgH`gH2xluCOzCbHj zULcrK01IGcV-2dv)GmWi?*<2qEG*6}<;9ZCCmro1 zq@N|HDJQ8RE|;27O@ofBsGrxX>-s0oUtyBd-YqO~E&#`nd=7|@Vc~!Uy#_C~s_SB; zcK_T6y@8O7e4X~*K;u;QYMbum_ODBO*dM6O1@I8%%iZ%nd0zAF>dsL#b2Q%0 zq59&yBlO1}Q9 zGIY=zZUok$@18IIR6vxg?-^I>+fFA`!B6Tt+b)=2jHh6G5l+*o?=Tidv_qDB1UAZ`X z-x^@(Q4i0=O9^p(24%uM_u*q`DOmZEtDSi9`NZ7=5eM?kBX6Rf{c79XI&ZqIacuec zM490ug%P5X`<2_4)Ti@F^tXr#UM-*S>6(x-l*p(2KM3x`?&KA~gAwY+UlfYz!Z;*d)txmG}Nd>R%bTg^e_F zJc&zu10@=gt-{N|G-6TE&&I-ZeFUkUL=biQZlvg8qdZTwe6{te*Fz8U(O(|U!68oY zlMYhxZ^Ny8R~hO>(8)UOy9c1BBXr>q(G;<*{w2E&NB#XH*heqJEM=b29y%jL&=$cF zK&>FZ#8}INrZ>lgztaQ1#Q*DkL5J?{EZX#PggL?g>523DHG?MCnkF1S+K-a}Hv70vA#YOX?D|)-r}Vwa`RQm&Vf+m`6|a$w51?2k0EYLyyOX2=sSAqY7At`& zj4epe!NH}k#Cpa?`#Yx>o_Fz$F)cL#$7Kh{uYN&DmI-%n;R$t9?J~|Bd2iBt)#upq z9(EJ!hh)s(PXbq-HP8=xh4u)R$w<114fOl2_$7ve?=We+I~i{>x}A{ev7!*pd5B$L zy+>CfHPOGj%%VkNmz@%uXHB4-oS|4kT=ik5FQIUCGdEePL?^WRBV2`RxexD%_UZ>f z$WZMcb+46#y|=%j^i2_+&s4%)8TK-|#@Eg))ISv|cE-+`bG&KcySj?d+KfslRn|<~ zpxYIAWaIAPes6NDQ~Yvm#mC#Bcx3i5!Kz}3z35X*FCVrnLrrQ;Vd!9+$1e5B!9lr2 zf^p<*jopDJ(7qSL;1kjz#zRO&@MlbS;n{{F2Dddoy^BJu#C83)m_~c#Y|>e{nMPF` zbAypxFq)usa^^1Aj0K^{&B;Fd*4=<~YWu@OcJj?-Fkj?=hG!kg zOv#pEiBgq{O1sGHcex4;#%6DCM5a zh4NJ3Y4mNc^l2TsZ7H%=ip$I{;?&$P!>>GaIdb2BOMQw@qI=KqVN&~S^XjG!O~l}` z^C8RESVs4gmod_O8H7Odu2fn1k}%ya?@h9lEitI(p+MPl#VV&t>o}S7!KOR2mtsxe zx0ks6JjrilYIbgADdDk=QCpi#3dM+3z|uu8~J?huzxY zPf_b9pVy-c1rZhnaEd7t!gUy2T_nD+y0dEb^U3Aor%$f_WH*S**#bh*=Fh#I;AozpkOV)wF8pu&@SHQO*}LQNna6R2&km}wX1j=Tt+yGzDh#KEPRi|> zWa;*@sWI04kK+3)n6*{lJduK@D*uYp(!F-WYJZ86jbGJ@`d)w|i7OQVzJ3U(#u1C^ z<$I}#LX+B$_TP1rTn3vagTqOR(r<(FHRj8h_ifkJu(skmDi8aM zMK^sOh9#4WU(IkGbj6cBygv1xFYst>X1#yj0BpAI9%i+_6+YzmSpiBET^#|YlzGH# z?pE{)%*fwXpFGbMK3wd+YZa@L8q5B3!<&mveeVZ;Mc1@Poe-7k73!XGUR7S4vup!v zLT&jI__4i4`9OxlRFz%WunoA27db%-I=r7&EhW0u0QB#4z^%2`u~>nDD0g)iH`MJE zUm(;;3TD(dTA8YOCrV>YuQ58i7g`9Nb&%;OjuUT>rolJvv56EX`a~RrHZ{Oo6fH6_V`{5Tge%O?))b*~8y^uZQUjZZRKF zzr&P_Wf~8&N#;c0N$}KRwg}hhOziQepmp7PpQ74lnwJpI45O4qURU+UwHOiLZXvu|BXnzzoB7UgDhUzhp|?@z-b_fA@>gJ9X5_2vj={2s zPQHA(`14Q1o0c zUL;V*1>7TX?BzhTdDY~K+O8tF*LQ45_AC9dqO}#)9 zzMS;G>h5A>nn%(#vhiTf$o|*M4%4rF51oGC$+V z(h_F=7;6RL_Y=)N**PO!+%lEd6-@jxrs}@q^1dr!!DlY*u0b8n@a?^VQ&TeA5H^It zF4YX|2q^J?azHjN@sclzRZK+Gw`eL941%!nZcOd<^Wk_GwZtb!j{;YCsm12)y z#=Z^*nkxpi?bWY^x?|H!CgZ^pd0r-yxneSTUS~p#t6Ow3-%d&srRg^g`Sn9A^22lm z&i&>vdH-G_sT}*oJL_0y>_vmCI{@E1f{)4@(RJ2P3`V4fvVXDiWkPbWDQi9#9}8{j zZij=11n70K=ZCxDmp?G-o*gJ%EV0iCclzFJc5DMmt@duY4S;~q-n)t2uFyt?6F`le$A{VfyVh1LE*7jn z4K<@Obg<8-nXnQt1z8U!rQ|%a#6-?#I+)Ho>1#)<-re7wij|Y57W-~?wHc8YT+lw0xIF+ct)cOTlEs;T1a@7<6t<2Xf=pA#ZtT<=|LHK;gern$<$6Sd0odzJGos>1glbhd~KM%SmL zi+@aYc4@OcH~zV7#Ar$VW18#@J18bUFIJ>9Z|OG5JT%r7$Yu%(9z}7w|I+Qo0BgCr zih_9`Sq<+F&OIB?Jnr<~ds|>WYPo-mT*RpulP@O3saEf1i#u?SW@L+TJAPcytx<)a zc=TX4NM+8z7Z39?R!IlWBwIt-zUTM(h~Pg!#`pApF5ZdyL^{-$ z(kNG`6?=LId&1uK$^lL$1jB7;`IFjHVqP%Y|7^qVb2)R{`7a&z$nM8OkyecPbZJmYFgG%E(3_+qzk6R2|7QVj62FfWLw z3B`|X+yin=!SDP&yt5?db>o*vuNdWG(U;ASoSrX6s@9HLnhqU}$a(Cm43!YYzvTan zUz#@=#+N?H6+WG}G|o+cZew~4;~BA&dhamCmB?Qd0GCS;ds=tPudg;3q0#%Gy5RA! z+`}pYsM46`;1AXILw>xS0#5**d;s#Uy0xR6N!Mc+On3xp7#0NcRGlZ)Nr=i1?%*e= zfyyH-NUa!>s%Kk?O^OpIo;`X~67LmYnN23-q?nJ7*9d=f3KO$@Dkd3g+Nw`zGz91rjvOFCx#6OY|9zdb?x9=#Lr=<=cG z)xM92Kb+ETW&d*d!mlA}XtOGP3*d3;o#+fd?-16+) zR;}s*<3Fx1d*4%p)M0s@pLyS{4;GT<-n%(1A$yoBez{m<6Qx!76&h#z#BS3lxE`Cm zvx)rVd{(@pk?c^XTsu>_0aWz5QZrkz)=+L^`BPIPB=+YpGTcvecQr^T*r45ZwNo?T zVzD_~qd(wEFVii+vg{b4FD>L@MY4rV)co$@18Ka5d3j;+udkC|pKC@AN8Fw6Xu7=Y z9n`0vB(Y2a8@f8EhfI&RZ?0{w>BeX48-bH-6K%b0^h3wnS4k2W42|`o&0YLgf%rP> z0wq8ZpazOnO2sSak7JQdU<+zr0b;Xn%gR{Fn7Va>y`7DlP@O;DOBv=a7Q|d zG;)rxj-aM;&5~*6q99`Rf^#qCZeOIdh_s44bQEofYi|>(#A6=A-Tvub^vF>H=f<() z=3;;E?oDIq-lGeZvOUs^fil%+EtD5LrK)!?T&EuF#I}fW?!<<>a+*Ok&tlV5CR>wyeHt}5Bsl`Cq??_6MmK`AC7S5n-{ zCG34%&Hg>p9sI;Aq*v#f#f$xCURX72tJ@1{hTlD`R5Etwr=^8XEQ#zE()BaZCDe+% z)P1L@kDZ#(I6I>4Ctwv%(cOMMDwy&T@c5s|M+5}B#G3@DC)&NkX9%sIj-(qjV+zk>(OC*CJP+YE7FP6`0Ne;YU4 z_HlrEIu>39Cc6YaZ2a-}E2Z1072YxxhRK^KP~$(>jS@=nhS@IRpiJ$aOuKI&@hhPl z`f^3M9`w#MFM9u^=>>dcdzJhPXyH=ud_Mog_=fkO^;4Ab73+yMW@_t=>x=?6F@Vbm2IRrf2d`Nz&v ze)B_hax}8qa+8bSrI2#pUOCsxpv`zJPonkD*1RfaXZ!n=2NzMGPJSxC!~XOvWYk=p$4Rl@EWU$K z_3qeKMK3#BW9UwjhuyA&d4gqu3|YBdPi(hpG^jj+OS!AKQ5Bw)bI)alSIZj@$u7wu z!Das){MZz4q9{Ydj)TFdf22W7wf2i4mvVVT7pz+~PecM@^VKGv1{klG=7C#Rb-HyB z1|2LGuG*ZcehoiH%_DAF^G|?%Kkl!M_!<9Qs_IiyzUqe%6Ts6|-NneX=wGp8AdG6D za+pjY8mGC=6vHErM`)KvxrDo$aML8&h|IA8v_G|z`w7+;cec$BU1M2ViUmFH!^e8Z znE=s>CLcYWrkiIOms~cfS^l_G2@ODSG>PSrtdZ!K1fI8Bs$I2^7n$6UB&j!%H-Syi zJh+;6Z!FPTxEEvMdBn$~+o~{CIBzK-+YX;d1j9JZeE3|_(Iwofi?s>|?c4$beyc{Q zyl)g1iy}12oPW_Ed3EasH8JpE&qid^QeUQ0F~#h?U&#WsHL1+syTp*@Fy7r*ODpU& z?3~A150f(jAEKqD|9-`$t5{|;V3ux}evNRW#%J&NLvmEHc->lGD6V4hH*+#8iq&Uu z%Y{_8s8YqMm{PkX+&;Dg)Mr*rQ0Q8ao_ zJzYevPA36E&d)Hz$ZVva%``E+o}9{#w^ zk{z0w zuTbs6FNRZ?sj!CA?epbw74puXJR%P6pWyF5oT}OW5&3$rqv7f}YM(*{CX6`V*#bB5 z4{=2NUH|OA>;Lxe`v1Mfu+M+LvOaKJZZ}Ak{*zUfFGbJCd-W_Qfdnk;Zn6#UVzc)n zna+_iSZIfIJ_K$+ZHrf}=@-8zdT!$9B*#)e_@xn1X4={j%D^%Ce)^{_nu4cQq?@ zUmD*pr`a<&j90DVS5rOVyu7KH9iZ=5)X`#*{p_-kMOuG#S&>y)-7wsJc%%FWW z0`fq&hj9;~dEjrxqG<94{B88eIek8A*TVLZsdZ9uStHKzN3D5w6eY*pgNgZ9=KOg1hJ`{snu{TE82B(C>kob+;ZZbIa372l)@P+@9v$ zBQ{B}WQ3473Hw{Cz(aJ{{V!Vqf0Nf8%DJ1i#)a4Xq+3W z`+(thS(@15%@t*6+X1z@7PC_g&%fGLoF2g0tLN{8o_5RQqXd3)@>5AEzq5n*<@yx= zGFa79up4`hpzV(|zC6Qpy{0_yf$MvUKc1N&2xBrA0}{YJzWsZT@6~S|R~LIv%e4Ie z{Iod_t5hh0;obQx^#EE2(aIUn@RhR%a*$r3?b2UFD5lp8;!Od2uD6YJ%c`& zU^XfW{1#j=o&cCSv3rYlfzT5Mqc(~B9e&|{Q&2~Vbnd30Jf`mH)#QWCYvB{U`}z&|f45fP_j0ALVDIkG{l7nwQ%B95s9OVP1PmkX z1Jn0s#ppP>bTe~MYRW=phs2LT}*58}OE ztp1X<_1Zn+T=k3cinrKJx>s-*S`bLl&bjQq+nR{ir2&_Dr0sZb7Gd#8*7YMq1j@bC6Y{GsW|ar~>`PkwvaFK_-v_d8H2_^Cm# zkl>C3h>8Wcq_$iOpZJ7uPDMIZKkd~TRRRJ-MVPN}TKZ_Q`rd^Q%A31del2|?_)K~t zOkkfp#rRbDEWnafpUYWveAAF`t0~ZxM)sQNrVTncA9(Xs|5Wr8v+{^$=s4}6rM)qO zQ5N#yZjc?4wU5=_0zfjj#^aYsJKy+(pq?_$vCc5=_M3+INSQjW6lxY}S7%-Gy?X!T zui_W|R{ZyOIR8pQ@3(O7Vw?U|w_oQ`Ajsm4S%TxE*sA?n&7DG*UN+WxnFg5Mh6)@R zv1?eu716vUo#Ki2TZL)?6}lpmVR1NeBwZvGEa&qB2+GPqswr?P@Zr}UEz%9qcEeQ`2|B4Rp>AR3<-XEBb*rzgSl``Y{kyo|=M1{; zVjKLsxIeZ)P?=Ax&fRwHEcrfZ7=6ZH6_zKO>C%aaw_g&QBbY`x1_rp)3r)C#;0Q@OBAAAAIFDi$$XX=* zr=?ADwy6X3D}EU*MOY=@XKRwyIV?fG*Wyho&)W=ZN_9B9PFyf$8rrzT1*R%X*pqbjsM_&L%I=lslG=0l1Jq*5_CNZKGq`QtZjg|cd8jfzTDjtCM|rhbx{d7K7x zgNSLa-}2?Sc_OJ*f=#VVl|qPTFAtsJ_LDg4X=j6M-w)XarYR_bINK!ik$%4q;m))( zJ>yv*d%@7Eh;kcZvF4DvC6ooZdU2#vVVsCTy!^W+i8MXg0{Y~|M zmz_4{`O-}I7&|JzqX`XO7-2^$fXP$MkRG3zHg#=Z? zfT`_QZRU%+g*wD`a9QA~7Zh+A-Y$@_rziCFWr{G{uxz!FVOvg=f1c*blU`G>@u_RM z=SANI{{Foht8bzgkd=G=BjZ*d{EAr$)X&dLHXk|&UbyENTwf!st-@XE&ro&1cro|B zu7~9jl*VDM@|6)jFujXb$C>s+$0UonU7)s=7`oB^8;Ks#{`}=92e61JL=T2?C*bgl)4-Q9qd&a3{u8&{G^zR~c7&WOqK8vLIJ9)jk z3B|P~l9VK}CDC~{Nu{b>HUp=qJMOGGKzB^f;?qqfSlh(ixftuESS=-UAjnO8YB;Q7 zsFGVCMezYezDm_>es<^L-kq}$?_i%&GR^_)V|l)UcrwfB4lk_W`m=Lsp_cjjU{Une zqfr)%(q+m*t!x7&?rv{wW<~yFi#z8Q6g3Kk+5(+l(6+H0yterrooP zkmqZ<8e%OnLL504n*`7FVpGe8`*EYoAd3=vX2~kB0~-TbX6 z?@GKU`W>Hu&KtG?TW6}NLkzJoZ(pjPF2eU<6q{X_j@-)btFres+Bm@f(7b1}3Y-yE zJY2UYL$XObwI}cr<9G$ z&oYcpHo+3{VEb+_13D9D++$2!ItV(>y`o*gbHFr=f1;2bwiBo5TAX2YJ61b0M=b#Y z`J$V#MmotxwIRDqH)9h|yk(HX)8ieiP+3N{wW?1~Y%|`IW&)ylyz2@HNi>1v6uKyV z)caE6Rj;HOp^=&D6XedR54+=2BL|C78TP$WV~0K|H}JxGj*KsG#xt*K5w#b*N`Jd= zt;~f!c)UB)Zsh$vT}D3nsj_vOCjR z<|jZo?dtAUu=X+fn{uPXy3Tz1G@IC2f!BHDZS3Ql^Zj{iA&+6+lA%$%I)Ls_a+7rA zyLKf#xkIpO%%lrV7;LorTCozU9HUv}a|eK%Z*Km?eZc$tCi!B#lEnK7pJAPD-DSJ4 zR4VKvXXrfFL-TD=V6l39!J(sy&k>562Bgfzzb;$v6~tcqeDas2edG0Kyxqt2`h>>6 ztUcDG0CB%J3Uf0pCkE^0$6AlFfjzcY3A^Urb|v!w=P=texj2S5V(JjhhPXw~JYBCM(U?}G8>)?HkW6*s z3e*2SN%#I}gz0PycB<8T?bvQcP@w(Q#`EJByjYtSyk4Fv)gO4<#4;g?=FeMOM6!%1 zEDY(G$GDWUj4noTT|&0B203sG0&oWTU-s~hR!YLO%hmaMc>4&wEMkHiwun0r<*Hpg z?T8o~8lP24job{KK>5iV%x|gAhfXp{*t&gwvQhSJq8rBzeE|5ZDvYO#fhDpaZbcWH zqVAtGjLpubLE%~}j$?Aj0dz!@xw5~S zMTW_Rlm*%sBXpA-sNu_GW;T1FH4@L^^O;PM4>YL@{`uOzyp7ridnK4I8rAINvdz(U$t(w%-a${2T-$dkZa!pT~wv_K`oSnYhM5dfSO6oQ^E%`e!HIJHtvLf7eKf`l`4D=M9<{3NptKgV0=`8Vpm z)MvMMXcf-Za4W4@@?o?58B(D2yj7&dsWsc+i%IoUbG?S!mU`h80q+d6z3OEWwQ#3P zIR@`shfy4BRgKti)chi@AV! zjydY>N}K}GB2igk^B~M~kYh>5H>c3!es#EnPA{{TxN&8L6!S>Gm_%K@eo~RnU_V~D z!PJ>v0E5wUGpi8*5RZ_5a&!PP0+H)jypg{Y+hj@y=e~#*} zOuGfq?m|*3@dMQW2CBg79%#M+X) z!A>x|I}g+-@y~U$7>i|)uzM>gqqs@=ff9Sd2I)EuZ@12}ebbKz+~wdgKX5^O^FnAI zo8=cg++Uq|nuUxb&3G1Ol$%};c3Hj-&>o^{hj88V4 z>|X=*f_dxxk}t~7kxyrDA%yNOLWFe17ubdwzV&x;Fzc=lTZ^EbTa7+?&Ou#WR^kk4 zceSGp$_>aY{R2CuV_-x8oq%n4%nf|779vUM z9xK+8+Wz1e``4aV3*B6*sYP^0mWdr)nX7FE=$oh)Q!O4kM*7S@OE3$9p=?Q_E`OOT zgpThbdfB&#i-m*jliUV5cZaH#M%QC$4XqYHF7#~|TT2G*>U&jTK#|!rO4}MiqG$X*`5lLr8$X7RtnxXyp&jMkt^ff}Zn)vowA>7Wad zR^TkCO9#_x<3z;Wbsfs-au@Lr4skK|2&*W!gG$*KVmbvz2E-dR2Myk5x&h*eF_wXK ze9QkF^Rct}_00-;knY?+F+40CpO>EeH9jBpVvMOM_5@X5StQfVbQiYU!^1fhTz`NT zYo;@>B|@meOAiYhk0g^x1!WuvwpPs%&Umx!ygf!)OHY}#eHw?Efnn-3OZt6{koM&; zvW+#L1x2_m(Rfd-iGN^M=`B(7Z@|fGAPq^BsrSlG_V~mqTkT#yh)dW`}(mV4D~WpN`AyIQxa^(L%_M} z?Cc{sdSMMrL$ASScbCB0%r8pL4Up~Gt-8EeKFng1rR%fROxnv&L0LRKGwm*T-L?e@ zX4?&IYch^irH5rm1|wUdxI(e!19WixgK8y0gV`#D0`>Yz`vtab^QzFO7gQ>;c5o43 zg39CF2gfPGW=QAFs?f|-ZmKD|-GxNUee+PgkKSiJhk^bH@!AF9r}^rZsSQ@DHefsxY;Qx`ifyIfhekGft zPIY5o##i%fNE)>15=B8??A}0Q`N~+m8hgo*%J4mI0+jo1ja>3U_Uj^}t4oEZ9~KE( zg<_E}M}HX2`to$tshwQq@1^NyKZe5b_fNNqwR$g;0pC-%wmn+sAGk6OqUskAZf*q4 z2~CSN3x+fw-#@`Y=soo|jCfEm3p5C9_Ub!ldm~QW^GHI|A7Y?_yr+xP0T- z4wrpIQEy)L-j8L(oWhPsu$pR7NP51-yviE_c~z=*=ICuxzf*y8_?;u$ zejFX(%U9Wd$#QXsX%WG7g9mqeqmk>YQ~0|&zGc-CR8EJ8Jd<>T8HcaZAvVAu41zp; zXS~!*UWK}lHk)`U!wS~|%OvR_;YEWi`!e@X?K1Z)78A`NpCsu5%Zf@V3a4A~c6wQ3 zj7*cSQr>A7+Q!qAkXzgqrx1I`);WT zYPh-&#TO4AszF~4AKXtU8RRrA9iCz9oZva!A5-kTyj>LAbb|mrg`s*Tv#RC=J!ogr zlmrqFqk1_8KLh$aDW!SX|c@KTiwTg zM$yHlNBW=mk5%j`@Kz{AI=Y6X1Zd`NqHB#>UrzSRMmTpg*~T~WM)h~`_z9Zy8GaDK zN^Yq4@uxGG$J_7F@sMpAWuEf9oXYj}tE{Kk_*k;b*F|O}TUQ;HZ1E-!)aFH`&u&V1 z+jyAnh-vhZBYz(nTcyJKt9B9UOs0gMYSFUHHIa8ut2 zcHTMd6kH>XR{gb@C+motGYV5B*1>OW7+#| z)&gK~WDkdCilxa7X6Jp!o;mi-CJ=HdtHR*CCHmf%%vmY@vROusgMOkaxi;6zHC@XT z0l-u0D!`4Ju1D~0RjUP}8^uD4CEPfsh;l)X@QMU*^(w%%=DGY|?d{yDDzxcJt;o;C zKLlWVB=XvK9IV{s&lP`qqfqog7OJy0Qo<@!-cIK|KuNyW-o_H!YRI25G-&+xL$^@H z+I63b`l_w3+e!D6O2D3ibUe&bcL#u{b`^oVr^TOjukC9ApPL55c5{;zt2~3&x0xqG zJWm=IkuHR{CwqCge~9-6$!fw}!^52EwxyBlc!y6fVVgh3I%ttH?s(n`^uZSquI3 z>jac*XWOc6TaX;4>UzVhY~6PV2gV;w;)q|$5BfE*gpnBv(uKiAB-Pc==au}j5_GWr znH!};Zw&YSSW%hn;m?dKzYR~06x)3@XSUSa8)X?Dz^bE3HMiU0>E+UfO63-S8UTi% zK*zNBf9fWfKHDEW{NyyiSFB+}l3+8!RHoFf`Xkhpd__t)&NiRn%{L&17pq^f@7a=M zt%AyR+X`jMj)ACV7pJyRO*bFTYE~;9GhV3i+2c(qui}1x#Pt{sx&^tBtE-k3G~pfx z-LINA4iKvDrt_Xa4DZ=~9qI43gNrp=JDjv(RaPh4c)$6`Dx0Mww=rCq7u!B-h4lFJ z@Bq30lVS?MM~DONJZL5q;>6<3#e z$!+t={4{U$T-&p4?Yw8kj&THAaU~0kh1yHR@)Nkmvp+}f`7g1S|5yF^O3CywO!nmftx34 z6;{j3@tZNc*?pV=mgftuN2vaITe4x4{_{G)AG}}5EeTHq~+~j3DII+#?45ZFGrhnkdk}R|;z~9BgeZdaSr~^pm zTFKtQqZuzTmp+jKS5Il3^yVEdGt*d5&=`}B(NvXV4Fr(?|Kzcc^;5u4mv&NkK_?alin7U3~Wq*becUHdusZQ9F};V1R7 zQ?z7jh1zYx_Ttv@`|}kKUu+tU_EFv&C}JGlsH+!V$2XXdD*>#9TJ{kXZc16P3lpVW zOSGUF?s$_m4ywWt(yjP9#N3fs|2c5eB7t-fT*Nw(qx-|0&M+38xqH|%c@n@3MqIko zEER;21({fLevo|~MmClM>MM0dH>y<3(e#h<_GB9v`~_V6KI{-aICx?wUB9&&ZC9&| zKA-`GR$+M^V>e2u7boTt*}`HpSjjEcHX+e|sqf;T%V2a>1I6CEc#K3$F+PqI@SkVi zTOn-vFmpS4#gbRqP(9(6dA70>Apj!^=gx=+|C1U9L!I9NQUv1woI@iY?+MYeNOHQ$ z>IIayVp=NViP+;;9yIr_6*#KX0U`Ff}PIglnDAJPNLlm z1VC!XdmiSI3^r769L;d3roUMyJ#?i)4Ww8ng!NJ@6QWYQ0{Y2_31Pbsu}~ck){&vD z-SB(XZn}j)!NsSj>T43eNaXE~Zt@>vi+tOu<3V8|4UAF5M%=nudOFI&ff%9>aa61K ziVfGm(=T~YVQI5!dl7_mJYpIl1GZ_)RK{2lZq1o>GQwu5m#@2!r_VzbrBj?{(qVae zXO?bjeYCD+-VNuj_tV)&j&rZT-tWD$TZL-~nLcX{xqa#+|9uC@8`~WH zHbJcO$F|*kq3(P=nN{=7=n5!8vY(5-@$`_QvYL5(|LfzUyHy7J*)`D{~CDF^c z@^H8^tTzj^+qXCz51M(~!XVJcJh{j?14kDJI5Rh{|CK?j*gwX7&)89Xik$)! zt4AJ5aS*D_HA>UoVP>`bZQ`|&Lo^}ky&tK4#RIf+A1}EoVvglQGybuBW$)zr2t1f* z>j=kQ5cxbBvblDZ`i^Y1P2fezr&Un5PGy5)k*yj=99=a_^E790BKDDBKG$yBv%BP0 ziq$&#?E-KqpaUcSWxR!tALnSKfBM0~Gpt#WRJAZg_VKm}ce+}% zx4QY^6yquv<4Pb1P?ksK>0^~*5^1}YVrYL~xwHhY;i zabeHDaq#3sv@ud3^{N9JO3+InQA*(6+l z^=lx#QT!LA4=H-#K{?}Ye<%+(haQy#@OJYKaQE=DaJ)3~UJetFpS!#qY_B1n&*iS{ z;brDw8i$S*u8Vy;e0!XwA7v$$!D%HD=NMy`TisQy;w5-Co*ntQgrVW_UT$f-x0H|c zdC*005Hbq&c$*k_gtX??_=?W-`e@P$#gpwgs{Ueig!mrv5~tr8CX7qwigk6I$%Qfu zQ*={syS{9YI$4WJRt!a7pJvM4bc|3a;^GyvGjp(TTweA1X4&OxdUZJ21jXiu9=xyVs0j@!=riQJ9LZvXJFhwXg3pWdUaa}F_Jk$JE%M6d* z*qK|t7R6YdqZ(s%zHy^8-E77{u`Jy*!*u%eI|bKQi{jYWkt-?=}dVwYC#}}tldv~vjOBbk*ecJsOrqlUSE0f7Dic4GV{LV>lRO+V7jn%&Kpa{o?j9*-Rs1` z#y$x<`g|OUR-0OyCqydwiWZn1KpELB3=3GD`wx`4(?-5=r}ue_TnwjtbfM1IhU(gA z+3Hxwjgo~y*c;Jj>535l{mu~ztl*HlGG`rL^mWEJwsDYzhO?DwGdMnqOn7b2X|JtG zyy0-;INew&Aa{zx?)DkOe+U}7i1&%lc;p`asXqvc{vYo-6brLB@6D`IuTZX0tvwUft_0W=CWgXX_RjW@;9KsyB{XgwwC8e(qlS9PUE3p%eLIOp}b` zqu)aDa3V(2m3_p}!S+z4WLLOWo_WF@#RlMs5Kem?jns!VZv&b*9D#9|V%a23{u!sd z;h!!w4@@Sz0zQ_0{&e~HbRVZZU7uP9*RRG?9crvs8_>64wh-mRe+~1jxuaOFSg2VK z%2lp*+X}N8%oEqN*0TT38MhU;t!1li2N=M7dVpi|{NS~Lr`50niYrxPo&aHbm6^jI z54t%xS)J4|0(rI5xXgQ9(5?%dyxijmc#L1u7-pBr8Hef)IDak?e#zZpxH9^z8N=;; z#U}TK<|h0XW(R?XNn;`^L3Wq;iw0>iJc1#iuQ!uyXY_@3UwwkCzIn(+eY!#VE!UcR7oHPFROta<^et|IZ&fsN6f6n+Lz;|LX7Rzjukh{=NDsAxrV3 zGpP)xnF+Jm$ekZMIQK6Xzgx9=hzkQ}PEbh)&Oa;dzJvEVoJO}7pMsCbXG)A>A6^7w zcQly_Qi6QYC^ffwl2Xl}0Zlb`mXzt69FB#+sG7CaP6SS)_LI zEGZof+#&k^=z0&RCby<-_&pYsrXo$cg7n^{D*{UIL;?iqHFOA7jtWReYUsT~2sM-t zP!N#bYbeq?Nbm679yrhctp9tzwPdZ_vfv(O_FOZ2=GwEF$QsSVln|nekwq=re!!2% zJ8X(BFtbfc9bOz)6T5xX%i=GW{YO8)ZpcT^{~ymQqFUCmYXHVN2f2mV!Z$LH)1j0< zDgjPi20*%N{h4d@X8ryctz7ByT9j$!Ap`$;&I zEfl^GBjaZkU%?E*_*nSIF3JS3s`&$*B2`nCDmkIqO#XmW2ZYp_FLB8?AFqe?S;u}b zc`q4sOWhAC?Ltr))&4RKa0dF?&(m-09Qglu{aL)Ga^!)j`r3z08(#BcO>e63H(lN-T)T?=}; zaBco{b+xwQwqU}+G6IHMoDc;S~tG*BbHNGG75KK=3UtN+Y=1a3-arX8oBrcc8O3y)f}3}+T@6Y9*g8nwZVi{JhrnP(78 zXgML6459a#-1ijnDTVyx&L+<@WUQ+{_Mc8|x-&n+Gs)Y7BicMAxNG?A3eVe+i)d%( zcn{CZ3%@|nsiIWxcMU)cJ+KuOu97(o(8O1+A)z`>cEFJD5$;KbD%T{EQd)(}|E3?! z549z-k5v6E_h$$qa85SO=|*tT8^L4#6z;rUwo?EA65zw#=h#TA(KWLyuJp8te9y7KQU z)o)&D`R^-lR3vkwqWaXkH)a1nYl?Rf)0Yc>HlUhd(r3>6NEaf7{Q7QeU5`OHq~>l!SK&~;hA$5+ z9lSa4d6@DjJPE96=m1)0VAl7Iz|yP{hcoGK@me-ju@W)qXWe)K<;{6HpWqu6u3Q)2 zS2X`MxdBbbA+F^_mEgqYN>LCXHvi%0L9PpWyTKA)Y2+xe$bqN$dcUo}&S~j?%z92C z2h?|7TGZ4Ez1T&5^O*}hjz4!7oU{#b_X$DkTGky{9?t6~TVqm>Rr6b*O*w`F3x0}P zFWz6JJ`$Ln@Ys*FT^A6THD+t}vl)7W3atK__bTg8bKq8Ox+>ST|5CbM7hQauZX~zd z*kH);H^>p7_%Tn#fd7=8ni$9(#NB|1umxj-p zGF)k^B>euF=WN$|;STt&WNR^{c2_Y@i`-pl&bS}%t5|HOhbirv%u-r;XD6y3ra8F& zOvfGNZ&40XeCYyrg}VMXtL=L1N8)mFriK zjR_~?W2;dI&vwDR!qJd|l|`;eBsex=2j8n@J&orN-tm`LDpRqwAXdR-ne=ln}*kSnckHrId-IqD}2po~~5 zV?X0KFYe~-;(n2e$F;@J|A87{arXS2`zlM>!_fTM5@N``GdXPmhp#snBDY@*8$%SuB&Uj;T z%#F#}B01@g(&%nm3RH7!he%a+EQ@Q|ezl^yLa8ov-zf-(%~O)H3GRkz*cJ`Hdn~Y2 z*m?Pi6?L?Xi)*DbmP>v9tSv@9X{c-=*QRR+AspwX&@SwAkYxWh>SF5dc3UCH=dANm zLU+lvgLs;wVUZeUi`_OvwX5mr=pQ^#bf~Z38DYe7%;f8b$lGKDA8P2W5QHeT-MLD? z-z%{Gc#2Z{fbA&X6fJ5tuCrJCzR{Z@&QC7KS-)O0w$2nTYaVF-@mW%)?(E~FU+)l1 z!&XsAR?lWgX2%(pXtfdMaVgMT9hyb1k$H;#=k}4h*+}(-m8V$BM`MguX<8WtpkyP~ zQR=Oh+!|R>LDG_Gf}ZIiJ~KAK45rXhvqoOkE>pW6H^hGT%nCkScn2qcglFaQRflVi zC~c&Gd0DDnrHfa1fnu>KD6ST&(C845;|fMv=E$LRLyPPK4az?dg|SXvxyY44f}!~l zdX>tBc0t~nX>Tza8Ly&zO4AgJt-?KHO+jcEfAcsGe{*!DDcm#GDqPXF)G*2?;}u3D zEzmpKE=ajh&m~_q7+N+xNYQj5koy5R9cTYG)CQ%SBZtg)1qYZlmO>TcYE9vaw&4XX zXg!xQ+OSn3-NG#WWx5~fMI(1`h6*j=ch82|SL0-AtJ_z|TWZ`C63m#f2l#rX<_Sql zS_Np?3f5VOQ#VDNXA5cKp}axVUmwd6K@TWnWlzZxly_5!j3v~!tcSvs45ka@g4~U zOrczrjBvN;NQ=}@-tY9$J?W^xLzG%Vr4zzC%eLYjTnwL>8q=(!uWe}?eR9!hf=!;^ z4`!|}(Cp!xXHWu*Y)iZ=!f{LS=I>Gs=+>RwElSfyX#C}H$rLjoK~0zEz0kHFX!0qy z;D<+bsCsQ3l4jo80#*YM002(Sy2ZHLu4u#4JgyXP01qeyC&Ydyg+wYKQ?vm#U8F&1})n=U7u};;%A?^7K9hLdgLM3v&Uo?#2OD zv7jQOfRlh<0i=}g17y)BeiOMXq`mGF++EqEtISycQ&FIKH|JdT=+uwF8r>kwO`3K3>B6 ztfAda_EZVn_&YSuvjxQ}nbB_-5Aw&U0W~~}2Mt?;7vn)hsWl*`+GI%1MBx57^RrcI z;tA7F=Q*O$o0T1kE{FlW{Ar`d#ZN$C9s0x}U0~AQK*>lJ7$rZpt278jZ=X-na$RmE5jpksJ7E6l7>j$6hR5Z3? zigXmKC(!nrcVj2)cCaCHDfLZo!)Pcp7K1upamzcFt!_bA(+qNFmQs-EaNzlGSW#k6>m%>eVgoA!>l(^Ruie#afC$DXk(k}2P#zS@oE z3$Row9^w)n$fMW=@`o(pWLv^4`?%euwsBd!Pz?rgkpqpwYUQAPmSLi;QO=;>B&&P4 zW&_28cm%7V?#!!hjlbNiup~GJc>>*Fl;BXF;s7H`dhi8zvqmk~q+-nsXC0(nfO+O^ zk$4Y9())JQ5`{Bm0Mom8qo4;hMx0t*r9Acky@RleXS?*FYQLYxq6!lESd`2wgoFddia`T9rK|E!i zZ07NLfo4OE`4#PYrO3mX2(!NE4HykyPqe+K$YSdN%TYHod@|-euN53bbN*e(Y})5p z23|J_mhM5~QeQkx^pkzfq=qvJ<#qwKu-#EedV^YFny)I_cHFgM!*y9W*)i-dfb_F} zeTeJ7OV{9H$IXE-Ji$WJL}B;>g&{b_c>N;Uz1psQWS&_VZq-Ot{j0<$%5pmsOV#7& zwHPm%#D%q1tGQU&K`_=$&GDzbGd&GRKFb9w*IZRJHe4_cF?ZI#Qm>;afZ6IrUakk> zg_(~}8(EAtCYPW6{gy5RRHt2_g1}3nAI#OTdG|hci-=;0W;@14BWId}dEkwS(;Dfz zMuxl*71?osDY#ON7sDpF9}>_=%Ke>kO-KA>NsNyGeG*R4Y}dfh>?zCJJeLx>%iEl| z;MnY8Ho>V? zMjH#x@^15csQhgq(N)0_3H%o1TnZ1;Q`Qk#-sOm#%)~_oQtiE-3WmQY1X@sy`5L!1 zi~JLu`~dwh-i%$KA@yB0-TJ5fNIQGA`f|P@AlONsDb_TCHz$P~o{=T+r@4f{ zS6aoOQr2p~1=6;sRg1F_sveFy&I$DD&?odoli38bF#64ETp;Z1ZdwJVo^57jDGIOq zO-0h;M9xguZNS_vH?uwdt5X5KSMPh?FSdbB6HY0Yzc>YDc`w&UMs8=S+OiI>5-0dR zOwy|AQ1LZSoG1NY-n=P`$=usQVjeA|*&SG-?kJ*d_u8Aq_QD`438zgjdKq?ya;2m! zG?&EneORCJ3@?4xFQY~5hUKltqG*}-MXRhXB?LwbHSOSW?+Zp&;@;(X$lW<%eqj#g zS>YUls8<>_3o!FM9nIEMKuqv{)I@U0AF!pVUD~xgtUrUXjYLRliwld;TE6b3~4jbg}m!C~H71o}V#u^}JF8dOHNCzw~w_GuE^X zqC8rYtqZhaz;e2S6Tg(sNrB1a)WVVIees?5=L~<7sB*PT6;3y1EvA3c<`ScsbiZmc zYb{nNb%c$4-Gk?7F7fYg!|)|>Q_a4v4u*jj_G=+<&E2pE+mqfJXr)gFmTq<0U#}+c z_qL9Lbu3fIf05cH@?Yq-Wz%2WS9Os~`&5(1D&VqR_&`t&n?xdadQHP!-XmA}(e>77kwujr`3aUwSUN3?0Hn_r0s?hiwQMed-3Ck|$8IA`3C2)#{ zWl0XmHg%Dbc!PXC(F{`jMacH8YbGI84a6lt&n3zS5v-J?>-N<*wM>Cis(S9rbbJQ* zvwhbuloh3bqhDBDrN3C# zC$8*&m#bVfNk1sT)+IKKQ(={l`|joYdd*bcB;$mSUb03HF&@=mXesDNn1>7dw7{~7 zUCSHy!l5OKdnZX|ot49d&&ScSgA_GjOT_>!W)vesr+BO2Sg&BKxFYgNH%2t4a42M- zd`9$$FpOgLRlXVA8}@zI2FfRUdW@RFk@d!wI7l@>VN1k4P;_tZJl0Lt3o&3JWh_rZ zNj((~oxxyOoDYV`y^aJsG4T_OCr3~4^y&;Ui2fMldXCP28^pcEKG0F3(&j2vGSX7G zJ2**EDD{Kb^wvfs&D{VbQ?I9O0@23N`smn*0n)*HeQN^$c*J0nuLpsqjXWNi8ix0c z7^DoWcizX;wH$3nZUj5)6gAXaj~|HP-)k5lUH=4GZxXU)8Ov-1&BEpm*h=;pKl=&S;5Pv$_e7pklCg{u3U)W?^Y;!Ef3@3=D%R-b4RhVW zN2&6$RI2Xchr71(A~d$6V(dnXd3u|mbCQ2)mhf{pyxo5&4H0RXPl7Eb1x(jtr5U|s zZHIqt>(sxd&(J|yMt+E|))sAG{LJ_fW$MuIGe;`|vQSsyFn-Vpbh74?8PSK^t7ObW zbqz{+Br~Ya++SF?yoPVpo7Qm08I;(CZqWwP@ZK3hOlsW`Zu)UHk|rEiVqQ3vYq-BS z&~cM^ypg$S??)Uz)l1+=!PNCu_CF^`fM7Oqt9A08*P*J9qhpvO1QVs2?{ZdaUqX=~ zLRNPhNQNfTj1oUXrzDPV11nz;XQu9K*gjbgcC`JydMkBOd|NUP!l{*}_AH}WtbnuK z&84+!(eUE%J(nj!5l!HWV4uRnjI`vcjU5LNDslHiF6g&QUD(@8GVEINbR!mY&cB5L zl!W8fP!^cC3C@u+I^Jl)OyPjuM|qdAZZc1|#wJB0mU7&jx1`~jTl=Z3K(2#SSb?D_ ze1q!od?Hk*pr2KC><9FgPA2v__duQ&(jdS?u)G`nLx<3Kj|-*!xlwN?%3xJz+UQg^ z6GI@Xf$wAA7e|0c1#SNZS-`t5!|h}R&mga*?Yda2w&`3AH5K!3Z5H$Q^On}jPqywx z11G)b^oXYctq&LdhN2BG0_?G_VY8E{5GL@7oOq&phSx4zmF0uD(=Zpb{aegH2Rr$w zk`K|Ne2A>YAIjV5bw%bs3{#-bib&<{x1)?@tfT6wr|~uYVy&F<5vl;sUTpdHw{3;o z-2|cN$!%ZJ#s@QXRNH>`Q&Z>&=k=}FSYTcz$A7<>@Gij*kw@w}`* zJJ!iLB~ZM`Oi~RWFZ^94qe-jgoNNgTY@Z%(1;lwlmJ*DEbI=F%fW_o5ZPmfB zdpIugqHPk1m^HqPk;-MF)TWCu)Rfui4ix<$2!I{T8IC^W>=PtM=A;x>9z z-{YIeGw}6#T;Q|0o67JdIV^6aLDzZuNd3UYF zpalJyOjCVcy?DiJMWd6llGD|xZF2%#vroTM5*ITPYphs5lA|3iWP5zKt!9Fm@kRM_ z_gc~!X~vmgI=exx2Hq^gCo}Q9K<&`{Npyg6g}zBwxgcAP=snen)Shn!74pNJk@joE z1t2zox8)BH70bB=@vj#Y@f;dTk^IFA&=%(uxZHJ#& z?{S^zc;m;y^>#Rd?z}n6egbNIU&;8oh+cKWQ_eV+3ZMzMA~iG@G% zyx85$#Gc5Rj|&ziskkcb5)!QkxoO(gl)ANd)BAK1*f!(rfI+Feo(OzwsZlZzfMwh> z2?+t9Z?kWBSZzf<6aQ+uGK63~>Upkil)>fzX`=4Zf|X9WK_K@QP@+n4`e>B-`fHz=tjIgjz*dc z18U~VkIz+Sl)}wAS7Lne8nNI zfk%$d?gaj5T(2kf(iEdrl9S2mHAgTaGwe&^-+#yb5% zwJM-V2f^Fp9Q=^rt-}wf29mGaF^Ez1Tm4P#BoOyqqTm*3^*`8emjnw!P}2Fb)1Dm# zSqpCS01F<2_YdX+ya{fn@oYTYP4Q5aDbGqL^5QU7&g@f`%h`9j=`gI@#wx*j<>RRw zVJ<}K!HW^uPqO(34n3odmnSC$$^h(mGk9aSvc`c=5Oo(-+V1b-^!nBUL%h;Ja@KIzSw9J;A=pE!+fW(|AqL(og?iNvtCm2&Ut4s=IIrMYDIU$<-z!V zui|8uiXGSB7CFQLHdyfXArn;N-Vp-5rafYnK!)R=?0F%v)W5Z29O=Pl zWy(uuX{`-Xbp{Qr!{)Ozl}1@$Lg?67g<(CG)AN^&0cGq;6_+)>+ikIFR-SJncBa*H ziY_9+K};(C-$~n(!kcqc{O6y4hf(~1tnw{+ZZFIM8Q6`c zj6Gg5FfM9+k9<*W{Tm>Kig58foyNU@<6$y~;y!XanI!=ccrvpEeYEjLkFRj}Hz&oe z@|)@ipQ{U1DBpu1(nNNozCng|swp?t({gmiivV?JS{#4qr3#X0=d=Q9b)ZYW9@Q+*k|7cHtYM67v zD{)%ZHL@Xxsgw09hbjI&ju%b5C1rYB92>y?&VGLNelPvSC~@o>x<%Vsqk0$=aZ4ee`k1-uE)RRjGP_{c=7;klaRF-()O?&Nun^biq6p?A?qL9Ux>& zJtnbe_VTGGqDbqWF91#xPq7mWI(hn*kCsC*cLaayc<1+ix!K8;0^YF`Mv-w{ z=ysm&P#kR;IM$n?$4aM&<8!JJkdfDnaDWP<&1tq`%?%0%Io^zstVSBOr&o74seIcF z`?GV)=e*7Q{%4UB;`_C4Ow@;DH>0e7JF(Mtli$A8|6oCMk(PH{$2KcS0ii_Y83{<{ z6=~Dac^kdMNl{If#R<$GV-{BRzL<}&4YuiuP5_7j$WXNZ zXy&^f3`Qeb71R{&xHx1pbs~57C?C>);lX#EPjeupq(Ck>YQcz7^ki+9iLCa!$NZMR)=}BLc(? z0o&iX2Hqxe_<|$;W0fHka;fHv6m4R6?}#!z8Z34xy06-MzsO~iuc?a1{)i<+=_sGa zzKLm*ugIl0@P4lgyYa21_(zYPY{ytA{W%ywlms&|`27dB!T{Si{;8QTph=NB(8yIX znz*WQgH!e>N^nSH;2 zf~$LXFixWUR?hHX>h{zqhec{EGUeHKQ-e1u)ZnELxMMv?WMy1nlo8b4fkQ`QxQ1Dl8=LX<1)tpCX5^TmD4Zm6>J z7T1{j>9A>Zlc40&%oZS; zs8;+}l_S zCyax*s`Yk`1=z>9`=8C^%aNWTA1gTrqIXuj)U(r!#g6`(hysK}nTn?hIw%JC_9fx! zsffqhMCw|;v`9A0>wB`uHL*^7YQ5V!2qTYQTA(1dzI%qDLtn5=M1O>0tA5I0-3mB+ z^MJ|;u%SbO2N_S=$ltihKz)Vn=2tanx`Us9o@H<{Z*l0mh)v^8etH_n|4*|Lz^;F% z1m#@)_4c*p({zBlIdQ|?^b*iMxoX?dR}oG5x~0$uC*;>g{j?rh`5+!I?br)G+Zv0?H9O_xEJfhzt=KEVos za`G8{7e!8xp#Arh6Id*M4Bql|+TP0y_rqpjTc}{xX)c~{sZ}Rk=(Oe>WWAHf$}O`l zv*u5EH;Jin!G6{zWvq4Z`;dfXA`AJVxN|=(QqHSu_Hep-?5`$pP8}Vr>dkEGJUEz7k+)J zYt!QUn(P`JIxLagvFy>D@Pe-;F@8r(`WMu#;aHWyvy3Kzj zZ5r|p;1=!ATnE<`-)H5r#Q;n2G-6cfq9A5*)w*Bw+1dNHEGV(taV@QPX9H~e14t?_ zozAx`{2i?4`81DJOY9;DZlLFRktEd^1M#t~F*G3l`9WL$L$P7Ls$i%?Xy7`-w!)(``pIj`M6Ee9yLB{jzJrjHx9l3jm9G`_f7 zd%8K*=nK>?v`+nK-e8aQ{5Di4QcL2>#jCeIUlsNFivBC_^)SYLsYm1VBLErdqV0xQ zK?g7|Z2qv4UiXtmt)Plt{ZHejlNiqrD7lTNybfVDxxJV!_u*032Mn$1VUb+&#^^=u z#RdJHGm>V9mcaWU9Op0&suTMJ=$btV(dEAH8tF5#3f-gSsO~}{<|KgDQlY!C-EO^u zdYBz&N@`yRIfDMTpDzNGgH|5Wr*8n#KNp&q^Sip4;tikTu z2m)-@Nex^be3^1XLPL@@X7qKNnGrPmD1hfi5^@;?nV)tzPHPZo7T+!oXl?IvxsShb<0n448h z1HL`&P*$-mQG!;~Av^Yz@alF>?w+pB`Bq$T7Csy&+xy>dhQ zwy29j@@HoU4nkYxOk;UetsE527daF^1)UarUr7?d*PPw`gZvYK?~fOI5|!%$KhRBi zM#dj95>_O*I{eaF*{ihXfVbw458m1NQ( z3>XCd$003AuDcieu>cR}AN5{O!0Q7A->hC;=m1%yP9Efb7PbT=P9s2Wn2RmxW|kbX z`9(S%F|3$QvFeXuPz5-BQQO<2UT)=v-M20-JVj}658apYCQc`Rb=SPkC^oVwUR->k7Nfjfy#L7W$1ZhBfqLV7b<#kD(y)gKcby z!I$LQk~-Y6=O6c@Sn1wzZ?qOQ8jh8>_I;@OJ^umo2lyFJyw{d_L+>B?1#5~M0Wzz`efTlzUb@EohgH!H;3b}qAF zbG!`6P@T3Wv3@LLUbRbTPlhTK`>NYSn3fr*A_^X*dCBR9lI)Rakf^kyb!i^vs6QFX zeOoj8>GEH5Uyn0>b!Gi8G~A#W=*Io}Cu;LF0BKe7BWHG2Pz(n1FuwJw#TO-T$6Gd@ z-QG$gip6d$jlTr}2Bq~fbkR#Nh_pGF&Gqs3cx>Q zzX1yba9=mW+3i1M)MbDX_>V4ao`SA^UjDhBW?$rAq4fpoHYaYE0nvWdA~?qXx6VS z$Qkz+G$#mV*u1ECUdULK&GtpdW_B}U2<%G+>gun0WW!4;0`lX*w!;UbPRiS7!)+s-JK@o< zPM$RPNpl;GMN?c5HqRt7H_4v3j|Nl}G^)!SEgv4OwG)^lt$#?hvg0h$Sa7#}NyEs- zw@iuD>mH-HO5(iR^YU97j{YZOg#E8CjdnHG*?=zB$p01KIw)Mm{tLV}fem@%Tz%sQ z7O5deqw9ij>_-VpfAYajNs`fZ^o-PyqJ+g$%!1-q$XFrHu418+dt;#LZ+d`VekelV zB~T>OAaglut5tSummnyhP73x%WZYFh>AcLrvgLHf zK}n2yLN(wu5xT;IsqGr@VaD?+i0Vq-$Pl5$X!I5QRhGo{68Llz%3QAXe6uO^S-t)v z?n_4=;H{kdVmx15jo7pi>2p`S8`*ENt9NMZ)7x!6k@fyWK18lEh3!mq$Lymo| z{ry?YS}w2GL23#>njiUM4n$@9kxGj$^KQ={iD2`x8n9USc_aAjXj|fUc#*|<&;Kl^ zAIhhb2`Jsj{m*T#3!fTH{~yn@7dfD?d}zK8kU4|uRsQPvay8O9h(BsG$f~oSxcBRI zW2sqK#s$6Pcl2k259ZyjXyrXzz#H0pHf&<6Sru>yszZs44dy#DU2tiyedE5Hc z>zg#_(e)thSBCUAODF!uCm#cR&AeFLSH4I3U4x3Ht8;6lAhsO+i(|0w*@x8wX8z}C z$l70w{@f}Y%6yWYMM^0HvtMXb{~OoOEu98qe=Upyl`8l8{V9Qy$J2)@TN<@4DR0^BAEJ3KNLuJG z$wY@)^YvSKSd~E;FNRV_Hs@woOq0JBb+504U%KpA(5*OQm0R$G=hHR(Cir= zWQjpWy{BGSATdo&kK!Ks*{{bQS43v9whe6E;7-k0S{Fpb%QCVN{anD4q2+L<%7`1j%@zJjzp` z-^v=OvS_c)F`!z8zjqi|sW8eKyS}glg>&~SSA_rG$E%hfX4kP?%yMNX&0X(8lUuOq zQ2pvUv0I+SG3v~xZy^PhaHU=FOh~7Xk8Vnwp|XY_o0pxiDIyIaBJ=Wog7#?ma+0Ie z<*rMeqjdP`ToO4*)rZzvHRr6RaemUNKDL*w9P<3Nn8R7FCfkY0jAY8zECS931~DfV z!YoG<@%a`P`QsoF!m~E6dx#jYoFeTclpd zh<^T8ru{klpz`E{m5$&=5PmF2=DG}w+Q@f%n_h#?M(F7BAcvhB{!{NweRBQTuNvVt zl!6Z96Yd&k=bg2XMZUuf8u7Ek9T&fe&KPE@^IZleO?=--)w zZlaxE6+BP522v=^YiJ{kP#WPXeyh$b(f_uN{#yc~_gS_^2Yn)%fBYK#sm+cN4?Wd8%Ber6;nvsj0r9^KP-&s z<`5eH8$C#0r_u4u55fISw~oI5Ei>kAKUc95Dyem}P^}nlO}w>8VU&`qM?cfkImHT; zUhPO%XKBpgnTA}SI}bY}+Hv?6GY-XR&JOzv4l@?{inyos>wl?yDl;7}L(ETaNRIW` z(5TZl?(QDsIB&Nl1`VB|OB-h#vvt4G*Qr^Z470PFO9YoM*gqR8{0ecN9ibPVvA=p$ zB-`sFw0RDOrNzSisVMz5_8)FNaM@ZAifA7Itb80NJYP)jJWLXzVvAG#^giWv<_vt$ zy(z2q{VeiktD6tzzgX&J=11LASqVm|^|D5iTMo#>_AWNh@&>J@LhPU-5H{h%&B-&X zIz=S0=ccH@A@pmsyK;T|Ia)ZbxXw>$HFtALDueyUt2+92hkgVWe>4>t<3wPt_FyAn zpKVgEL_m8xNou=W4_2bnCGt07`?tz$fD+<)vt`JmdxpaLxdfYPUNE{&md(%%yKfws z&cj2_#$B>&x0B1LI2uN@B${|!KKoMy*p>1$3sg8X_YHnEa?0^nh&NJsB^~y8%6$CG zS{@6@9x4l4Omd;-C2Y9&B+Ry*4b?!oL+$$Km^}3Xr6WqRoh{6E?}_k?+8K$D%k5Td ziCS@*F`eibVZ2(F=A$L;j3|B9rC1~tP&g;sB25YQ@V5g7)@d9ZgUhg`|6>t(D#LJ) ztS=a{^4-3|w6l_*BWuzplY_s~aol@KnmUs+ih#e$kLtm;>ALfSd{RBw)^xr7rP6RT zyF`W2K+$clCnRmZ6bh=A?YZ><-h*1*T=u443FZuK^|MVIZpV?}D?BYzzqAarZl+uV z)f9y}m0?>KW%i1TDC*I!OjoIt)c2v!iETZfBO_^`ycsrak<9#+{BuetU>KdZBq?xy z+p^|5S$b+|`Y&gVf%6K=PdD?M}nS zUhH&*qL$&i=3@Bkw+}}zF~r!W4bx7IefVUJ*wq(9n;$8kPRAZ>KDG_6)L>B=)pgta zo=j*#>92pm68ddZ6OwFLr2jSNWRot!Vj|UcaqoAot5kv`48Rp&M!3P%p6Wf=w&>86 zk2^vs!HLc1Hx588)o{%bB~510P}~BIPLu=z<+!;KwI88z{BflGfrdo z+?MSkyM~g`eMg4_{t)e6BIK$`QB>DRAn%bYkn-5>x`y!fj^syem=Gh?dwD}#5AYq| z3bAO|F3xXJpW_gCEN*`E&}IzsA~pZwWfl|YC7WXc8TTiG)SfIA%-MHkRnOgP=w{Qf zoy{9C>uBF@-4=CmIO>&3G4Qn-KFEPBOC`KoKm2XCSRrxZzEtgNYQ;vLY$&!U`^ycl zc^Ii~*_?3CInF?FRGme#TBUTguS8NPFP(CS?gK{5w>^x+XS{rniW#*zgKb`X)Kom; z57gwSG5!h`LND%G0k`0YClJ|}FMoS=1d1NfuR$s0PR`^+V>u36qjV#}?Entq{nPmW z+p@^H1nlj$M&OMSdGEgS#_vt7hT(6Kung7>Ie$1`+;6>QhLW83WBLwcOOT`G5-C0I zxw+ncFVioUQ}#3Lq~_L$nq5mE0pvX2lGHlg;XH0?g*9gRhpE_AmO2{qH=TyH! zCLUDn~wXF5f3Hk8YAbQ|%eFNO%8>?%kw|M+XKFq-O!ZTikphv==n zU=Qujzwr!*14}P|3$YJLj;E_xrNiboUmIL_PkyG!;vf#=tK8G>FkP3ev!ccvD+I_1 zf!kk{Q6NlRoaWgn3Oy_t=a0$Qrl#`zeFqEPR1ie*SH5vGN|0jlzydqx>F6tcBcNZDQzOHAIo#81^aoN61`=ko3ld6t@_Iw^DH&lAIuhHH+ z!lU5LXR;UM22thS>M{I~e7S$LJ>}v{e@qft=QXTaZ?Qi4X=BPn!%&qZLh1-jiS}f0YGk-XCO+T8ACU{Lf>CKrL zM8z|GNbO1oJ4kk$en`l`ZBBst(=OSE#>v-Pr@oc=tnn|~d$10mjSkv2VI%+;tYXWiAc^!G7LPv8um{*b0g!1%9YM+c!W581TN_3# zsG6p+d!VB-&~1;<@yYSjG2MqLZ{y|J+g^Zd6|-||Hl14>?&1|Hm0v=N-lXf+-9nhI zF}vQn?QW2s)Avin%cc57wA=0-;+r6;NbefaW`{n7mq^M zh*~@IkTnU|N4+*ZV)Js6MX~KY(#JJ3K)%3pPVgSAF}Z$8IsoP3--r)PVSQBepKy46 z^6>sAImi(SwKPkpW)j(Ct#B_>eFVUB%LD$Ej42W>(!J)%WH9mjHuqwr|k2|;O-ji;QRlE{fZq;FN zQNizHqMvv;MnXNT$PK%`7B`Xe|5QZ(jwz?Err^1;*5x|kFkzea|r>y2>Zbn>})3j|UIWXgsy!FYXBNxx; zUrCN8Gb$fMHpeH=6z!fc&aqQGdtk|!1*HeK_o;pd@qJ3}Q;>^J*ETKtq;gti=1iMB z{&Z+%S=7Ai@Ef^imkC~sV8!LJ+meYi0>>yz#)Z7~Qqn#{!zkW>4!cBrw8U9MY$a>- z0*9RaZ}6i#7uQ!-0a5S2f?cn~<6+iQoc05HuOob6eQ-{a4&&;QjiZn3ld_;$Cax1Gkt$@kX~E701shWPZOfUr!)sRACFjNRKMU~ z2-7D_Q{c+qZkdr#{;~t6Hm0jySIz-f`M2+xPx zkJH9yAdc>3jBb5iajw_e-R?2;-s`zF@5k1TXqyfdtMS_Hyl->z*w(0-gN)O*-r@~D zRX^ErQ-B!>5VpL(kO|ExC731dy~FHFr+n*=fv zm)micro>!2dfD!_>=j0rT;K#3>%vr%QWx0-+pp0^kNqP{K67sx|1-OckHhdTx#bF$ z1EI8v_rLB_{7Zz=b_nX)M7$J@*t}M$02}CDZo))N-?!H|Gq#e}%H#&mRm^(>a2(V+0 zMn^XsDL9>-0%Az&D*_w05rJWT2j}Q&GF_SKUCzc+hr`yv?pt;Z8`}|f(fQ$mukoqa zx##hz=BEKbykk;XT6Rj`Q2Kq)(Ta4UeuX>Ql+`f4}5% z%&oPqGN#77p87jy{r%iCBwMy4^7rBtwd=H7#TXBo($U`-)X1TXlDvp{^hJTSR^zPC z^fTmvIr4>P75}Yd#l>tk{Mf|1rLg&KljnsNQu;17Eu?$#-$z_ez>G!lW_!B?PSWKW zu>_)|2-D(*Kpnovp@4t6K`NulcKN{Vfr8Qbl67H4ZQa$n@8?&)RKN{46Woysi^<^3&heBVlcT@FteEyYO3 z7lPN-x^JI8wC~IZ!q|YTare1CjWj_NX~;2HtX6SB^Pp*C(uX5+%BN{V<3M3Sto8&< z8bT07BYm!K((RChQ}}PM$;uiW8wE8qnnA} zEz#ZF<T58wDPnt=sjnl6A3Q@Y8dFcre780Tji`_4Zeu`_W0XW!u-7E(zb;H?sYMEU% z6@e7&aha+GX7>mALGHoGSw-(UHQ-mPvozGYZ=R5SQ04xfJJT@QbviqWGn?||Z0uHY zi*vfy}nJ3A>B35a2Kx zw6VTL6-y`bC^cj&@C{8rO~?|~YwiY>!{ryt_>V${C9U%DYV_ySQYHT)G_IMgLQcUL6#XV%eFnKHqCgI7cF%L+3R&l!W-Z_Q$$Fyyc}(`2rW zQrXXLVZF~;qfbSK8Wc1d{C*cogHc_q_c0Qx)6MT8qurXEu|=fo3+C&u%iH1kix0Sx zHs!q;=g-0ahq1Q~YCHPUhpCt1rMP=>*B}K76mM~-xVw9c7BB8l+@0Wo7I(Lx#hu`R zU~g#Yci-Rc{;|VMW=JxbbDwjbWB1(qsfiZWgZn@flf$?$huseIC#hF6&cxQ}x67)7 zB~J~0JoSb+Ajus#^R&c*#X(BW*#S-kp>jv@ux}nAnoWFA5PG58^@-tZyq!XCX$-7=L zhi4Ssx+87RZ2%fG_06v}XK>~6)Fbnne{bWm=lN}R#!6IMGdDM0w_`2xqQ%d@tlnML za3C8kXw~aNRC3RY4{i07tbP}pYewi|QX5zoq+w038`j{4?C!ESElbf9LjVFqT3K)9 z&-jlQdY9<~5xbJr#X_bq7C3adqhPL>;d>%f#XWM?dt68krE%pSSiz(nzb1znR0JfN z^CC;7sv>2Z#Ra+Qv%+XbSl%xPGzRy{lE=@5e`jqp{doFYm z)-YDcwG5-ZpcbzZrl}LJEhji{@d$IH&(J8VllaQ!l5eIJYgGM3swLDX4?E5}6ZgF z+KT7mn(y!vFw_pX)XZYQ3N0&nheUuq3ePV-2*7pN;<&yZ8G~4QcwPxSnfjK{vssk8 z-Ia2>1mDm0V3Ek=`Pm?x$YObI@rpuXVL_s>LWi<#mOQzYN`7)&nyNJg+eROYc0%ok zjHUHw$WZoRII>lh(w5a7kK$SCV|eoJtYd>Vu<40aL$I>myY$+iEIA?HoY>DAcA=5Y zc%dNx8ex=AFEZB&^FQI@m3sPGe}p4Rh;yr275=a~!P(#56=wKO)2 z(M6QV<3l{4%AkIR^JA$+=bEzJ)w1;QAp#3y3`QNrlHZ#}Nt3b^T-+l~Co>z*ixmpj z;O}&DA(1v@GsGcfF}Tf*+E`c5XmOriDwRkE+UVm_vfJpJ8ecYG(|?<^E|G7)|jM$bK*cPrK!W zP4z@8w&xqsjLcJbzRNr*Kg`?dObVQP%Jah7=8c9ku}4hW%9vVf~hulk0ec+uq0az5*+CYm(9_Q=dc0R&MXGmReuduRUtZ}Qh!5p zJb3xtbm4I35AIDcG$o+U1nIqkR}Ezx+TElL3VpQSvbj5iYZ+u!YZ*GeW>3==DD;u? zYD<)HOr*EjqP|P1#+yB+(sR}lV7ld8*&5%+3K?zlFV-zhv=fQX)BohpSxPY1@eb02 znS$%ACmDzwBIo`cQ)4;pnoZW_1`ORB#ilQ-IV4$PgXk2_r$6ym6}q17#x?`)X=H!= zeU7kco02)@bk%=u>kRC8SCytoGvlwqmg)j9o}!i35` z$dXpTCp^Qb5D;8-_7~hZl)dJD+MRq%QZ$BNyNO7P|KVk(NbG=NUu-PL4|1xc{DGX8 zac{^zWm4u+!msRPQ#D>Z2W6ZPTLn`BH9lbZIArf5=5D37@X<}N(jkw(d*652XQ3|Z+cMbyzI2- zob-v;ZKRvm#Ep{Q8_~6P!)!`}jvfkXVo_p&?U%@UNU47->eYW(y z_0cTxnvGuz5?Q1B72-{EQwve6z>D)=xtF7gljt1foWZ1Y!8eDqhU_1cO0lrC`%co`$=9OP#QeWBE>9j z(gwwNdh>dUysb^nk5uMwo3onufB*l-(nM$R4Kqk1kqI4J$jrnnQ)T zF!jhi80!{XzD|<2ilcZnoN%YkV)^BGGyhfzd3!Tq=&ttnoUe}Ig#0-A2Qd#+~vz0c+u5$gRy*I(H=fbUj zI~1lW?SH?(A*l_w$I~{H^4qM0rgm4*z!REji+;sm&m-T$o0am^S!SgQFpH)Co9)dP z-Wq|XfgJC6-(8;c7oe;EM{+~ayU1$98^ToCt!avw@4;J^Yj4&#PPHcJS1MbNUcpB^ zt1syDxulyvJ1oZM#}pe8U$mO1*$Hef3PE0kr*RiN8+`j9U(Da3Ne&A=MskKdj+e=l ze+T2#kt{Iu^zwyANJLIy17^79g!66lU8d_+t6%`+MV#MmZ}r`l5rkETIqr>8IT0EL zXaw3LKg_(QeH?QY@QMo%m>H2tmYjJ<)+#2%v&gB$m1@BQ9?@O36cIu*!Q^|A6qpeX zKyiUBc?5)<(NwD_$ymj|7c`GIWXhU~2rAH^qg|b>f+qe@dEr11YF1$9dh_}t68S%| zeO~^#AkS3gAMQa9xKll>OA+e|0D|@U?5ywc>y$s4?%oa1IckGUCiXEqR(C%H){CQFaZiRuOA7+;ZZR-0%A6~H!$PHIjL;8s)n@;UGaULf#WMa4L&Y0+PyZn^#m z^)NzN_YZ8j%GpmDyu09VKiMa z>_;o&zb~|JO^O+8>OmGG=j)Ir`@0D86WmwQ=cEdcx7Av|yY$|H%a2?RMQ%i%rZBdD zN9P!sn+GOS?pq#s2^`j6?#|~ z)2vKX9o;5qWUq-Tp%R~`gHE+&;(NU6X9%LMY`u|N4p_+kel&ag09* zk1-+y-47`+urXvssW@!kavHuh-n%7eJwd8dUzPU`Z}r2Wn^6k9F`Moxhg<5h z$Qxl%<)jD;#X0{S|3D!m7nozSNcw07NTd_r2U=Po0{z!XAwD{b$#E4|dHEb>%!*Cvh+*#wT*h z3}Uz$@TG<#wT8Rcw~n~6v~bGCkF<&)b|mwQLIzdf{@s=N1qW(1PS`UY%o4 z&E3z?GlrIrz1<^Z?v=ltTX$5AeBCzIW=*Jv0EN$J3jUMl9vbxbwrZg-kpL*ZFYj;O zCH?mMLA*iIwH9r6jOUp`XBvhPle-gcFQC~8R?TQvDbEQ4w#9CS?sb|x&+J&&wHBC} z!j)%(n}y5NftG4&%Azp9UMKcb zqS>LI4*?`QfoNVn3Iii5UHqV(^A2E7!t@a-dH+9xz@3gSsqX_$9Des2_I>&4!T69x z;V#JN%Y2>1C=Ru>O$xohNvk4nJ#V^@le1%%?u&A>u41bF7C)%PHBmDhNi0ieWKofc zcbfAdNLcGGWy?k>rkJ-56~iinIPi{I|3KIkTn0!|F_a*f2TJPo?5klz7kNEnv2$f8)Ht3~YrAC5hy5381C+h+am=5?(^ zyx?Yu0%{gtL5{>F&6HXneMt z{BxG>1;8~NuI@b>WYIV61c7{kffru`O?|l}Xbq9}Us3jSkM! zE0ruIQKj&XGK3;nkN}2#2JG$Z_=2?N@P7y%=t0ajCkID*Cx$FB46(<;=7Z?*;nbG@ zXOsl@<^R(g+6P&yG5*M!@$X}4rz3NTUGbfu>1ysOE@m#Iio@#D?VPK9PUZ>b81#1A z1=G^{-ut*C3y(Y8Bj2xdmJiO)iY;8h6=kwDh3_maE>=E)6|VJb zwjl=n6;{LI1Lo;kh9?ZZ8tOjDoo2X&B&8nvzx{nwUna->qmjZNtBm;RMPfobo7j-t~HKZ<7;%IQE0PfHRwNx8>`x4Z__C(rE*YJ}1ApFk?9WGL z+kH^y^vGI8)B@@tJkIJeMhsf_TC=-wCRnwJS_P(s?= z2|B^-3gX$AvI#Fean%SO07--Xn_9@m(2JF4#fx^6qb^Zl-kKvFDs-{TQA`;>FwFPN zHrMobjd07--zHRUCw;~<5zyv7Qe&2-@Mw6KsTMC&wNouCEtDpxS}9V0(HAw<2_P2V zK~o^C_tuSS=_mj3&hzpGPJU#6$1AT=chfk9RZd9>M}1-|6RVBVvNdRxvLqQvUv%Z! zO1Y&m3b&GM-T@LPHr-&za{Q%4!wavJV%kws)TcqUXMNFRBnpy< ztne;Gsj-!J%>qlRBc+ZaEij0yN2cU(l?^9XaT6?zH( zPs(KK#3>8D%0X4Fs}8^AZtN#=)iXYZdA(=Scizfjw2l;A)A zY_jMNp?K;0N9qsu-hAxe$F`}YDhD1;DI!6lyeWmpM$KS$hVNNf@_j9z4W!hn7T9gU+&RRdOB zi(*;6M%EF)`IHmi0ayWaQA#Cuha1_aF#vvEq9&ca!|)EJ@xT+YEw^Z;`jmj;ymF(o zrTlHer(Wg+JKd3l+nX&SaNZa0Hs;x%o(o5FD%{O%GMIMcvEU868>Q0bS!5z}- zK{w#Mb%d{xSQgjffK^geEJd%|6154ulspsld915X8&HMK? z0)!~^OE#WNDTVQd=z6PO8++^7s%Y>&h#njLlDKoYzX)sZd{f`!^~|mw2YhOFGzP5b zzJ`X#+|I$u7-4|Q#xnY4+5$D%Ubu~_E>Go|B$}0Ppd7M;W+v{R#*yg1k(RoDt-1!) zXOLqwi8909kH|$i{`lOsC2p&~)9aG*RM{7MlPW2MMal2>&9f`W?B$z`NXFm9Dy-J1 zw0hNxl9eib5UPsF)FQQUNN$^8GwP!*6vK6$OBFQP&n78MW{9CudHZo_@xwSkd|p;{ znD%(=&X&ecL?d;|wf%yAIK6OV;bMNcDnb0x+V?rQ!rC$7jb;o;qF3V=8`>2$S79nk zwxD^qe^Va^DDRhi-JuGF17h5(hKNn0e#Mv~${1y)&)7;#q%mSX@^Fe=W*Lq`&}h-qY!3x zdDwhHHkKx7ApfW{2(JO)mwEg>$c6rNu=F2a4$k7_nt#u4Pw%Q}Ah*X%zzTEhnMfEh z*9uwDnlov24voY0E=W7qv$uAy^K-ct>!6ly8l+|Mr^Pa4)Wqj8y-L&Bt(yd@N~wSwGulNKa&k}sLz0-TB}gZv#NB22 zJ<)W0{;#|nrJ~-ta|I|~z`E5&nUf}3SKEd#`#dAvDx*}c8_!v0#X|8 zZ66a1xuM$R4TSkxW6)#y=`qtCqr7hUc;sHLoj^l9mR*CI8h2yL!MxeQ>>I4@JvQN( z?~kNnzOxB$@4dk~(2xhL0YPeN)97vl4ScY2z7irsL$m79IeWdgNu z1blMkWWIDtV~k-O`8rG&|63DuToQwBUmHtVdOEJ=nWK*szj!8w#yfla zx#n3u35Xtm^VohZ^*h5-g9KE#fH{wk80BpsmC%bC$7z9%m41mAw8-sCB^b=#rfW_& z3k%ikE(;u;g(mj}0+%gi%cmQ7LvS3&;g?f9y#f5YFT~;RLwQJFCVPZOyj?V38}fth`(fm80|w6Rw$3&U8#cAasUTAHIu3D#oG!bRp9{2tSvC__!Zq-%sv zwVhSI>Rwm$?)ukI%9Zy*6PZSbZlUWP?9mGAO-~S@i=cKp>2BEuNlC%_fP!Q!v@g!k zhTnO6Fj*PGL}F#UaQk+?B14K@P&v4^o8hg^NmZmA6G?n1w%Z(OlhDV)GLnjO@I|WY zUgvY6e!uC9B0k*l^|ZA0aXdiLMHx}e>D-}y*u|wd$?lVBx8hc&u<_UQ3~$CmEKKa} z%}mBZ=-*({hmFG;zt_QjI+5&d5_Es#Zmu|Oa;^x8tP~Yo0P||M_(hfP{N@zu$07LE z%_UfO-r$WuqmJYCCBSqvXkCt}Ul2Pa>DlOc4xL$7PK}UxSslK{rWYj6?;n1CB)SC}0mAfzAK;-9x>>QT-(w?nAR3`15MkAwt8_ju0 zGn?Ck*Vx$Bd}V2$N9u@|4K2(8Ph)~^5ORe(kSf_t#6mT2B(@7_m@eY!-L6-P;4^iq zXtYaIB#-ZRpUpw*3aiwD6veS^m4Lmu57gj8I73VY-lVJB1GV=OZt{#fr{S ziLy6HNO6sT2?uMh*ZOSl3gaC;hMP&cGB00C3uZWawc0OOknENtt+VMnihlqakn9rSAt>2cCoHN3%SvzS`CO(gD@R|tT zd5#gPDa`oNcSGcV#;ob%s_X=ZoC4TnbQ_AM+7!syksdvNSI|~Kr74C?(y6;0N*{HD z`E`Sy&a;H#EJoRZDnH(Ayn3BPO=IH*mwNZ}b@ST3`QBoHN;h+hHlxyx*>oFu8j&8$yN0_ikhbH(#@EZ&p2|Z*%AN!|7iuo!`;9IJ zLcImf49g0+bZLCdF=BIyOz62L*fqCt>K|iu8W5}^Wu^*G3Z?v|jnReK+zsx^JQf_k zZV>1w9q|HAltD9}i9bwx*-YOOWK&{19|JQoq&x5Gg0#J^$tBuXt%AxFkg1Cx@Cw`f zY)*A|pI=?0Dz_Wb{z0*4?%MfmlZq&;-SEU)rdAt=UbD=w$~>nf@+>6eokNwLAzDvF zkV{+>ZW#%WZCYmayX(^ir*ciTHk8Te0+>Tigbp^t{k~nY^uOKJ`G4b+8#?-K2dFkv zwj8)Yc?HM{J%-x6$9lXrz&M*m{_P&XulCz)%0;g0xUJ%iue*7_x|y{3Z)uXcY?g`0 zZA^-m-KsaX8A&Z7n@K&qhcC+*i^TPFhJCHAsgP z9d6Bpv3F(OVscJ&f1p`zQMCeb6!*A?BzaG!9kywIT|cBC_M=!F1;< zYy0Wiikt$T#@vFM3ft*}rfsu?I=64Bw!M!Bsg8x1;eqG)CKP2#5UJI;XS*XTy2Bje zIWn|n0lZai_*r z=I5@H#iAD9R1Z^9DE9#6TCd8G`wOT-=A=zIp_z8`s$R}5Y3J8nDAslp!{b*S2|l)V zuzH!2k?Eo!67xs8`%jAgI;uMdJuql@s(nji9DK1Gt385 zL{-~SmP+~DlYp0_&h55iQLeJN6U4Km?3Hbi{aUHAEH>-7-24P-%h%qTH64^!p+t?JlLBO>KpUmT z))aU*wLgMO^`F^48@oGvNh@s;wFPF^4!uPJ>CMtj7Do$ER{b>$u7zX_!R7k76I!hmFwO}eO~qF904B z%gTuN*5>&jJUP6`BYy3)oS(~+s6Yr~dtl(K! z_Bx}!9q~vEUBKIn#a~+{v|3t~E2j;)8R=GRi0X%oVn$1YWXDH3Y4tyqp^l+uEXsEs zUbtJ%+Vo3`o6JN=Tw+G_>Q`p8io(HgX7?#TAb@sM>k%RRJr{(m|Y4<#cyoFvQuX!qR4&5R4X!d*Z4vOZ11 zCPC6Hnhw3XCexvr89V*%@bsr(&r8QO!4uR2>xkQ+(Ot$KqBV>popO(6MrB$#`W|FA zpW0lKcM{L}p)F&+#m9sQq@B>qs?SKyk3? zs3F6MIsACxDN2>z!WJQIEwx8vG|<I)YZzo3KX9oiRk$f{)O$Me zDt1@wSrSM?2(veNhJSB5KDGo1{cO<}#rBGVKyKt?%bYZ(W#f?>Cj`uX9mgz&dd2RT zM{C-Nfg!ftYv`rEW<-B{yg=8i>JrEKI87`Ma=my>tlPcI#VJ=aVEu&0Kl`<_Wn?TE7jTW5Z+RV{ZWh>bR0 zS&y9xE^hory)Qg6?hB9q$BftAOJu$m5Am}oo5j9oY9Bsct+H*mcoCxv+3$;Xt2)d5 zKxD#A1ZFYljvj97RN$qfovqwbd0|MwY>fl~`hDFvK#i~tx^dEXUIO2KSfgqTNNAcG zEtUCL(=%t>tao3-4Rar>WbkVCGXJL%#KwryQHmePVy@L;7E=K=*=9vp^pAfY_oka% z)AT633Tr#&a+n@^UnHh$?z+Nh1?oWhAfXfTRIT|984(6^#aYO7$+G&X5q0$cA@hHO z@hLT4M1#ifPA@7U?%HJNSVd^b6klc>ut;x1Fq*|CEWv%CVMx+-j7H8}Cp7CNL93|)&CT-X|KNnZxzW=#?x!cG)-X1TT zvQTQ{D?g`^Z$c#eK=#ucn*ZVS18_O2%Y2;1&cM_#rgqztcbd!++&DBYFLaC1W8Q+E zhTHm40%gGZ%XBSMom;i4^PueS;mxb?M6v!e#t)*2>v#xx)(euP-SRuHMcK&d$@tBip$&cOmL4L1; z#WJpq+>G<3!&S$f2!aXS^!F+O$x9EB>Amesq^0|I7|g zu}LI3blfj34LSLUB4;^pj)b#XN3ikWEQPb1sgip`3O^_sZDBq?`q)bNTW_98MtnK| zl$E76GZtA%p?&+CbEVQ$8$7^e+^ia)Z!BCRP$-pr9! z*#GJ6qI3Ldn!uY|4XRbg=na;63H!6JD@A&>8j#nUc7hv6gUU2uL`{yc`AbFB2*OvD zP+OOm9IeiWWo+)xjIYIY_ONxt!mq1i25D%8ST6e>)j%4M~Ew^Kj_u_io0NK~&2;_6-Cu3$vUC zp9F*k!Fy3NO3E(CV5F z5S)r`z^vP~_6W0_$c2b-??kjFn?m$nuQ;u>1&?n=Xjh%{Ix2H(u)|I=QP2%RLkb%zp~fheYiym9pEv{;MX71U7hKIh3G#@p zKGP^R#X*YC9e-`;oQ|~DW6A);W2Y^;c!f6jgoStBRI-qW zAR285T^&<;upu*7;)^k-r8(eT*IP;dua3@0q4^6iRU1{3)}GNr*SXB%H-5o8O%Trv zpR@0-!YIR+ijUXJr!+6wfI>5cM4?dg_E0)O6O$G?!&$gfQM%a@Um*Wvm~EYi0k_Y( z3`48n1*}j6zn7&vK*|7)?Y%*_w$9?kBW1fu#%rl@X;vN)scCJ}KQ4Uff4K0t)=Z_X z_G66$t_|YZM^fbEJA$yX&Qg9(db+7*@P+4`0azwiDcs*%s_)UMwZfLT4nQ6bXBevc zw3{Q6O$pcbH#wQW`L^;^6eJEv-kcMp%RBm$-?#rNl0dG_Uk&C~i-VriPGCaz((i6Y zCLi0SCo69DKbvYj2AZ49UXrSK7%P-|2z)ql>|f8`3D$BNEOBw>uC|{nq|p(CIrfuw zHcqq0FsOKJ`Z-hU7N3>(J(Ek@ETlyja&)?d0-7cekZas_ z0H{iuBewBu*%%CYm5yHv_q98M(W8(0h-bMrG!D@Er2}+A{v1Q%{(n65kEBzf+hPFe zPxwrh63M@rtT?yppGSsEx!sn9UV5PW`YwON<8(86Pr;!>v2-D4C^@rO<4?x*ndu zt)=ecbfa8pK8in@>@+>4t?A)47|zN4-ul(j>lPsFya-TyP}1CVd=TOx0u%xII`tf1 zB^sf3D$`aBq~%mmE7m2&}@cWe9i9T z-NBosg&}saZeUE2oj)29v{t0;qU|n7yCHnC=LbvlP;1Idl1S>p?FQP{4OnkjB^M`^ zMHR-E9(T5qA>6TfhJNtNc>c2$weQ*bV}ut=cY7GlQex1+W5 zp{cR-ZQT8I6HFt$(}Uyf3;mNO{?6aLzl?Hq@C~r7w4Xu=_q9srVZrXbe1+jBI>!RK z2!oH85L?7@e#t-i-THXl*Z$GFFE$uvhvNtr5iKyRABDgBf9)y(vy8h$~9ZjP`n~N-LlFd&wMEg|)=+X#rVc zD+DgOj}_(-RM!SG(3P=may0E40KYilV;>;4I66EzH^KCc1@>iwy6!=(U`2{pN(X*t zYuZ`h<{2U5jMY~N+5@&rzW3OEv43>@M^=RO$A$$Ck7nJ>a=aJa>}m&HyREU|H^^bl z_m9X6z1!V7Nk~gj&#?;=Zq~_?t1_PL2<*kV)o%aTd0Q8l8G`Y{89gMBEH*%`mbpp% z)F|a!In5BIR)DMap5SeR>ehlMNcA`X(<3)2FQz2H6y1jK#H5;fuND`UWGPbG#z+n@ zZeF!aF*`J?%Z)>AVm$PF0ITK6Tbx*=>y<-BaujcTgr!HZNPW+|?JlsRo(rLTfozTs z?XDBnoQ#FuHJ|A38g3g`A=aF@91`W>Q|&eHJ+bsF5n*hT&8Fs(;oQ9&XA$XU*^zog+{tpTOe+hdQJ3r z-`7LE5zHq9sYQN0YF%tcyjS}ZE#N`J-aoWHG94DT{k2q2p^r>&aW0gX|0=wtIv7yS z%|1HRPkB6?hOYvgi>Pi)ISTo|6E@C>WT|v z6Ws)}OuYHF4%)|JtzS-fqt(2?k4AEio3XR8zXFiqzSy+xXTaaDT^#aVz)s}&hZ5c& zl!X7GgnZAcR?-KOMMjlI2d8&S&%1#PBmm6CzvThUtz^l1=9@NVi38wrZscNI<8i9T zN*D@_=RP)=Q#6lPfR!``o?!Z<>ZOt5Pdga+G(z3-O^hiYYvy(mA2l(u&^|2H8~KmA zq#gtjdj3Zcss}+xUhX%KoVWs`M^9W40p)V>)*HmsDWr3VC=N&0A6Ny~Z3qI>M?*Pf zSnqaG+JESvn+FP0UADUH-_25sWU%-bzdl&804WU6oJ!H3*|>8$_35Fx47Z-r6+{*$ zfMN7ajNvkH8<%|cVB=4O;k^5e^YuYVU;ij6^yh;+arHT9c6>cLv>WLymL#4m=J{@T z`eMGqyHQv}LYIc5<_h-VsPh0xARC&2e?A8(z*OsHDImrbk-sgp!0Z>l1~1hi7Gnn5 ziot!y`IQi~kKy%nG3}d-A<$LW2=X}UD2B)6pfQFXF_`t{W^*B4hi%phwB`rblm9<> zi2uOj{0|5qShkr7;NP`96Fp5 zD%=Q>iQCea9HLw8Z;d@PraKs0Me>qzS%hM~~Zc`tKlp?1DYdK53cHo5k((eyW? zc9;ErE(~2!OUz_C2j|Ts?flWW^VC?zczO}lB@g|jqnmCxY2{k{9U!p=N=$-7EppzX zj(*6_!nMD+$OITVbU>??t88K1A4cRo+fU5u;*NZE&q9Z>zDPd$3q6bEz03Pe6ZjYU zM9M$VW48Z=UWQ4%XbaH1(UP1UH^z~vCq7F32?7S>d3~;a34FD!w_{X;QiD`MlbQsz zKfCZWCSGE>c{;%o?s$d_lmW_2GQ?S~r;0WN*`pDE>I2v+vFftwW+Lq3(-BO;MeNz3=qtD~X$xduJu^N2nl0t_BJxKu?Ph^tv2iBg6+T{@fw9u$27wQO3-^pF+bOKo@_No9O}^m)hRMe zcg_I))UmUZ2A6IGkG2LVOTTUCH?%#Xaan^fgYXp%la}=<36rI}y*Q0{y3{W(*8ZuJ z_Yadyd>_=A`bVAY4}n?6*@vU#E99~{!GTk8^kDaF&)E9L)vsPbIV-+231T+uhpSlGytH8Kv4ZI$T%zaHG`Kw-PDun)RpVyX3p7cg-%YcWYGq zJbi|nZwqxiaXi7#hONwdsNraCa-4jqemkD%3|Cf;ar$)gY@vo(G9L3q`iW>c0^UC{ z(ca?RVm|t!^$;|BUc@5Z1tYv;dRQYovj1ZDo|FNG;lKr#9K3TiUR?C~KJ?4xoAbA+ zQJ2K7UK9UWu!(}*EdxK z*M&Nz`hpA=r88$4v8pV+>4vdOny^h~2CJ@=;_22CH8vWE*cX!|=GCb_ zLRPQs7X#@f8*h-01G>-0QMYN7qz<`wFQyS+O$KTJ?X8&Q#|iUzV?B&%H1q8SCeK3C zUF(H3D{_4^Tjf~PCAcLEoWI~LXpFgAH+WlynQX_Y8h?4vzdox`R9V2wD}0Ss z`{`}s_)MEN<>pr1*<*vHn|0b*_h9-1l(EwK{Dj0TFCSimr75HcZ!vquc?W!~SH4z7 z9YJO)3#{Md$4hX25X?LFG96yDO!HT#K+|7VwAQbeggBJ$%nB8+x2yR5|giR zXa^{an*nvAj4}2M(oWDq>v*1ar|ERY9u>q|yOWxis)Nytiwb0(7K_s7s70epQ`Czu z`st{+bTArd)#`rZ*PE^*S#iqjPSGk z*`Fc<;UV!w;C;m@JJqW&sLBs2mZ?%$=}Okf><&$Fo#Sazd$IktYx}u+Q@!Q}2)Raf zlCQQ^sYv{EyG^C+k$d26k`0akMzLVs>d#TR+K=?QH2Uqwv2VL;6qzj7tt$~*;P*6x z-ZgE2Q<$l@BRD5r(c~yu+s6~&E?)RX;%ZtNc&H4#tyU1JT1Sn3f6UuCER;R%`B~u78(lhw2il6_o-1S+B5uh($i-&VuGwW;_Xx z!dUk%90u^ z8=lnQ*B?b z01o#FcdU(2EZ_rYYtT%LVjc2iB&WYKSgayfziDvu zX~t^dkM50vo3=7iB|yEdcdVfH3Gu#-T#JS zwqH`CB=xBC*D{?&CgIvqYRSeV^X~JES#3IHDsQiS5SBa+*O1xzikXI+YL@-9*;$yF zJd=&mAbzIYNXASB{wL7;qR}_|q;uTWwMvw`VG2Y%?)GR7k;0zgZ+-kyHdi~zxwB;j zWny?!viXfd#NCr9O;U_zWTV!hH-xRFN=jQ2gw*4Wj&>vhVFnpvumAcxZZoNe_;cQdx69v^l)kZfHCJs-mT< zNu|?rGjU*W2xnsiZ4pUaRAs&WCHZSrLlXhyW+`8zo8dsdv!{YGLOK zae;O=bTudQ-JxIPVPoUXOkEcu++Ch!7oKm+P%&?TQM7OPRr$r95T8+vf(V58&& z#broUCrAbnraw7+*{qeo-$-SN-?{jSyfj*tH9eC_d3FfH`V#~E2dqY_pS_Ob?W++r zj&e~gYmwbku0mZ;v{nEne%wd50(4s~7w?Xqu)JD7iDjEuMQ&$&yoW~Od|LgAGh$Q* z5ZeT)9{^iz-UIax|GvIMKx<0~D7w35s8aO+^_FVEJ) z$)-Xs$)*yxBR-b5Sm7p=9of=K*BK^-LJJqHQcrZ>t_P^=XbXwc+K=;4EAAiZ*A5YF z&hroNd2;);E`PN4cMIR*>73oo`vj&FtwDsnj+1S^MfG*6^Dh-?(CDwT$omwwNj&S8 z7sJ?fv0~h^`RII~&_h0Tu@k(pbEe;-WS0KgBEd*4?KiBrL&vCi1eu_tKbGf`cqySY zI*Z5%-*RH^r-X!qH6G>8V3c=Q8z2oRY**5xFYw}0W-c&Z~XSXDsV6mwwl(+^VZu{N)Z z!_=FuW_Ba{AgV5b4cY z2n-kV?4U%$Ltyc9st9|jl-3WynYN zGqRa1@FT4=wuk8h6;X-79{QU}r8rz#@Ob}mcMO^Q=@yC=XCG%YFeE z=Yk`UbY;GwImNUGXy60?xc6R=|L(mGm^^BKxScoQ2@GsCHR4{$K}6z`uZq(AW-6-L z1cyB22Yg0*RayjkRA`w;oVVMh5W`Q&W_j826fF4)N41e6>z=U8lryI*mxZyG4ySxkF90JAue{YEx)?$Q*lD8@=#m z+ZgOqA`@NLJGVYqvXK93J>Sdr(5K2&S}dOK3?3l&CoQZy@K8t1b5x}myl@SRQwa<; zTR<^Zx1l%Po~grq=pK_ptk5{EsD#~Z?JzH;jFy<)g|H3q@Fg*WpYbDvpFVIG1n(CA zS0cv;dU91(royB|d)em3EF?q0Rie~4{E5|Pd7Nn~G)1&TxhjDl|3_=1V4o5_r?7lY zp3YBx-v%1!$ocVeZxfP(_!y715RHawLL5Q(!y>4fBBPAeN@gkfWUE!axxcUJ;-OaF zU3^tE>NM>bvw~t>a>+9}Em`ZjF{kQqf>kQYrISb80OA|^Na3~JZXM4iX_K&!&oiep zAOugv|C2KxI@8AN8LPuSQV0wko}2GPg(sAX9%Jbard_Jk&P;EwP7qkEpoHm9YiHww z>d2M*dBkf1-E&ml%8pZNlq4J1M}DHZYMTm>zaa$7tcS=QEL+>1o26ER_@U?yk#6rn zUrc4Q(w)s?wxc(X5Kuwz zhyua^kq)5-*<@XV$y+p4rdrJUVlV7cAyirRc`4Vv!23jtTthh z_`&gf6-8<8`*`A24EuFKP;398rz`4((_0NeM_i7eVwY)ls)Y20Dkv?uL+L)LRr=0G zOz}#xGE>E>!?=Ily%!6W$!?_8OAGp|axZiS8tQ}695$f%gljDq2F@(cZJJc@_lOGa zoaa(e^yGln2~ogJz$bLJSMF8#ve4qq`G1SQgA-~ZnvnAtcY3Vwf*rO*8~8;w8zbH2 zwld8^X4i1P8#`{1Smbn}@<3$2G195v{2VsHC+Hpjmn4;Hl)jpM7Y8{6lT6D0lq~X) zd>wVz8bV?1lwmxid$JHRs0xv+V(T!2Og>oo4W(Px#jmVW35p1_n{gsN*R5piUSv?P zmWGXaK5_rigwT~EJ%5?J#JEh4xF_BmosRL#D5-d)W;>v27rc#R>)U4HxVR_S{rx(1 zSfX$P21UL*yUC<}U=&<;z@QrCJ~ysi0TP-^BVi%x2ooN$9A5MAVObHAV$UB+eZ=~;JTYf=m1Ci^NL~X?@-x;8F&+FCYA|0r1ISV*SegFW0N84clK!kPn(}xw- z7!LPGD62@{VTW=pm)Z@lW$`Hum6mG*7m5ayntWMEC!+5>4MXZ6STJ{+zC{O3t@_;h zES%*2gIvnFX5rzNsT8s`I#s`n7_@!$ZoKmga{V_ETcdmDxQmu!%_X|@dn7DX{3{k!KIn+%T#`b64!HcL5^|lXsv3C`8*sA>0I(y{axDMDDogKu{bfa~@FPCy zhq$kVib=Py>4yvW41|eTvx1GoQdo_Y?SRB-Xfo)Fc%09=7QO3~Y1fMLNra}skYU$~ z!+7x6$kJG>KXASQ6WrXsy@|sp&dYXj1=S{KS9uZTvlooB*cU!0d7*A6R|jx)DfC`V z@L#89zx-Y-$SI)-b0TJ+u|sVZ{CGUS?KOCL`phP-pY&*4v_2bxjBlpxFvmsY_~Wu0X9|& zcLdGh?-=U;S2+F?f7kY$E2cvy^IhM^FT2zx=0MnEpjAv9X(9e;GZ42lV=Q49d(q+69kNRVFCrG>N*W{&{Q`6aIR33alP3ZkWgQ+jH3@gqzx}4C-@2hWvfMA|c{Ye76@N225@7eJ| zy@uCSu2D*D<^?FNdq8NE5=Ntd(#25ObF=0I$%&1+*6O~P^-7AX;8UHEoLhwIn0i*j z9c#Jb%*$gAOD!T^iK}5xrMmOjLLY7)QVPOoz zA4I$!?RdhD1r2}b=B8g3=wVzOmXY+Zrtmd~yw(@5u+%M($J~#y1eR#sE0Avsh)Sut z-OxXX@99;jsJ0wvD>gisQ-;huBsa!uy39D8VWol@k;#5U_m6z}*bm?qZ{RGNub+zX zXGU6X9TToQw0Wt0{i;xZW%{?7*EmM?3Ni|WJ366s_p*zxJO3Ve!53y&`jB3*z(J& zPr<~-PD1$hd?z9ETEH}8S=E7e`-(U=0iHLhmcfZlpzKETW?U<%U+6sU5e`oeUcka_ zM^H(GaKg$t;0nP&m9$)l#iYZpoZ3b9*$srJ}D zXgrR|lMSH>p1m+h-Rh7E@8m3e;}%hyW!<bxyLT|JpTQ z6h}!B9VG6T5?u)oD{o&XfCrxeVQuvkqf7{%2iG%gk6*hBKU_mJMZ^#vWgSea4)!i1 zQ&R26r^rn11}N>9*2l+K=ji4yjq=NRvv^)lV1Da=A2!q>*O;D;^+N@3eCbCnXS}{- z!aCRstj|wR32|k)7)?&Pmks={)E}$92MB@7zyh}wx zu;|i{c%Yt62^y_5Y;xAj$hlh{TiMlsE~U@!^ZT+rdG#HoqF4`uvw@~40)4=xx+bh< zS1a)q{F+8mGfK|{hk=D8o(ApmNav+J~TOux7jU} z|5E2e=|R16VgF%BgDrwlm@A8oW|hX{3C#V^Xbqe+8W5WFqJatEIX6egll))RMiT#B z4OrBp=>pvFpa}v1g5t&R3XA{%{i(E{0TB~_v;aUBq8R{?r9jgI0M!2>`JX7_w|xq4 zBfoDJAFB1*H?FbQE|qNx^ru*NK^XFan&9v=$`&j2p0Mp{yT&QUh8C9SR7uufcvMl~ z+{@CX=#hkEf(j@Tx$a$dwW=|=P9ku|>33~+>V4o6im28z4&}cJfsGxkg4D%PiB(`@ z(+R})AazG8u<~13+PAOa9>=+SYR3 zTi9sT*i(J<;b-OivBg;z+C0I2H;|-Mr zMAW{w21Go0LghM~MYnzj?a3B|kJT#nI^Qm2S&0P`p7)4>g381MiuN%zJ=21l-7?>7 z>rw6oopHN)wF1{MIbXX8;^JK|R$XBFl9?3Yt{00Cn7o;8@b?MT3V46`>$uJu;VK&C zzCVnJ5!?jA%1U5h$!_x?$%<+LnIe@ByPM$^7AZ4r^^rTad*sD4LG%C^H+2yuCPt0( z9}O?5Z4uk2Y;JGNOl$|{Ps~^6?npa^fjn(}#=emp+h(3U-j#r2g2ILtwz4Cd^0&mf zp6>?QAc1AS&Um#-6zKTaIld3#l`YcZsC7nA;m)yz(dZ>bwZK zJGO_UxmN}kbE%VW^pV9msoPKHi^^+5x$f1(VIRdOApfF4d24jJ5WL@~ND03!>G5II zALCQxZKk`jQ8`L+@)zxqE=fN#>i24i$;a2~frjauc3)GY2O7RsPevV;uoJ(Y8ML$q z`6*z-LZP~D!>oOUp>U56fd}2ovVDbV&D?JC)`j|{nt0=_VIS_&q1>SHY-2z~`w_`y zW~SP(n%#Khfn*)IbCxMp5D6EQ&+}`xtBAU0=@7pnbc!B*Irj0`$y?qwI*{ir&hMmh zhftrNQqn?vyjyiLLn!J2Tu?f|-X|joKx4ByRp#2PLgt_lpi%v(KvO`Si2tHzKIf3m zvWdtyNuA4)RIW}vyKGn*dK|#f+Gxlee?vIohH(7dm9@)Xc|rMuc+mb=AT3DmEkIBy7MhCmE}a0O1qcv8dhZYj zO{Di4dbu0%JLi1wd&jurj&c5c+p+WPy$pM{XU?_uT%rpX&-n}I&Yd%UZD?a-YUK!A zSeZLIof87aq|crH&FOLe&nf?p5%qtJoHMqywsSCbbTqfM`M2xShg>u{xUHUEOjijTogQ=;_+2dtQZJdAyxbVWxUHzlRDg4i`fXq#t zUjOY8ZnCB}j^+d|4imNh0AB-Bt&P}`3JvVIlI^TBb@r$+5PYz zR3$&V_xS4!pGcnJn$8(woX>DL@(krG&(P`D8NMU>?T~YLt}{%9o}tFaGkk$M!>rCT zTwOjxGLo~{bF-bH&hs<;4m(30zcZZseulSyoMHIH8O~fibHFO!8A=$R;o_$=v_+la ze(xC~_s%ek?#y8wLT9+3euf{-&rk?)hKassXzX`}On)3b{|B?*pN-poI78l#XGr*i zrJv63PrXzQoNmzNA3T8RBp2=@Irg%mYRI0SIq-k)v*B1%>wou1C*JUO=Q_=x|8NuT z|L2>){hx2b)Bk)EUi{~q@ajL`1iSxy6W;vioA4Ph!4g-CR$Zy`pd=@M_`h?AEc(f&yK8^*NNu<@n@O@K zmCrI}Dq;=Sd>yQ%XZX}p-SCZ;-fLC(H`PT5Wz5%mDZH0vQyac{xNFO%Py0`8pIFMu z%9@FW2YmSOjiU9Ltn91~9iKi@$?NOn1!9qYjQ6VMIEowl^Y7`&$;uuX13oUbn{oAN=r1)GicRC>s#ZoJ{M z6@6lao{G>K_AZ3NRS+*Mlui7-zi8=Us8;7ce&F2I;e7Z|U7MZi)-9GqERWy$_Tt?B zAMW`qQMc+5aTcouU9ok(-nj$$bS_!_3kRknNIpHTe2^Ek>HT9S-x;?E=PV^J-bY3n z?>6<3G1kNvdy)saAAToh{%?;czSdKB(a8*H`3a$n54#&d_q|say+`$t7-aYcI;l&q zb2CPL!ST>_omnr(SV&pFe1Ek+Di>6kbNE#+6?cayr9n3Rqh*9@4BgkeRPoc0G~RFf zChzN+^9=!SoBh_D?YFe9hQO)P!~1aqNe}Ywi*Sf5>X(YKz1#d1%KJ#stIm&2jE(2P z&MI!*sIkKYy>A=2PQ`zhR&|T%xNShG(w|B%EV8RyE9%y|!VmX&iVZwP9Hq|BuKQyh z%#G`MSU{G{|CE*WZfs{Io+3$jU17*@8p`Tp`?1unnSPV&lPWnC=j5Ur#yGu1_edyv z)q>)Hk?GDsoEvt-N7WSzH>eHBS@0s|%7xq@70DT49lsrxG{PpTToD&h)xkE_afLDu zu&m|N^eH0%Qq=?WU`fK9l!9rzpMmsCjWl|aKONQiDXk3Vqb8?==LOPrbB^g;D*A7) zF4Uh4i0r{LNz$QQla_AzS{nitgRFbBP%EuM>wE+Fw?Wo*m&#StX3H=&ot&)GN`u2G zXMPKtyD-Vw#K#W zdkO|5S7WSnQuV?db2Q=xnd~!f51Wj#mMDy}6f2m19`3VdD$cC^KDyCVEr_Iysr8Ar zrduW>wXw@^6?-C!%5;|CF2WP4yL8R4Ge1AW8;Rb__|PrKSd)=i>0_+XEbN^7qzEfHiw@-w{MI&cdn(z;rzL8PglZ>v5-(_ zq-nW(gn3A*=11!V%~op*H05xg33Ewihk}(xP{3dsYl&B_6J9Yy{1K~|Yslf|cLx+C z*(I2BbBt%EMCrtP$TnhyT4M(5xRv{I8tM%#@-Xq6jlUJfAhKL(sAXe>v(T^0?H6u_ zSQe0%4_I)x=4f!ZsOUE0#=)?m(RVR4JSUAcxgps5(;*Tbd8F1 ziYn179^}X4L_b+)Ia3a6P8W|uta`^L9Ab`3b*y?T~-=1&7dsMS3n#F_Dq3U?*xJWu$TKxt}#Z zPCe;2ec{|+Z_7K-`?4Ndi~7^r^Z6bo7+goUQA}N`qE#;!2+(qyd{}det7LvLrcTg{ zXJu2bB)F4oe`}kAeB+&DrQ>GZ$Y+&MLFJBX5J}U~K7xjWw2<-`KZ}sbW?jf?8}S(L zMDOsXPjT?c;Q&{|N65iG6UOq{u@Uam5umn%-&v@O9i1?^l`mloS~lnoLU7P_<1jbQ zLXhNK2k2icd~W7Nko)8~0Tuehal?>WQ#bN06GoX_bMC)biZ1>75yH03 zw$4Fd*&Gwda;;s64Wxq&wELg0-t#~8Eb%EbF64t~vGypqa!3hCv|(#S%kZhx&w+J4 zCad^T^RI(x1dS;{FFVv=nn3TgAol3v`j}|6TVqa)Cdo+;oP+`aVY^nsX zgvU^9s-uCMAPZvsFlCo29wOp86gypL;0deG4&wufAN6Qt@}R`l7beZS9dKiwvP<}3 zvSx@LA7lxCWv2zAC;4m%pQaGQY!j8|>?!ri@ad}mGPPp`=^~GSO{83>|M@o?dHR6I z0p05z&cC@GYlX7V5A;BLS)E|<-0q`&=-vTks*Z@1ZsS%g(> zdcV2{%u1e&e7{Ug#i{e{^Aqp&3dVH>$A}IvWrUc=UN%KO3m>Qbg zfw^6e*V|tD*SlxH&*-ilItI*gMKQZ_2&*pXDrS$MBNFbEZ{v*}aN3bo! z5|OF!Hg!9Ph$%?bPwHb7bh?#4 zzM>SbFP0Z%G&$(iQid!kqnM0iKIILn zdthB~75w9HzSf1rx`*;{s_8W;)wv}W>}`vO>YES|Cwj#V$oGfh1sq6c5w;vYY>iRe z_4Y+w(iZDp&m(nuMH0Q}z<|4xG8KGQtXp_Xc006Xys7hh&fFV{POSXqRsw&a)vj+T zVfQm;N`efj{r7sMF@I6jat9XtLG^;N?_(t2tK)V*dq%+durB4it0YebOssn5UOW_r zL1&|>D2W@LM66lu&~|OP$_~mI>6zNDwKgf?D$jzRX{MMur=m@rHBEy2RG!%q-hoC5 z@3f@v>6n6GgX*?S;M88Q@t4UP$hJihr&eo5WOSgPmWwHTnE>14Tz`<)nR)Z%SNg#P{VjcgugI=uN zwZ;^3&M-J@IzK77MHDV5y_h4 zI8tlGi0tw7xlOP5O6!ZUdZH^)_U&{?w3+d)#)N4amFjrzv9&-|97!#A+dYPsXBcXK z!*3LS@avWKuNQdA<0peRQ`HX=Wp@FSoa2J2f}In9k5jwSqJ!10e~V~JN@;$=(v_P% z#x5c298E!qRN-~ctTEDJezpF90CO=_c&pNIgK?vW@v@?Jvr26(E5_UnQ8BKgCIjW{7~^{sK>YmH@U zjl_+8>67$|+}$={YiPR4LvZtjGzKvqm)e5n_m<4E@;X}dQFj>lj^~nCeq2i<{c-Jm zTA(B?gQLk;EiEV6W#8uaLhPbEE(<@V-tg(BE^x-V$C(N+Ez&DarA-)yjqrQ!6!JU> zQ@PBvr9#VNi|YN@e8^;6&igy`jekP^-=Wt@lunv8%vB<tOqbuc;MT5vHtXtX3 zLhqKh%zY^uqE{BX`se%R`z(d5c+53f(j-X9kahfG+3G5_mhnM`JqpIIeRdT0+s z@>(yZLvx63Fe<#=0$e|nK=)4~^mxWyUylNh(F|JBQL;&}5q~x0WQei#Gl7)MS;Pc) zx_;@+>Q8Jg9m|GEFZ(_U)x7Pz^L&$EsoV_9opo?chiVeGB+8q)y}%}7IAAhj@dm`g zLe!WTX%e<=?q)B@RJ@l3`}KgL*wwe*h$_#ONEMs+rKC9$!9!9z;`tZVv=O!cOYJ|X zI1n*mLY(017gWX+6gKek%dQyv^E3&n5b@H_%3cAvk*=VGU|e5T|IadQ%COL2WfA`A zYV!JC;#lHX$I&UquafOOoZxXF-jH0qw~p&uzvi#RKjEK_Hlifx-U*HIZ(OPv>|krJ zlVJ^&dl1G?evjg#23P(Us)Psr8eFHr@2z^l*#cm4_8Gw3^ad9!WgaM(D=hwMqM$E6QV6;HSafR=?O6Dl-MRdT#_@He!N4XKb$v2%=d~pGV7NXbson^otBca_kXX= zTdSfxkKfm+k`OR%3cS{O0(B#<*r*h(Os`C@Zh@QvF~*$lj~?gD*hUmZ7aP1b)e<`(0}cJw*r?uWneBzCE()gD(&E`!eq2<3=1%AEPtUt;kSs|9DS%nCC`R29)Wxpz$~ z`3(EV(YPBeeav?_3rWA0NX1vaN5lRQ+CUI9kUG9bcFKP-!Ddf=^4`eErE)O84~iFg zx0+TQL<59r*T!NP^JQ`+S{_U7kLj2B_bJqY7j^j;b@A)*k4X|#<6cPrqRol@ORk%T z%a=Q(k!#cnNQ@|BP8!3vB3|pyMP>XtJy|Z&%a`Ug%08vHDx6Z66QFLSQ-nkm^5m1D z_ISo#HM&rG{)Dxwl1Ijisq&zM^bmPo^72AGcUuna-|9)|N6`4S^~#SsGS;Fz_oEKA zN$q(bVLNrT{TlB^8#$@u9$PYcDN=PbPCZ^gns`MhYKX*w6b_u%uQS&FjQo=vVt2?3 zH(w~Xb;xk``pan47-wf$puDft@8}&gkXVZft%bft?C!nb@;9_W|2+z^mHP}IT;-d5 zc7WB+iYQM?4y_R<6s;xW8UO7`n7M%6V3i?w`4ZC7AxdyJ{ipABZ3SH#0VQ2DX=srM zuR_oD1bfEipJ5%1YhayY7QFZZ*364XaqNjbl+lY9yyZClp}=KSWGMs??vldPYrq=05||E8JH7qxiUkhf2CEWZt}6%9i5q~>#g52pc* zoB5h;Ny0t2OI@dH9D}n%+q%7cmLJtF;up?UvJ*McblpV;clg)yo(Qe`NdK@2a2^&k=<%RPw=n}a2VX9rMi8x zPxs@R)JG*?RStWl{s2Bd1dt#Un1TZ1 zVyf(BHMl=*pS1qq+K-rUD`Ol_nj2N;ZMHdBZk6;wFf;9 zo`&_4?aRp>Wp@&XA>u@wb+!3R2vREmbbZ$aNg0EoCmp=v7>}_0=G~e9njP_N>UY z(k>xr1YdTSg6ZHyGi$ACa5w)_u)+I;WP?)+5dXB`Lm~I#Mxw#$=L!)QH8m)fkkx`D zv@)WArz(;tZzvH|d{j1FuExx)_^K4T636Iomf@!Y^Y`D)Nwi04Yx6vc%Aw zXRul-b`=ryM5#(A-1H z)#Ei)p;AeAA>?MC8W{jQih;^+IW(F$U zr&;r|0f^wb2ry4zhaU~I^@_?+7dsiN`A?T;kQ^-BYR(hc3^k4p>?EZ^%%f{j`OI@v ztp!U}Y2N}I01B`o|6pnw^UCox*ywhMDt)9g>~Y&Z;#UdT-Dn3p3pHpCBy)rHKDd|d z`Q@ad;XW;Mq>lbkAi)6|*liv+cy@LZw)*_It#{KY1f@Ec~gnCK}$AOX9Ue zocXKRO7ndL<7i8^Zjn3-mpVuu`V_6JXd@3&JSmZL6L zNqA+o-9Q}K-8o@c7?-R>NLPX*f{-fE(F;2}U3FxQ*4p7hFD5 zo!uTl{tlO;Q^+bNx6#6kML{PHud7OB#9Mtek!GfsT0Oae&XVRpcAp#?XdXgGxN>jd z0%o&J%D(_)3ZD?mM)~ihDEa#}W`of-&!pF8>enoBcXzd>{yG%ly@n8ou0{pO_!{_g zYz3Hti)~YZ7bH`#Huw*+(0km4k&2^+&llqTe`Fi)RA<+Q`ho9>krj?1F2fj!a>{MA zU-i`Fc&%S&)`ei-Y2M7@5>z=v{My+$dZXKyNN&-6vMYJUtoLSH^#UJ=n#*XncW1r$ ztR@DkA2*}w?(Y(|+13_)W+!k|A@uPiRl%bds{m#c<%w zd2Zy;Ran1Hbv75n!IDB`*zEPf9G0Dh z{i?f&)Pri27-*L;6k3C#CEo3~x>Q9(auX3G@$9r}O)CP6q1J-zf_qLbX)Kg}vSSBn4nfJbfop>jvwtmGSm72!3@ zZpvM>L(Upo{XGQs;PxSFJm(=Ga~F_(xX|TiP>Tu*&oor&3S;4@G>4qrq% zd){r!%G=v`8Hu(fYHQhg2d0a-UMT&`aNuy68<>t53*(QTZz!V0OKgE+LfuanBbOj8 zA1AkZd`-YcT0UNCb)SZ!ifSjovX1ZzgS97(mnt}>+3rjKYS@W*yho@xShGBK*`>J6 zb%-y$gD{2vKzZ@~#6^T?4_2clCx7DhlGZGnp6v?EJSG@{x3#%|wLg&2Ev$c#rLU(d zvG-b`E{UP`@+CvEuVR2EM9rxAGy&!P40=o7Yu;q>tfE`T}W- zsf$PUh(&&}iyX#k+Utq=BkKrQSJL<&`7u1oR|iJ9I@x#8Cgg{ofJuQ@qBXXDGQ@H`X_W+m2;sH)B8=itM0{cC zLx7Bv2mzmYs-V%|6_gixn)4QvIFQ*bIi_d5*DhlqexdMs+$-&H<~DrMV~I;JzlI;# zmxo^sv|+g|8zkHd?`s=iYuv-9C{o_Nz+0;~UyA7(_Lh0CFY7GY^&_Hd`1nEPr-Qo5 zU%KmJ>t-1yp{}IgrFg>(nS#6oNZvIc_ytvP?(yx^pe_&p1iij{_=#+&=n}%zy#^)C zyi4eRG%luTy*G(XmyY0-EE0#UUqZaAeV9D+rmyt9AIZE&z+G~1CSmDQBqX&DOkjbi_AX~Z`xOe|o?nGhu zmDndEvuMy2h9|x2ab}Iy*EMO9fE+PfhzbFkhmCy>ExifWFe) ziunbp^w3exk|zvgWFki`1(m6*)^!WDdgW}98~QwsbMd?hQ$rKr`eQdwBmI1DH|>oj z-h?C%?Y8U<;K}_(mhBC;Biui*u!V)3mkj+pndR@@BO23wf;%DM^prZ(37_d`zm~i< zl)A|v>C`!A?Ix!IYD{SWzR1Y2ELpaSBdVi44P0b~P86S{W zPLxNGyujEsu|-Vl1!6}TDvqTHjim%JkGXi(POaXw-0i%I#>&(y2b~7DVOpYs#~C$F@IB$+pm0#o5QEvNiF6&r#Q}PHFKxBAsobF<%uEwGi$2#Me{7 z%KOlsQ+#YPhf0<=Ry3fDAijl1QexCy$Tl zZY4OiJDH!#ns+XFO3Zifx~z8;^7Tv|T#~r=A>x)W^R<dyC7TqkFHNfP`KXdvA2G5!v;R)Cl4zhIIRv{l_$n5Cw2 zmMfeHC2B{bG_rk=c{aa6SoEoWl~c&FQ~qP99Cb9vOTf6kd~E}F>hoN`F|73dV^|xKJKrMK zNRcz{TEx+%B`OSeb989RK;7hloZWGcdrJl%JVr8O|j)YEht%Y=bWgXtozT+(H@aK7WK*k_X5;DsO3d8X*KJI zDYDT6x+xLOXa#8~%cqH8iqiUH-hf>u!Z44RbayMe_rLQhdfy`**4hMuM_u@5|HAQ4 zGkeqI&1jbEVL5QTqm`t2A$Q%P@%}C->bO% z0}0%sfZNEBA*HEk6E{SzLTXsn6M2_VX%gG#=W$^tBax@_AI4<4) z=Ge=Q8P^d^rO4Gt&FujkK*;{>fWvkRGHBGP;Xpl7Obt_QNA`tb^g+r7Oh?xux-Th9 zP`IzA=e{gd%29@d3_5;)pzr_^Hg_bp`0FH;B<#fe&)479h4KBr#h3 z@m=R z%jg{Uhm`S>WA?4g>N^lwWatxbMX;fdRTNweM&>hg2z;aSqELJ+1>cJ(I8=MWq|jfOrU%! zGGVVptY1>L)U~=EPhR=xXI!}L_3)kYm2uI&arujKL#xS#5P?-a#nX~L^bbYUR4d=G z2@Rp(paZiug{&%XxzVvQ3YuF_RwtK2_|n=8zP(y&#+Zkf8K?=KCV&9)ECGi9xFip_ zodD~8)DLSD$SmrapcyoXYNe=OGVO`6qfx2ud-RN5%*^wGTf($sUd3>@Ez2(A*Xw&h zWl?FGD;C3qU%|z)ti3-&mpS&gk7VMq@rmDl^o9`b*fXCF*5I z?32QlJwBnL3G?ugRh{AnbAX+Gdy(1?L5z+ntT|4Rpv)s@0>$sL!19|S3qhs@LhV59 z1l>21GN`E9IG%(Qdn$(SF&>lp@*TXc*jzefU$OO~N+>A(Qcd8BVhA}YrThz)# zIW#J}o!8m7GIBTAuNI8aCVUq2 zsP%38@Gg;6Eo7m(W-WW2E~msIwMTKbB@DAg;V8$HW4=w!?Jhq}{?bTFx8h)9eq@(q zp%U|COx}246s7~GE zw(?$(s;-cWqtib5z2LF!UHL~YH^vErjWXLe#)Mvb%fj3SsxMxYSoP*+FE{A8XfS%> zoKTyD!G`!wU7j63utp_|hY+4HXxeqUzV$l~G{-6rn#&P$+EWHgpkI3UY0cT*IK+Zm zLwa^Cc+~;*X?qs0RV+R}^Ukb4PSQiOr1C>?nQw7LU~%a?!KzP!weJorT@P1-PYekd zk03n`{FVsoi7^;q#VXml>=1Z2M7|~Afp`4j(8f;FIE#0cJOOx$HX75l?OEQkJzK$A z(JC5CU|Doa>s5FgmBS-(0Z+P#oMlW_9B_D*)x+HcuBYM&)5(zF;CowxDaoC_%QiBx%7}x zG4s)_u+9om%e_|A$=)h46$cbpoC5w$2%{~uz?UiLHCWvvtQ#jteC_3u+?B1)yXlFIXN#EnQO935~vEHUZk-4wCZeT$vmOO zZndrUcRKsfJu26uZW`3jJD%#i0w$r;Frn55D^EshUh)ykhqtWShunSCD6|yaMAZ8# ziqGHqsJn^#YT7C;IbraCFm+{(P4F@TyH9%v->ff5cyF-CY!lSe+lqV#&u8~I_|s){ z{L^LJykmhbyM*pQzt?qt@PfEpFFRrPSmWflHinA{c!vZX6BWX8-+(6t=VJ&>>D-G+ zTtzS`b|fhBHD=-TWwM1SN10$<5SXs3)1bk@F`20yzOK1wL8i5b9bs;4HZIB?YGulj z;`?hR+AtCUCiQuxTg9+{0U^GTS!pmS_yZNi_7lhE?PKuK-nMdgRkGsb&K6#r1U~Ke z6y>;_1VSqH1(*&E=L2a0v}vP}e=t2xDM>q&=>@Wn%ED{!exG89zwQZqB`D%}pDI#- z`5H`|P#H;q?#~B%k(mQN4T>@cW8Z0Uw=^;7MIkE5>s6_=;;Hb7v%}xRmb)C6r$HE% zl?dZ$d+nh>h>w}w5{}vhM|M+r5i~9neDn#=iV!DA97cy;syg0}5Q6UG9J|=WY2kqA zEmndbR3-W;*SPvZeZkVQ`dNdr{ZFH^T|*pg*PgvK=Ny_iu*fBoQ(8CF%x$@$iv&M% zt^F~35f4qUrPBrR_Xf{FxN?ZkFZe)~3;SBtti%e3ewyVEY()pW8 zy&b8`ol-eUIf>eaiTS#n6#lqAlra6RR0HA}Y;Mob@~!x0Za&oS zZP%hY+g$(gU7#m0P;u7z8_@rQVt%?DfK#q>WjLn@_2C8XS{%*pys&Kc%pf#+OLHif zY_-F8`63BFBjjMtY+1J2<%(w58VlDP(C6#Y{Ur))p#}4exv%pjb^AznM9 zY=C$$=hHTZM{RDA-9Vm87~!a;0iZwKXqNviAo-O>lhwTS?OT>60mEKGpw4PI6z2yfvh95FnuqTb8>vc z0(&IZr(1f}qCQ2ppEkdDF#DjAhZot(64^hI&D)xc{{qq>tTX{z2g!GbAG3OS3&9_r z;~LcPVh?gPceNTWE@Pup6s)9%YRpxgR8(}BI0Y-pT0s+Gr>UVVGJ`X)vl*gG#*csH&#$A@4KPM7arwR2(beb{lRg zD~(!Rc?+e|lR>$Jot7HClHW5hp!R77|5lr&&%*w)MVN<7&d7@lwBw~d_^NqE*iACD zkw)UcV0*E}#J=7H_;e2K$_FDDy3zpaFD!+)8E>Yj~3c(cIQLhzd3fJY4X) zgb-f~dxV9hNTDtVB;hu|U6!rXbIy5^TYw6`yMgTg;3KO=f0=Odz!aJ^^Kv1afsYq? zBpm5H+bIWwSdkXq58s3~%eF3@w)r~-eG&UtqiNp4WMQ%^_k36A0$VyuwSHIZj#8q| zpOHC+<70~zXG$q`a_QG1m-n`30nOY=5CSQ1 z92=ar&Y1%__=^@4m!xuzd=dxn%`Ys6^fLrpqc$yo^^XpvPw)|8iwy-Vkk zmb}Zz$0c56N6O_@br>)Ks{U9l9pxsRjvF!U0tC*XU3j_t-4g8xk=SKz z*;CDdjhqd+)6PUeHdqle6_j|E-#{`6QjJ)0mfS(#*m-3o|K zhXjbk*83t`Hl1c`o~91Wf|` zK=aD4u;mp@hsWjg`xZK!!Iu$7`SKwTTWbF)^}MQEECwC*q!;fmSy zV{@aK7g#&D1>XwYhl?LEY<*ID17*qYvfR{31sVRF@6d{mbpcjMe1GGJ5b}iVJwo$F z^|^EB@@USQ*Y=vggmPu=-3y$_65n>cyC0f&O^Qj14< zk!)MP{pktr2gR@wIpoSky^xN~>+_(@1y`=;Du2;Au;_+s{t~OO zw0FoKu8#L(@r%cX<(u!tV~>~T-O*p-u`GTblp)j1G|uj&Ak0A zHPCBgX|SRptRuX?oN^Oqb2WK2g(I=Iq%okf`~lUhVafUZV6U>nVISQ&+gmTGG{RoU zc^6Lo`c_lqxjzh>F)^ElErBD$S9|+!hDVY2^-z9}`LPqfA~J4U)|+-O-PLcL48d&i zsCV;lSlnY?b@OQIpndhqs52a7l(jo%3mz4=-cAZy4DN{ZYI4rwx36~zsr*!`r6;~` z@m<@Ru#)>xx4gN0xcuO-bh7tG#}LM3Ga4NeUU?e=VLj=sdBq)xf%^(SSFB!VR!e}3@f_ly7 zf_&|3rqakn%A~O2H(gncO0WBG%IRt~49LM|Wcvu^tGcn_D3-qY;hV)~hEf(T+PVmJ z1=%?7^5Lgd9(qr$Q}$Uu%3nZV-YPFA-7S&tV+>1~HZ!A0SpjEM;%^SjBh4eit1NlU zM9yRLlI4GkcGnL~iJxhXk1+JKxb$b}?+xK=(iDpwF?}v>C1sDZY zGJDR-k4JUycpVY-0w4a>1KqAC!uxxEZLBII6|f^BUbeh`=e4%FWIonkiBv>B82o=Z zPT*g@e7?D{qNSJMZPU2%sWh3d2~|BXw?HwBvmLBpDuZuEYlPM=3ZaB0I4Tg6A~>o1 zS)Ca9PAP=yhPGcq_+x5GKKF~YYO1%{GwY(Q!*@%Chq7^>_?z3VSwBXpU-v4D_9@9$ z{fkelX(U(4E3BTcs$JcHLX^{mBgv|Dq4-=v?l);(_M$H&!-ZY&`aa%FLH!xQS3_XW z*}db9BPye5*cx_b=iSPasz=E?N56`Do$WTG*Y-6+=M5S%n}@aKK(=}qMD3^L_35`c z3|=PMDt*DzbhsRR*=wOCfFK}8fGF?usYx30i2&|2`Tclx*DS3=SYXJ!u3WSN(VbyH zhg;Txl0QSZZ(S~Kh(Hal@E5$^>0q2~?;(BPl&1bD`wuQcG15I+;*K$O>{ItHx7Jf{0@n}4?!uNBTnJFv5Zj)G2!DcL-WG}2`y zZdG&~ayZK};Ekt0Y!AI3cN(SA>9PIdIerZ^{#8kH{;%*e$vAMz7Qiuaj z4_(Dg@H<#IXk6Vob%F8yi0;1#T1}TR+|+FZp3kkXzwNXBcFGJq5?72G&$Bi)H`2Je zH;ubx)01rPL_~PqVJ5054vf!nS6) zdmwYIB4xMJlR~2&+P!CwzwEv(=-aJz#Ns*$gk0-`nfa;7=JIBTnZ%DVrLV#1wl9e3 z#NgTe<-omdFxqgr*a_I zc=1Nx)SSgi_vZt?LlONe&Dozyt=RzT*1HS>fz91|%9;Ff0SPeMkUMhm-^@lQL{XVU zdwnPdcS@F6n?Ad{m%7khI+#<=G-6_AnyD8N|NSs~$}H0b23-wJwGOXX?vW_u)qbt@ zS(k>bLXndaN{YFPx~@KiTw*U%v_?(KB}Fw zZ2-b47-(W#%W6!l?q?L1?GNv*sNR6ih@w>MErym9wt zN5v0r#(kV+AYBrZH~P};(OV{u#X{hrjJn1nYLRB zL2hWXq&3z|U+ni_$o8jgQ3Xbui$$b-#d8*V90aXiNCbvHVi1;zDsU>@@}E)ymFJ0)`oVlE0y7X2J5kE=ArX=8ACCSxLA z+4BPwW-}@=g1gr&++!F$d71TW{M91nuepvvuc1bWd_h|trA_dk@r+l^k>u>JL&A;^cS%r#Tq zxO)X1qkqNxfWn~CX=^AZE)@@6hxsLZh<=5Sa(C|ahsV@QfJNx4Owry4x%FFgS8d!O zRGhfLozcog2YfcAMWm5_E+la}#ye{;voCAcrVGhlS`rb=K7&URq+l<-{YM&z%ekoo zvHzz3(Nj+Jf2E71s2cQZbCDKqFjAim>MLinL_QC|*HlFt@j^x(i` z1U7HV_MBilUda7D_W^rmO0N)ychj?BAS9k;z93yB@DIfx#sQqxKrp5uI2{g}8UaFo zezXf%kXPBN?%_gF=#~tp`CeS50wby6EoTqP{SvAaTGJlES8~1A)d|bgo+}Vsu>C~G zVm=h zRUZTVZi~~dPxmGNxJQr9{+v*;_05L3Ymv{Tgj14Z&pb<#kIPftx^T;EG9dgiTC<@+ zQ{W0S)s9>0tT5STrrWZwhhyFvtGF8fsznZM!_4Rh5&V~CjuiLI#n+!E^=if>W-ST7|bAKX^YGlKd3O`B9;uXwDeLbWzJ0Z|phjGQw z`*dPAJ8vJ7U##MiW;Z6Y)jMad+F(#BI-T-W{@%`?fZ5Y=MKPNAKmZ%V|7m0FUV;*D zb50px+-P}UvefLP`HAyFPox^RXzBSdnCQZzXfxEHlgOg!VlVVnI z*7*4T8nzsFkY#^fUxkt=FXow$xSGU4F5cNZs65Uj~1_}*d#yr@p9U%PqrHjWPE=DFnvU& zHlHR>puqoSwc)t^RrxTcG()T8q3TL^;z=6cCL8CHU6C38vZXW~VL$SET3nQnHNLB( zEk@9~OHQt;y~nnkKdxuvV6?DsgeWlmmw7@1^9Y!!y8?-}ppJ1RZ;{=!35gi_&X(W> zS}iMTfV7HoPDFcZ@xw|<{}rJRnYZb9Boq?jOvNZx1wY62uf~U?GSgQ2e>wq>*xew>f2mr;>pZ032(r#o50X3fMhh5|Cc4jF|O-3 z@8JBU9uaW<3wo~X=^-H&F6&c5t9Emk*3LNwuA>f%Y#T^#8tHPI7!acaUcjnNe|-l& zF^~3yPdL@mLgj(qgxk*v_uPhH7jJ{yZB5-98TES&!sz(eg_b`BMLtomJ=Ux+v$bjX ztOSqGwAklZt@u0jc)4Z;%Qo~c0)l$OJ^aN6EbT(J#guT6|tj*dEq z%V$b`(FD#qX8qKg6)HNxSqz$qam_Rajb|B_^?&WGM7l!oTCE&38}9DCNZ>bKR>lQz zMQnmbc!m9Z)p|Ou-W?0-VV>QQ@{wR37c#CLSl7`+dD^`9MD`(@)9tZxy?&r-+0&su zlez%e-T##H_tGWMm=svFoK7B3Iu5lh`lrY1o{7_*a!I}x^{6abPeAU(mpH+jivH$d zUn(=dt))>`peUPhNp5NysXZ({zH`g>aUA}F`t^fREs-cm3N=(c@5dQPj#lm9DV5fQ z;nqk~vD0jbf};DrCM`_#=Pty!JTvy6PJ`s%nuH7trvLJ@I9g%xvp((JLGDM)UGV2@UOKkEx^Tk7!INPqRABx}pnEJo3iBEEqU5>IR>! zSnz#8vzCt^&0^nos?HIXw#0c0d5V}U#t*B!Px{k~QB7*CO5PJgZLwPwT6*3a>FH3f z4YZ%f?UtF<0Aysz$vW^$)O-bk6^NC$!dGPI?z!0xOdO1pS;e_OPHe9_S_w(NSH7vS zx{}F^g^PLE9vJn^e>Ah%YjIwiK{X~g(l{`<&|)V1Z>1Z~K8SFf>{O+8I$8)v?DU3K zmge9#H;2_!B!mfd^bm=-%Eojr2he+ia&Nur_d5(xL$rgpw~mk6f2cfoZ+3Jbx+-q> z+QoRToJ`P{K6bN1=E_Xvk)4+tq)sp7|Iqc;VNq@E|L{>!R7yZZTIohwT9A-dYN#2M zlx~LRAOh0T4F+99*D#Do2}4K@HFOU!bThnz=lGoSd!Og~2VP4rX7;-8`xEzCYwzu_ z;;20AWED>Dv#_(iT9k6J$7#kj4+=1yIfoH+FCZ2WWI87+VBPVmpIVDDy|E^#&rQ5A z@BH>m0=_Vo33yyK6{W1Niqc;q%^al-$#rQ?Mw#4659S}X}@sYK0B(ztGPv=dNK#IzqW~`^Ut1g_chFSOzsbp(9n<)KG~b}k%HH6 z?&tU)K^&;uq$K91jvpt#Ss~4*ad4t`ruU$t4%k|T0P!79Z01vy^BvE=S0Rh7)|D|^ z5;B?o8(T3KlQ$+T>F?OVE-ws18F0;yb6-mX{&zH!ErZ|Qj_C4cOmPaF@3Y-p7MqFB z`bhS<^~mUcQ%?qERm*;${ghty#to`HL z?~F5_@#>lv<9I7~Z}IfbqLxDKU0gqiCtDVCOb@--ONY}tuY+a8YEW>F#m&&8elB(M z8jx?uupu;T_;k*s6kt2ytYzD6RgzR_AnIqbi)g_9|;TV}suPMLu8=Y2HsqhCI6T%*y3>q9uQV?jgFMi3;)rHXecO5PiG zOo-M^)2G-wY*l}}iDk<_gvLBYogZvwYs%b$-NS2`2sCyvPiMy&_2b(v7kDmRvG!kZ z*bjd6;dl)2h`4(M7ncdaLSd-@LriMV1I5KqX|Hj$m+^gY{eQRm_^8hdD${ct`jnE$x|y5`LVga2V?w&^4; zAprs(BN(Bru~Aw|SnlX^il-k5WbZBR-e_R2l%O4FcVFzE|6vk%qv0N&EU#$OoVX~c zX72O=GHS1eKo(PIE7(ZwCP~>uJzeWP-PLooq|0mpQdJr-Cv|g8yfs)!6c*S7<)|pf zSue)8ECS{W6lf6TFan*B*w>Y~vtnnr5HWajlP-obJFZR_qBPA2GcDTp&Rv2`9-~bP zYk?~rW>=FOxw9ysc=+MU+21`vDm_Z(uT-MN=`*g>hq0o@_{-~}#xj?FHnJ}K?v@Wn z?%F=8kxP3~>LXLFQR=z?Zyp~mq31(b;>6-=YTGI^S+<#g+A-$cItKml^L(*gE?4ue z5>dMu-!~8Mu%5M}sFyF+cLWa5==8OOCSh;X+2b{eL3!_B;evhd-I~Su#7Tdw&qXcz zo3R}fHwnW)|awEXjlelY&I6xgv zZ%tkyP3M;d-+uR0@Ap7spQdd2^+Jg4lE{RU-%&Qfq4bV8*i!#%k`hDVlE`a~Qjq3X z0|pe1yQfalHymw}YCvI`anoG3_Hlawm$|siJ>BJU!QJ^zL>?YxpHgVzt1;bVg8RUJ zF>)vaH5`Xn%up%ir<4+IhwNcW?VHT zl8Qx)D-s7rYKz<9!4U!Y$siuwG^Ug+g@aQBZ7(Iq{}K>8vK5F-waBiO%Od1Up(W%^ zCGPs5B>-+u;h@D&5gzGHB(dkXRSATwu8*mV)Y^8s{3=%El{mOQt<+p*3Sd>ja{f{Y zQ^!ZiwBxkV8}TPUvb$p8cX6J1``uW4g@`fyIJ0RkyeO=o3Q#Hw&_YK{z9@~?HsGl- zi2A(TGXZ@jagDF7a)!&+&ijBv7i3^Vee#v|B=IR{|Aw&Iar@ExNnss-;gF&WQ<#PZ z3#_?5YMm|E zH9-V;iRfYSu5q}`7tg5Ov)h9!h0`XM$}A}#W63`BA_%l z1Pc6z2LBmoR1+bsv~db-W%AIcRd``liUD;|p;GF+qFn0eSPd!#Ge|yFvQHW=;OL1iJ>(EQn*nC120TzI_F(492dG3f#nWj}BQ&qM$#D9yT9D?=uggH}F)wQ73ErX0?vc!`@Du>D)vA=GcG8=4@)+dXR2n zST)yp^t@s&%{xv)u;f+oQ1bBn%=cU^gh!P=&zmF|$e>c0FaF0N#TCS&&)4t6%xh^Z z*C}VnZ7+%Qcu2Ln<$fP6@ex7UH1l=WmSS`^-PbxGS_tFV9gP0Mmg(pV6a^AmS& z(?a{}+$PgUU)O!QokE@nkGzfMOYnS@tP-gJspI?9+hSx%yQcBXj!bXj3B^H9)C1HO zKa~g9+RNi8dG2&PD`9SWZ23sMdzPsEehWb$haBg-g=7faD!Zy*I@I;g0Fja!tSN&e zz5mGuNq?1|ZJjN??B;}&;CXbEMU?i=qMvImpHINT_sl96sj_{+VvM%P`f<ObHrp z2l=2MIw*PcVeA?ufpId#nSC#(2x33%gF6RzohBi% zN@ej6*Zks|2W$Rs(v9z`($@8myr?MZbB&4K?J2BWF7}|PdB75Vvn*9Qtc5?8v_e*S z+F;%$%cpTB>15#5oAm2ilCHOmhnrQb5>`O-`nGQX{Ig%Ih%-MP>WbHW%K0#s@}upw zv~V+ZEe8tHJt4>@&e@MD5`ScYI44tTHng3~q##dSaR87eBs?^Bz-T=rOyAF607NhI_QmZlB3;0sY<#nmTGZj;(#2Uwrq?4t?X#?lE zrNb;S{#cf12V6J`eZ0X4wkI7a9)iXa&#6(?sZq7tg`c*TfTjUMl;pTvd4Fp4iQLw-OgZ=`V`vL zRHhFA+mt@gpDfT=_Omd^pc|DlC@?8;5nJE z;MDGkgYhgUT*XS(N?xe{kkg5!8v%JBw%5RqY||~anTf35W?Z>O*($<z#_%mu!nlo|D}*_7jZ!Y*>0TlZ=$>QSgVQblJAu zKC(7WXJ%6hG4hJL#y2Y%O~drlh9?EG#WbyCS31<#uzOSkUb~KeEJ@B9a&2P(x@lg*u!cu@YEnca8DpwDwYRADLv?Z-1C6lGbSu z%}B6FyN#%rjutG`CX$GQ)|7O<;@M#mGOI**jcdp@-aWdFL-I4;+wX>1erGhMEY!u@ zJ$EK3T0UQFGR(v+s%%V8i@?8SStwwByqDfmbE{-(E9n&T<)HT@>NO3ENrC~X=+mk} z=9J-7hd9`~>h&aRwsQAXw!3*uwu`!82JU*S{;Vj1MJU!_=TL4eiUhxs-4_xDPFNZhK z_-ZKoF>M$TB*0VUty06@**N}u<1M!#pNG(DVrt=DEw7Jn9mU0wA;CZZiSjH~0%NUX z5RnrS&(|mu(Gz}0(eLOJvkF{}tU$0IFJ64Fsake;xAz5cbrJDo!it6o&2u)Jb~NIf zd~0SGvnK_4SJR;J$h{_&BXTKkuOpxsTB{NF>=0F7Js#4NpNUbA+wy*WH>IGjV4JC; z%cmD@^~w+#gg_Yu1NoX($_jlg&TYhgiUzIbqI_1|2^o`gg-IhQMLy_Jn- z7agRiYHIOpb?=3(dn~nz487|pA8O;C?tf&t<9`b-ho%PGmF-Ii({8-`RLk)j694F2 z+C=PA;q~K1zv@EhhTnRd?bh3$hrFe4g^aC#yv=bC%@<0$08a;e^e_{Hf0iiWm~14@ z9eBkO?Y0tM(0<<)(SK;;%>@#gT0%fsqD{R3h9e*OFgD~A$L;E`xA5M!@wr0xM}l{c-0%DbdX9`t7vw>yNsWNC+e!&cyMQL3 zQ*mP8k+-m9dhuh(ypZDlOl}QuAb>K{(V9!Hyf?)j$of#5i+`+%m-+Fe?KcJWaE^hl zW4)#q5VvG-t}YR33H1?geZUqMMkOwc(?RZ~;xs?6_4m{i?pThGr9ajANr`k{y!o8hr993W7w1znn@lUU0?E@#`Uz;h3)%$ zbS4JIB!b0vNW=~G5NDXCS_Igr7b^kTbXhSfkBZ}43p3umiV?h=4B>D&>=_q;kIKax zlC?*Ej{i)kmIHVP&>j6%te?PXT)e$oP_5Bi-^8u&ze3eA0U7o@X{tt;FXRF(_00_Q z%yi<1!r^&r9$w4$#rRCV;`m7iTC)avZSAr-@go(AEb6%~nHg*!jZAYiWy=~hC3wDK zz@C3HCaGwGzP)HF#Yub(-EaOi;<;Q6@nmfV$J_UZc`B-H4Tka~1$n&bk&W$1!fkvL z1Nsy!82H`fZ^q;;}!>5#1EU7Ua)tx7v`!Yg^L)U%gRZCk}~I#Ev; zS34%t_T4gI@L_nFBwDMv+aMV$qTYl+ndT=0uHzU zx*HQ9E~6Igy|@so!7>H>#4`eCKFd?NaUqEfL`M5Z4G|iBD_My`Q{Nxp2ErCPWrhepf}O} zyHBJ4qnGn8{d~!ri%?9!8j^ zIa66Qs>#oRvB}UlHQI5btS!EWfF^*}jHY>99w4Bmy3ebpmJE`2bve+3$o-*mD~$PI~_l}%jC}PfXFI5Ljgilxol3#Z0&ZUzbderxjF>R zh$hLEPm1HIXi8s(z?xJcfxvsi;y3>$$2u9#wgQds;T0+e*sj7Oa)Ik`TfR-h(R(!> zebkAX262|4TvWiyVg#nT@dE6p?rm+9+_*XpN*f-`OYxY_Rj3Q8J@pl7 zJdmh`t3jGUa&x5W{!${uhHo>xJ@U`{e-p!2g=;$ZW1*hy8}8E8JD@@2<2zw3gT#=) z5T6^#$0csiT{XYhn4R%K#~b?%sNHxgF$!`Rjt7waA<%R#@*7T_{r-nKJJOtDc5##M z*c;@nn+wgFJ(#8qnZQ@57_7;B@90$<=b{cHARoL95IlH=G9!dVs;K5JjoGW6E`L*B9kyaO0N z-P{fjf$rn+ZiYbT%9=c+6*2NM_&WzXD-y8M&vcN6CR?Mzh9<j6uy<2{sd7&CyLb+ICP$$r%oeH|)@k+$-x?V>aAT zp?a7P2uZr~k=`XBZr&V)12E41MnVhYceagy#3YY)(_6VJDyq=7CrjLjF+E?i>Uk;9 zCYxs%q(PN|&Z2&8 zcUk?6pYl|9i@F_Xt2I|VZe|sK#cAfvl_h;6AJB+^);##j)|0>r2w;w3<7A@>t_F#O zo6SwUj#(0Zw^~?Gf9Ky4#P&ipJ7I0f z!`JG$L{<{`NMoR`DII;yL-r;5-fyU@{?)9Ns+_U4Y-2$dKu55rCtN$%K5Sw9@rgrd zM}UMd%j?Oh0PGwl;1q50075Xif7Wlgxv#m`n{ryJxxDB{P8=;EdHBOsCR$o#w{2US zH~4v%24gGb;Bnb7$&)UjH==nlu_GHAYIzVdflNgjPN9v>yy_5}Wp$0Z zW2g;h7{DN#tP^p;C*|{I_C(ey^nT<0`Yc*!-{FdZ270B=MNdl~x{Pwi-6Ot@%-vp9 zpFE3W%j2!AW*_fHlSY#?nG<%Gf=$j0&x{Dk zaOa@)w^SuFZuWP2lT32~su~p^j5y*=JzJSXinagr}2bFJi z+EuV!R39;ib4fL9rm(fOtnMtq3w=Hr{HDH?`j;a;3>mTb)xExcs29KKs6RTKj@oqj zYE_{fh#ufKn6}1BSO)AacLXzV11nBZM4xwLl?6T2XQb@=BW7UR7daOTBwovrV(VF= z>&GAB;V1n+1%vvDnzlD`{QxMd|I>!de8F@x59O3Jb4Re#1#DC=gx+!-Z2z1He!;r3D^k{if!W;B!Ez??_W(Sx)0hP9w zI%GIm_;it%iAoDg&8_?Pf2n0Dw76T9V))LWcOfxD=ydEpgH6d&KpfAd5#CT*_Y-yh zoz#c@d|{v&*qEfZ!LUcOmG{I!NB-$xnGwo{M|`G=4|fQaKOa=rte=o65;e2YH6X9?-N_jI;}W6(_C7MjS$IHMD$F zW%@oVcR(;t0A-~>e_F@y@+_h#ts358;AMg{UNDs43I@b8yeJ)Wx?p-@3h^vV-Qy+zY033F6$>l!SFW zzj510_^Zt!Y_)Oew*_uV+XQsx4bM%@nteUeRN2$KdwN`&@*}aBTD3kTQ$%{MaUfn) zkWiazsPmzXaEyWaN@9kv3uDE?cXycc=kvP;Shn$9c&-g3K#VyzPLY6tr*cv_^WwXw1U z97j*>VsJ2v;>l0tCnUcge><;>Pnvw;ctl{oaepJxzgHRBR9Q1r=4CW<;?%xgqbkDp zf^b@x2_b=8==oXf=Ljwpttwgby0P(YuJ+Au`Ae4lwMqF#UwzAeuh4|Sqkd!)wyOI1|BmfNLl=Ur%a7Kf9Rz#)_p?!7c5 zD#Ggp@shkgF&^W)detQ!&LLw+Tw@Sg=R17m6Fmu zX1G{*YK$qY@ME>e1;D~Sqd&(Gdi!Y}GAuSfMAaHO(%h>Gt*4Xm zJBkFhgb|4F89=?;Los%l$+XA4s-c~kw68XF90DL!3Z?cImx!xuYy4NXZ7QF44Z%~8 zBl^>;UTsY3H9>ivH4bB!s~8MNgp&-^mlFBSPtURwXvsO?-;*w_YXt^y0#JA*0LQs%eDlfv z-kYh4``x?CP3YAW$)mg+pkH^w5U)e$P+AAV1fK6*D<=ps%KnXM;BNsFZU7hdW*)|Dc{EHH|2R1=2qO2;oZ**{7803< z9Wk0dU$DY_N(6%XK8Z9bl^OYKla&?wxs#VMnN`8$n=Pnoax8#ejTW3$ZeH#8-$nwW zDwMm>4-kJj(3ndcm1)YS6xPXfKn;;-Je4l`apDbu0w-iD8x~&P#kl}l;J-0+)HJ%}0QJxvweagBk{0p4 z%n`cG`4lq~9%kIIjv{jO-(63}S~pcn?EDEh+>`Od-8?*<@*a)Mk5sNd)I7xoCw=;b zq%{GA4rpy5K0|e*Mm4hHT(e>+AV{P=(keyZ3Py9u8K5@4q1A&6f-^R%c#n(I1b%DE znguDB?8_D|pZRN`nFCFq1B9rI7r3=smp_&Po_IfD86GfLF|cwg`Jk}U0a=DXP2-ks z4;`d_C;lI!U!+Ht7NL6`z_5vHXP ziIg}$C#lYmwLeZ$t|!*`^d3D~*!wvt!Qsx8mE=7s=4i5sR1Y|-ij&dj{LAQSd9Tdm z3QOkx%3*+^wS!F^6)3UB+tliaiJj`aopX`}Vok}}OkoQ?sZ!g~zK`!^ux9_4P8Mys z)8TY-^#8AuM)8*f{_UN#v$%syYNo#qGW{eIk1jhEUJOPBg}>-4_#n^B#_>hbuy~}_ zcO=Q$C=ZoVYcX*(uusJex~og|Y}>-w*Ivjoa(Xa-`M#z>_Qk|8TN>Ke&=)n?xqv>! zPGgWV5=gOR*UbLZ{-K2(?}J7i9VaZ(s&D)A5s@vADoP-URv)&PS!y(j(XRq~6neK0cNWm-%Y=(L9Ymh$uV z+4;8=Pq>R~;1iKg>L}62y{!eTLUOzOCim&}>^HLLG5Mog49QE3Cr_WS_O}!<3*>-i zB*c1KrC?(dqMt!BPw$ou6tGVBOUoeIB~n2R9@=jAOsXAIpQS8a&t`j)_liwY4ay<% zO&Si8neG=2WaF)kCUn+x)Yja+6J5z!C=yMi-6OOc zTd$iXZBgDnVcg79%yik43}wz9`{QtcJJ}EMB}k6xe4#0Rb8n8_#5|jX>j5<>9UfY4 zlwg!p@1`DsUd4p|>D#0TkDDzOP=Z#c8HDK6^-?wDOI-b->1~IBu5x>3SAD zm6lyE`59~5*$8@arSsNX(cnRD zLJZnH;Xm$GnwCdPI;VTrI9Ir*bv9c_@*6W5i=9Y%5ywD=hRR-JTv{hS9~lW3sfF8V z^k^7YoAo9g(1<`#&5M5;g#fR4@y1?o{TUS0+92!lxZ$Jm)2C0TsF^9_2)crC|CsAl z{9SC4%c4E2W>VCCs@CDCTc_W+Qy@b8#t1Pg-7+S75;j;V;jyX962Ywi1^5yu0ckVgH@zU!I_-H)B zM}!f?jD$g7-@kcp_w9+C$oD6YKI$qrF>IXJueZD!>1e)FKErmht`Af0F)`QkQG(l=^(0)k&@X+<-40p_}KiwjvA7o79RZ}bH6jD3{jK5vxra4$dnY{%3`>0a+?idoz)7b5wjS5f2V z@AeY+p0rIcX?Xjsk7zA>B8AVisYHn6 z1eD}kWRUpBrjK0l&)X`+tB=mkO>I8`(v&l;okNja22nytPYHuH%9lld}#4B2i%SAiAQ*95Yn`5obFja!VpXeH&rCRq{?^4{8 z2=Dg4x6Ca+!XfzuFD)&t1JMGUFweXM#!Gct^3J9)2IpKmJ~IN8oT#gTm9VyR2z`q>=cJ2qdk>$-Prt;L%s z^abb~^(ri+o*I1&t>X@sl&lAROvFbLRPO%3rDW@$jEw##O}xFEiD3~hurS>lyO>!! zSrPwM%O4#Iio?y@&_>lbPGcl)bt-yVr7hZZMgDX}TNzrIb1WV85OKsJQmCM%GC9 zVX~sY8Sf0*G+IkkwV_j?K`~k&imLfeH9u2t5bI2o&0Diuk7=GnepF9Q&dsn1x3JpA_Sk%F%`{pr*@<2B`ncD#t+ZM5~d z`4+J~HA)vDvqFf`81wz;PBwU^huyhc*)SoXrOmh|^@8_r+O_`8x+~!DMir_j-syr@ zrF%T4F|jb!8&EDC2X(zyuNDo$;iQK73_N}UuB0KiQmnNHOw1J_S0a4LzXW|!zVTME zP(i}2%gjRyMAhz%d-B48(fmsl;HBMu;%UzTrd+|A{cuNQ)P)c3RrB`bNm_Wx#bHZ7kXW=S3n%C#h z^K!acm9CF!iDzFcgp!dAaRn$Q?sV>n00*4U?*#3AKW zd_~Wj=2}xt#iJVOVC&*rD2rSeuQ!Rn$^AHFS&I|Tv?hpzueW}vBa)l)uQG|7&SE)^ zD(`EnOP9X;nlKc;w!*SWHPW$Bq*o0y9m)QwC>>_? zFaMa3bY%y*1@SRd%@S_>XD$NkUh$`TTl*JlPm2ra;0bt;P&R{p3}SJ@jCN88F>iRbEAb>`5x3=2wxvaQ z&MU{JVl0w*-{TyE&FPr7%K>d_SMM5C&=Anxg0 zpxcJ@9JTXz=AoTwj*ro7y-aMn2EGKacA9e(OAIuA58ZM^b8@~Lle*-YFFY3XLifh>RCN3nSO$st|S zeeG@;4f>%UaNb8fNp`5=Z2p@aik*R_EB zd}7MY`NW(gq4qI4nq7{(NR4i-ou3t|UJB`84oFdyHP_7{OI2})aleMRUdr$$-gbJ* zUPM()9M;5x>xqfHQ6NX_YEAoDOHJ2NWur#B>ZuVbpJGqH5&%fj3Qtj#7JlwmtqZm1^^6;vuwAwPHGN zJ}ene?IXC8t3&nO*EZbGVA%tx5uh2F+q3Wm*G6)#web7q(r4_W+Gf4O@bN)<{n|U* zM`4VHI||`%S?&p3G=;OX_yzD9pfhEPSaPSbOehQM@s2G)btgpf{=g3RZ5W*=};D8kE4|y*Z4848=AWHnI;i3XWWcnpz z++#a{OZ1dW)UG;*({I%H$~R!G6T^Xa`;PZ&lSgzpvrdhqJ|`}kc_qZbWRgwu21bD$ z1EyMAI9*`nT3xQu-%SDajaPe7?rx`xG+GpRWTX(m@pIH~j0Xn^KKYkU|JzYd8sl|- zW>i@Cl`)ww)Rl5o2HHtlPgo3YhkLvEOv4E-S-SGG#Eq*X0FBS<^6#VlIYHvW8n^%w zm*hNe(m~$?e&TaaHmUyV7_*S=LR*|i^|z5JUpMlAtJN%oOh=p7Lr5#HwcZ}j(Z00f zQ+4J9Nteno-5*k^%uYUB_lVIN-ZX_z6yzn-K{Yd;h^@HiEj%V65%c`f$|v^8r|O60 zqIY9kn+-g;wWgAM{$5`m}CE))yK<0{qmXUL|%d-MN%>w>B-bs5odSyy@XZET$pX z5;oqsOrHrh_YVhD9(@j0mR6Pyi@L#&8-rR$ITtw^}V%FQnaWwOo|8vGE4Jd@D# zqm_UFY#pV)-wu-UsAQ~)vE?ZF@_aYj6OQ5nL#AqMkVV-hr)x{upy>whmG!5WPcIov zPIeN<{v7+0&eo&kWKQ?z5ybCu0t^W_Iz6nJM0VhH;Q623Vi#u@e>>S|k8bwbOBaRv z6YP&n${Wo*4!?WL?}Yc2`R#$EgHL?ZOsD6527ZTuB_fV5Sr3b4H?r@3!Kjo4f=FR< zF0lRewfx7~MA9t2hR4H;d#x(p!oPKYyBk~cPB&;INca87d)?rXixU=oYyBNh8T|`9 zlVjFdnv>0&#~z2gp^kT(D=t*$CeQW^j#=ku)Q$0Gwmt`UO>=;hmKid^c3WZNatocb zx_xTJnI|Q5;F0P$zBJFS%Hh1X69%D@`56XmKta@yly)|Dk_}gK5Y`%1`}0HpOw66B zn)PpSLNT=YWW}8EMqlZPM{Pt3T$4}eo_&64O#WA|_W9)$+3%5YoI{YMYH>&kz}nC* zbJN!xi_MjR->0kgYQ@in?Q9kSRf)MJ@upl5PQCYUXS(ENr+SPwj#UnSiR7aWu$UU* zM;@Tv9i!_7H>Q7{rSSwY5PJ`Qp2)+#ei46&nyXV3UPV0w+IqTACGJpIE-h2CCF)a* zXJmTdj<>1C`wVzf%cvZz=|d@*eaIaSG7V8?4?ZXPT5>E6sm)mwdeCL!4WObgfVt#BXgEWA?9mu=p^5J}$@3C9S0`#cld}cciXavTmy^hEvz;{yBGav!Fx)#+gfgR9@v$PVC` zF+u>Lg`T&z%FlN0CcTYIw7{Q00ummvavLPgjQ{rdGSPsn47@sEE%s!*uYEKz&lFS~ zVi^ItQArRyU;9@Nd^dPL=|7|BNXY86j-$_$91#3_$qz;Lr%kVCDl?1WUVB3%GiRoo z9`NIKd`fylr!l5p9<#$cfleK3YgTpyr4ei<moyh!JG=e;c?8L6I|PNDrp)#~?oF2fV2nIG17E&0*M6f1 zxqSV>JAJEn2k;TZeqZ#j^~y{3`*rVs^)R?mvbm1bZmr?j#b@_hjQ*ePlT^cznEuP- zZh)iX#6z-^iKFH_{pU-Wgi~ogc87OXHmLG9_v{JsGqQbKnx;y==iIHoY3-TWfpx0C zU3VI42(!?0^lE1%PFImn7x7TqsQNI5`Dqk#5NvWG%l8@(P!AN3`WTJxk*brbo06>A z{A8)g{<9x8a;R^-O0!4m*2b=e|1#gqdt@_7*i`jSab1A#($R``Q>F1;dxdlHptI*A z6H3z}Wr`j+9{evx434Fdf^{U)sv$8PqI~5DH>v7vBqg{c>@N0rVYWb6LT|$$;r3J- z*lmkHnR~^q)A;PXa3wDz+ijr9_q#m0^Ijsu+zrmNCM3tA++|a zZFRS|!_e^>*svTQCjAA_+z%Rrsmw~t7Fwq8w8$Pr*2~frLNpQF`#IliGB?Z{?E$1g zct9V~K}IAurhiOI@|Pp(y}vKzV^iG*ip0Gh_}zPpH!sffbGE zz;9g2%o@*H+IReCV9geO`o~5lXPrDV==jA%fy)m*rx^=X8hw&WAFTkdwas+}b_TUC zQ}nLa5w3gAlY&D~LCb_MTI+1~^ILNx4Q1fN!ZqFqZe>%Pbh&4;D>I8R1*K|8i3R4C zI~|fT9*)b$VdO{#wHqxTJ};l%DdxfzI$@yra;i*^=tbxA^jp=K-L_lq-*|_*aiM#5 z6}9F4DEs!Oc&bSyv!PoO72Y0Fg}4&TCn?uaFBRC6J_;=oU0<#u(;#mbCh^D zU0HlpBgV-+*{}y#KQ^q@$1TTm(zDJ1VF!%uUu&`6Fq0otGnG-zS+(=l)1=BvF)>$9 z*in^+{b_>A1%Oewk0(HZQQ}9TwKmnovR9e-j%p-a?*P&K-_92Z?7!IA)kNdAu6?oo zYO`C30n$EhplIzd-NdfaS0wnh!ZgE=p)U&p`Ti;Xzj1)N)tjV8#19$VKTpsW)RsEp zyEw^R{nSe%eY_Fn2-q}q`Np=k2A9%C2^b6Bl*`Xpv%)2Vx0btxeIcU5Btut-Ats zq0`5QLH#0*ABay+axYZ1r8IjbWpW2~dTd92+6~w~!(riu(eF-Q&RO-Jp>iY9x;;-C zk#{oN{qfs3Ln$|mYF!UR9sN(|d`wDETa@tQ+3(LaFE?YCllafxKYO~czb{xDJlY=5 zr=O6j`$t`eF%rVyZhH)@XG%7NhTpvc#VzI5qx{wpN}Gcy1F02?=TsqbaeJ5{tvG*t z>xzKuRLeKR%l>SNZjO}hy;pK{zD@u;?Z?IL`F7~fLm&l3jYqu^avS;;m)n>I;the~ za`trJmd3GKr_)rb{F~p$*`@>}2eYs~bD!JsFXoXB0hkC)ne*k|%;F;+iKb`l?3E3Y zpJgEm+AyN=U^yc5tJ9OKkngth{4-=(8?mmfemhF_;W)U~fR_UVm$_WvGJ(OY&0Z|p z&Rnh}TVV}7-?qGoscz`B%y#O_a-NW$sdef;>7wjG^m3{dMcUwkw|(fYnvYwmN9p0F zeZ`5mD?I?Dg&!xgf>Nd!hygQ&xhV$k4Z~AedP$b5Kk0m4c@ggZot*SCjOVcU!oaIe*%}6;`!9X9F5*)%Pds89&V7k#(3rMJZ;+kDHw3El8l5C{7(aXpx*nS zr{n9(8@m6X;orcxg{;B7S%rFE{`@*r4f&iPz|XletzcGD+WQCr2+*%sNrgAXQ_^Pr zAO1Hxx=Q0s#N>z;vJtkeF7lA1#se=h@<82hAv^SF0BLg6%`&uQo-(s~0WXV1d*T+~ zF$%o&ei0WK-pkCmD~%#OQ)BD`f5NChaF$J=TfVYEZd>&Y1Z{VoZk8>S}5_OHHxch8yjqWi?Q;)M4S z%*uc*!>)QP@x0OpBuuy;f>6roJp(bM-tHBXfhp`yUa{07z9$>P1 za|U;+4Ez`!DiUS0VAv{tgCBGFqTUFQ?j2fW6Y8C=w})$X9rxP4IzAk|4$V5X8dNBb zD_q&QOY3Tt<N`DfqJ%3hOY^qTvkeI&nbSoCIKb1rKfFyLwJf+cneBrYPFh8& zMj5Z9^GfTwSys!1`H3dqe!eX;)qveUnMo5P0Fut z$AArY;U868Z5sC`Tlf}+9Q;wn-gSR!0@%R{}% zT-Q|F95DM)bZ@vM-ZdJU-!C(tSo#0a_11Aww$1yn1qLFw5-T8ZtAx}d(k&_=-7K{r z?b5kRBOoBHbR*p?T}yX&E#1x1UB3%`-pBj_A1THPCz0ra9+tUK83H$Zl?f1(4gEyuuulBIQH3XPCH1mTllO05%$jei<4&y67u&aF$L(g) zv)3uM`Xpp#j|#YclPturNK6^5lh>RIGaVD1HEg&?5TCq! zX?v1pGyd*nN%qz3kfx@@&XWh-(iv~cS>6fhHImI6Ua!#W>lMluApI3$xW?D`ywT$c z{0&H1D$Nk#DW%QNgXP@0jcbFQ4LHs&&Mi?))3`XAr6xnURCoWg%fZ;dCjYl}d!)1e zn990#hn(-5nc=c59pt|z=dDLIY?;6u#|V;_WiKI-O~A#x!yYBQF@nF=>f-o;IZnio znql8$^>XLheI&tvcb=%~caCtTLwaD#;9gLwGJ=@;$_n1aWZA`+0@}tI6#m zCrBpsmW-a(FTBA4!p*<$fEm}mRQM)J^;bs678o?{GbM3`9Hly6j2g}7-a;|7Gl=K- zrp3lOH`9vzZQW(vu`S_rY8IcIat2V3QbvaJ4g@0m|ON2-e(#G~ee9J-NJVPR01)h4}Cb z0y09`_X;0;BTR)^f{nubQrkWgK?$iQ5^3%6qVx3IymL=Ikgcx6}%-HlYDJ{=`WMwRO?jN8!+i8;m?37ab7-rBtPcR$xVo@+6YIUtf1EK)Qo|H zA1Fe>yH?gRUYQ6vpEP|dbA|lck}oG*tcmrV%rxvjMcO!?PvUC$Bjh}YE4#N}cP)+H zu=U?wbHX>oopqu*UBO9mCpdfeGOFl&-Uo|3W7V_-E8#I(iVjMi(I-f#pBn1Q?9ES> z8NfBGbpoe@PKoBsgniN!MUB7cTiGgm8Ge%Rh(ySh1nWpuM4P<}lr#RKBjus; z`K!K_PZ~nb%h39}iD3TUoIV%8fBxhr21yJQF$`AH{ib;TJR7a9MYIKas-xDM6tYP` zJQu60icSulBPN-RO6XT)_=dY3SV*@jBZh#D)S*z7YS#Jt}m^T zu&Uu1dwz5wT^O^s(a|+>(L>!!!~SLLjsXuR3P1WSqto-^`-7RUlTy6}x{IE4_ssF* z3b72dZ|AALFHueO$sc;6D53e3M9?}h*R57jo_*swi6wIK`Nx>*Q9*ki4`=c-$xG&5xEcS4in7>Su1@p?yuw%fSA3 zQ!x3aOwQ{LxY&*#?mavlza1&dPUf6s5qziL9aZT@=B$&9ob8#(qV!XNPl<@%;TF<8 z_k4`5ef>hsgLuSfck1Q)nQ`5kBWn)M79(~M&%cJ=z1;V3AMm++M$S%ko!U3gU%QY+j z9=}h3AB)H*1Rk2?=NoJ=Y`~Jp=P&s9_YT%AjCub_$@=_{4XTf1+H4w zFR``yonCHC5qCebrXf0Uf?sXMIFzd&pRH~#$OGcv8wuHmzro; z$dvQ+kW-S{F?F1{1X*0sX=!QkmHb*m-!sk-9GvyG>xF$+UKaSiJTEV9K@NeP@RrU$ zZ0H(e3#d^$c`xIa;uh6)p*o|zIoEez*;*#tz2zq$9a&xiGMD%`nx?LiC|Uc>jBVd%MbsP-g_7f z&sDasQ%Iz~XuO5&$h()VODLC^>hH$Y?pk>#QV(xmdl~6dVdtd3rhN?5q!g0Ef^%$D zihLew839vOR@gXGC?`oLUf@@&zOTsYktiA+-_ez7`Sm#$xGrnhpUK8h(p@NR#gXCW zbX@GPzotbI_p!tY|Hs44`ubCc2Y~J}V9dM>m?4`6^~=lV_#XkDW*@Yk*|^{*Y!dx0 zymRPmm>M_@5b^K=f-8Ddp5e`1t&&;v0F&h--ICeW`58xbh8rk_idyi9U)}LSM8i~+ z!8GaNIDO}bWoQ9ivvHH}dgGK2NHTaucK0KdhO0zlmNCs*D2(cWj`v{!`zM|1dOy@z zH^=%DzV`5-I)V8Yxrz}M_$=r1pS3-;DMUMOG`wYh-T5-~a;dI{ut(7!?L$$iw9S%i zQtJ{)M-}D844mmZK01AucV7MGR&Pw?XJ$2w^*dl8A~tQK@L!ykt^C~1n@O*II(=u$ z2^^Vsw?}H=@1UG<$zKsh?vkMX3%sK`ORrYF0Pko`Yy757DEi_K+>J|w`)zc zTowI$h&q8*uP-Wic1q*7+W2v$Sg7A4{%$HGCRL&e+SN-jX>y=(l_;RxdXquoh^|O1 z1Y=}(LLRoUfX)4*BNF!^Nhb-7%(Bj3Eh81 zyUf4`qL!ZT!5myVXVe{XQE`u-;*&fkenLjD6}}RTX_m-eG5&`31h(~pb`i7XGm#&G z*GsWPNzU6)L5@#)=2DLIv#2s7Rw{k7p2j$wSkF-lf9t7T*%y=BH8m%YoJ#qn*+xd# zu`GsFe9R9=eV%%=zwu#pGW13CF@t!&60aJYImLcZGGW_1VU-)`BC_mdoY!+nT8n|& zr^l30MIWgxDk*Hc7qnliVfIV?Z@z(3@Pa4(dF6FKe!fEqv)yv1#Eq`sjQ&Ky-xHS% zX#6<}d(6Y*;P|)K0^uuO{YQ*jE5D*Lc!daFc<4_Q6>}4{L&=wP?h5{XCAJHm>7-*6 z311r8UEZoET$`U#+7vkPZ!lH*+s5e|~-FPUrG7gOB@2WJWv zvX`MNHZq8&Wp`j<3&fy}4&Q4f{m7}JBI=~ZsPLIDRl<^UY!gXh3EcJ*_ZUT0o3MKw zHB>@_Oi7d_Ex*xs){-8pzqQz1_Cs5S^XY)9wT`>KELhfB4N&?sEVCj;-MyTSq3b98 zti3=Cw#ffUqW{dN16*yyY{C+j*c)dRy1AUKQowcEw7uB0pS?A3gQE~ zIhsqk8hkY|%k!n&s{bk?w+oJ!c2uccm~wODhCrhY1J8>jwd5v{jsj~XJZBuwaBu8 z8=*ASbCHu&m2(j-%{T#AIJG`2>@oUdKbTo!m%+D+5bvtpH79Dtpz~7Vq*&WIOen)u zQio>KO>L&ro$Qd(z6lI(20QS?orCKo>H%0Oz`~2q)p;b@zaskra@k%gZ6$s9!eRRy zM)C>>$|R=$M&kbo%KXYU;&Ju{Q{H5QGTq#2riYXo-^t)Ip6ZIe=pcjTl}8~p;`*Em zhUyEd2NJvBDoMZ(M`%PLdu|Rakm)C}2xgz1F5Q`j^rAEcggpYclQfvyP*bA64Ec#> z>&-7iUM2-y>zU-9OtTrZ4)05bYDXligcar33e)$Y6DV^=A=`7;HVio~!kM7nmuZK20d?+xijtIlpwiZ9gdTYktYhmH^G?kHM_BbY>#?9&s)MP9C9aM^LM(}4D?_DEn?6suA0NU!A}M1jO`3vuZu6H5T5G=8PFGok+EIC3fIwY@jss~RnD#c+*DtQ5gf zMdFUG(yk6oV3jxU*)z{+tX2&^Uc7ss)!qiPww9bO#cY$wbtOcUYJT0lraENAqw%ChTc8%iS6({>l! zHcS|6*w+ZHRsl`3`ceWnAcw{ji6h4e$%S=qHyGkl^%PK=H5D$aosaP15BaPCY#{y` zTQNmAQ_p!|;p*Z5P!%9Yh0I7_0iYxKg1ockBi&ue{%=P~q9nR%Q)~mNy~%W4vC*l5 zl(+k?u}9>8V9yr+cOOavN{9SlmJ-vGMUN_@&-U1ne6G}#v>-UT&05S_JPz%-+%Zj9 zMFNV9{=vc~IYn57d#QH&jP<*I=TPa4qu8Lfw0apN*1$O|Id~z*mQz#2QwAATge^5i z^$TBv%Q`sL=c4rijdzkLuY+@&YV!%eemLpu!mzBworjK#wOz)xd2SE+T)@O3VnhQG z$xzE*Rt;kM2HR)Xj@tKw9%K7rEkv#m@wUzV6eWr3VildgyZ|!h`EGql7pr8$^YH65 z@|Lv-V1Bc|3l|_*!yE~J)iBp0@6a11%jRLmjRn71DFM}vJr$gXcTV`+6MAF8ulxaViJM-!VsY4jmx>BWXPT3W?;6CE^ zCGW#FF>fhUYn5me7_h37BG{hv$G37xxn}T+Q0YwDJS?c$oza$NO~v-9WWRaWS4!OA z%wmU6cyR)fw*iJ{Uv^!bT?4KMmF6E&I;BIbTe&Kb(ja^C_BF|s?q}KE0L&`dnzBmV zNL)b`zxknbVb5Hd^3s4O_4gtA1f{cWG}s#@XhE(>3c zn^Ybft^EvmBc7e@_0W?ToT~{;CAw;xuMFz+!j-sd!?68cAIklZW z!G*o&HTWO<+cQ7xXMXOi;rX5&lAX#7tC)FkmZ{#z3V?3kcr9f$VA(j>J8ZNYO<>}+ z_r~!uRZ(0Mp-t%Rvu$0xIHup)+Wt}TzuF#L*%&jI#FX0mYHNmzRZTS!r!A$G)101f ziz#e-QPi9qbO->|(r)eUd(%~LWVcEH*Pc`lAIn+HPupgpJ<9>Hc|&*ZN$i|dZ;qj{ zP*lr=?1sF|3Ym?8M7S7gtjk+c6}Q3$d#E`yb#W;rtjVp=s=JeqO`wIjgKL0la0mF-fX!4s#$+>;CRbo|9;xn}%yA$uK1-I2hR z-_hBkho;8yNmIPW>CWQhIvnY{cQp(2>p<5Lz!b=#SW>v@F{YS|6k+Ul|NQX4Of&G2 z&!e&G)o6}tyLsT6O6R?-(rU-Ej?yHzs|%#d75y8^H@f3XOj9Rwu|3ll8&y41XZsh@ z(}DN=?_DgoOY;NOFWz{C-g;P|z}L$a=enzMmrq8@oaapEcSL5D%BF`Ix9@$nWo6}+ z|L}^JhL!iqV9V(e3_CXKpDKw&&K9=~PlyERUx8L!LMyRPeOlL(kYUyKyBgTeJqyr* zBk^fkyTAo#osqjV7u{3-9psdoG)1+?X7miHs~I)*U)3pF8t!HrXOP)(U)4%F%QMpTQ*7S++cYoR9H!T!l*G}rzB#%>#642dGYbv?P;mF-WP$3E^sLPg-~uEod`Unc zvL6Nyl5m9BSA>#UcQEX31cF6(r>ZbnrTmxMn@kt;aVHHdSQDy_om?Hy5mT^nH}tZ; zMubHl(8SSRrmC4f{(7~!F*=j1eKKi_?(iTG@IwcNvZ2JjWE&I9*t#Lw&{RF5lrW)T zgXM+s*eDmvI&Y6iFbp%8qsuf$lrA)JtGE_;^P>#Q&{gdxkp<4+3sa{nx-u%!9OM*< za!x{Y)3D*+$Yk^#r2n{V-Ne$=?Wi+}WFa!3D@f6p(-}7zbWA16bbR;?`hh9W7dQ^# zT)~1ru9}g?`t?~0?)4oFU5Z<^)Shj_N?TeUkBybrg&L~p%fjq!#A1OqSV>hzl20R> z`Bh1$BCihptFtC@#+Y}Ol1VFYPv6ZO2GbS#5Q7XfBAmKQK25>A)R(iHT57>PAqJP* zfevRKfdc1iIg{%_m4V>4LZ-scy_-r(YmwFf*fn1|kp3!Xfi+jFCi$-zXD>G&!Bg78 zZU~8pjf|c>opw3j@8>Wxp4>&c+-=N7i%(j%!&{4c3K=78oR>US!)qCnNSqS74@5Zz z)f9P}CrO+T3ssko`NU?F#=jEz3vU_)8{s0Q%6W4-6B{#ID|)G?G03#qoQET_rz}1L4N$dWIwFUG$6fCs}WNI|g#4GT18F%VkkQNhY^Ohmr;bv=^%S5G|9} zeQdGT@0Tm8d(*KuqMTt%9xBTwI2(JxWolx4FSjO1(Mi@u>dVI^ z_q(F}#}4d^Jt3(Yad4Qu_t1%o2fN+j%`y!cS^C7h^%tS zC+wn_jkYyJOYdvs+cqe1*>TKuIbrv!x9M{Dt%qla6$%8`0sc28lM3BV`^vMK2q#mC znzeU)JQ`}C`Ps@@$$KbeH9QR(scNK*97&(dMzbeao#$Bai5&?CRHhBGV1|#S4wSm| zW7q8ugxTbgU!Y1ZqPi^t+b&}Bw2c;dDoXIBqir$YUp8RQ~i*EVCJ{_~bt(e|aboOLA#7vpzpuMQ$Q@G9J zk=$YjjWbLv6Tg!H_3J4n^m=2xRbiGy6q|!chP7n1CtOQM;8M6eIX3hf4j1d2op(tV zgWyWSzjifA&qUYT>UUy~gr{O`ZNz@qlnl6r-mjjnIy^ybIh9zn2wd&$$CGD>2bSqG z@ZRJ?<>5(BL|G2y!FSm7&^& zvaTh88il)Rc!iTeEsZTogXULIh%!jON&v|*&85k%&5w2Y1dL#qXE+1$u7 z!gRERi*@e6>|%}zr6+raqToGV?z_1i!DM$ z%TJn!o7}7ldpfa73t|Epr4TPoy;C>({U&Fn%39I(D~6&RzUfWuzKld_^r9M~!}q6L zZQpl){cN=*7)3iUzYZ+EQw5ik5k+V4(iF6(S)%aM+1rYM7R3%DQ1L11u%|VwJ3^OlO_E&s@0Tt z1YC1GQiJP1T4J~CsTnU&_M#@wfyS>LioE>z3HIv5zQoZHu4;{~IY0TmKnBVEliASp z8Ai3~^V3BN$kx0SjwM`+f_wtVIT&&$NIuf#PT5dOf9~-AA|CPcw%S^$;!&t{wT%$y z+OYXGEE%Qi<2KX7*z1Nin@FeVVHidLlx}r}N+>mWE`g4$p809u>5MgvHc1X}7V&oR z4JP3!1=T*8NrrD$pN&(% z+FD#u2G5x~+6^>wzL;&1trMsWTCvhy%{T!W46fS*KsE+GN7`dud(poCf2t#YM$LbS zITr6T($vZYOFLim@J}+fIUH3Wr%qRrN^AB59hUHVLKeLy`au*{?apgVsBrXHuHFRQJn3;lfCG*;bQTg%fZ3Uy3a zK1z!MUK5Zi^Gph}E?iLwHyZv_H8<8U+K7oIrDXdB+T6+w#}_ZQ)S@&S`mRywDXMXR zb;b}1!w~1v5XGtAfD?`psDvwrs;I}oZNzc_BJMxNNMeFtpY1^%WI#0W_Y}at?WOO=t{)bobpZ?}m1CzTO9SkJasL)V*>dCj|dHEDhzaz_1 zOQ!?!N0gT~d}g(LIJ(YFsBf^OUxrb;7!aSX%(oFYS9rS-RdsF}sX#nb%U1q3&uxA6 zYdUb>)F!|zx;Go04n;zYZ1vU_Vd1Ia%CYfSrWXf;ZCEWJy)tr@_AMeFz3>XQiTj{4 z2_DvBY@;w%Ix@^XI&bUyfV+D%tYvkdObRV6}r+8@S(a1I8*AqdA=IVbg05M_8=r zJ9F13{&1zw|4uGd*H3?QC1{+|XDj2KOGSa2r)2wWcc1@QNAMj*8?;|@rR%!#QTd@A zyT>rlYO=I=ZaPT<#G7K3%3g(3m4<&`Xa=Evcq))woRwTn;x06$^?1lMt*yOvOFziN zy|43?cV?5Zc}L=~OlQy}eH(Y(iAtr;?Xs?_yD&^4bLI}QFd;sN@wRhYF37PyaW8jP z8pvN31FRo5xhocq6OGeq;@S(D^7sVS>pR5<0No^Vm@`LExdYkg%qgzRVNPdMX9pwD zF=6~*^O`{xs=t{WPad=*t4`iJh2o4i(Y4|M~Hf% ziJ)X6t~4=-c!A3P!w!StPnAKY_^dH0DI7qUavnJ%_COsu*+5i@W4LSf92K(y0gdK{7upLNT7F)etfmI{mwKQ zsesE-RS!F6Tk=L}7`a84vTZ%H!FBbgvYaEv(0)AW=45N0B?w8^G*Q=+Gj+BQ&c89i z&BrwIyyIka&YA@zbxqWmG2hY`oWTv!NkcNZTEcoRyG_a=-x`<*lz6 zwXfFu9V1SA(Rlce9{5ifNas3o{+YO~uDQ-9NdC;6pXDB1u%S+ZvpaZH2tI$ZQSb_A zOt+c3uFP6uJZRKe!pL_%9*7-Ej+V_=_3*nOTAJ@n-=;5&?e3}IUR-K)c-ewj!|4MX zZ+K1rXWM^tba&{FICxrYxOwq%jC$}XA&wN7^EaV6o{R1MHd1ro>vAd`sK8&Es?L@^ zU=m^d{<^pr9lvHZ_9D!tL;oiybFm!lS3YZDSe6L4=IEoh6t(n?EfAk1!F)AZF~l9t zt^?>WHT>$hAbDvjn6<{BfRx=9U@jxQY0w3Ef?{!AN%ws5eM9J~X(U*sn=2q~IX2P( zE|vdtuwbIHzi6zi`%8WnBIKI1WPl;Iwx_6|LboUE2@=XNG&H!CqfO$sowqjJCT&}% zHs#7$JbQ9s;c1#tc3Ow!n<*by}RmO zPqMh(;l+f1Yr*`Y*MOolEXaNrDOgoa~WO836KIBS0_q!TFF^H2;| zG~ipHkH4?PJ?Z=1+Z~5sb0&+70;4=*_TtY)xmM}s^!u$xT`|St_Ayp4&v+5%xaDl< zV!-pZV)n;7ME)g1rXCtx=Fgsm5=700FRRSS8Qtz_7_NW9HZntVCx!a!vK@PL1)(QZ z>YZ>N)sYkZ<_`uA9Ln6`o|H5mVP(p!_KP2y^pA(eI2CI9urSn&bw!5;6wKSuPJ^(j=e5DL8 zJJ#2CKoO?i8CPl%VviPXgrKofh{_xmb&t8isXbIjnm%wudr~Q(yT(F^dOxlHpumq^ zhV|VOD4*y{c)`HjfJ76JGn$v?;)qE;$SJS_`+w^>G-daIY(?K+#_EQaa8z0b6Rjq} zZra5y2H=L7vony8GIWsb@ebU2QlF&#KFEn>@_|W~q^hBbn0i5#yjw~jd$j(sP$N&c z5}L{bJ4JPbu&m{S!$L!nmiHsV#Q@QSLbwX+l3jfgZW!!lAzTsdLB$^3{6TGm%A;O! zzn#_`Pb{av_nR5Jzi&~F)O|R8=iy-&gN5rl387K{lQ88Oj5-NO#b`b{lIT-kYqe=! zOfh{!PX+izbz@V`>*p~pP|T?D0|9hNMLU@B?-x@qr;}4^`JVH>8cIkUrfy)O8>Vkb z%x|QkWu-)y7uCHP=b;|s7^ujdV5Z_^=*!7N69mHPzZUixKS1*Kurx4Haf08_u`^f< zgOSf{5BiIfWt4G5my90om_s|r3^wV^k6OL5?kztG@cHI7@5sWpJ4-XO$EvJ^4&@;# zGjM3=sWWh&cawAB!#q8&_TxKjW#}+it#~Yi;7L1UnpjYP7q$e{o~UimBM&FpIiM%& zB7%ZiGT+rHL2W{ovNBI0{}6fa-AQ^TMW=3Y=PYLY{vKu2qT0g@0yHR#+E&}yhof6%7C zXUZLxhDyr_t-nd1h7rvu-Ge;hH)B->yoHt8gIl3C?fWf6zF~!-;@sR1ym)dqxxy-w ztR!u5^+vHpyJn#^eUkKBz-><0A8tz5ZsdL7ck7o$cx6MiBwzBI);nl zWGkXwZP8P(o7^;_Csr69i#19wTk&lQJ+hSh| z|MuFGFKhUxx7b}7%RE!tMtWIG!e&Q4-H-q9mgz4b3TX)f{J(i6;yW+^-gK zer9;D#qOCE-MYM$#mo|xRf*aV2xY3wYsDeP?!+V}440MRb^3?v{}JUKOArm(A%=Um zz8iMKSNy%mPsm)OLB`S1nE3Vkrf(Oq{Iw^yD#iI}Lu}gSEk-B7n?YWtwkd)#x7`f{ z@e;&R)!mnujgFaayi_~#Ms#)1s~c~L-#={6DDd?T%cO!xXl!Zwz}Awh=;Is~m0Bys z?ipyXDJMdR{I_3Ocj_pLPo3o|t-C?x)ck$RJp&)@YUGdF|Tg$T7e4bLOMU22XX5-#hXWJEbFK643haYaY zwSph`q~QelF1#rJ2!8Nj{=t7m9iPiwPOgS(IW8`p6Oydc=~RPrQ@lfDAqEi>=Sqgj z7LTLw8loMh3T$x|lsO3^y@Psl`qhXt`1ZQKF)qFSE*mD;nGjsE?yCBT&@S$I8#QBk zMCDXUDgXvkgh-X!zCL;yp*(Mpyt{q$s|vU=#jZ%XsWWh{O_6fS{XpcRdp&ZV;p6WFX)BnrRv-oxPNM0Lim#t4hqdIjd*p>)ckTg~y zl^KTdS)C@t114J?FF^Sf-}BpnuE^h7mE;x-H_>1Wk-%IfbKrfJWGFkcyYv{YC^mY3 z%Lg)&IU$=tc%{0>MA~R&GbCZ;6a{YYlF%{w9OZ~RofL#QoVIpXeJVBcDVWt3#LFLU zKG$p(%*xIXR}DIyh2(@t4~()OnJ%~8Z38Ud;P%S}f_&ZT%l;wGWo<4=s_^4*pds}O ze!)rRmcsc)@g#fH?0oNe3E!is0tZ;~w3{jxPArUxT#p0PW`eDEa~*L#2!NYXPm>)y zd?6CYlcj@%^4+6fs-o6)p(FMX-uA{yOWJ9u3iwYVI zbn3<>-f zj&s-om=S|RV5-~RtqpgB0oI?@K!Ry9eS_E!poV+O4ISWB+qzf5iWQLuHX z!#pLfEQnXBy-0Gx9PBPiViK==GQ*0SZMJ#uP7OOlHNe&VHWGjKGJOR9()QIWKl*=& z{9N}~-Yy$+n6+O>SK&C!Ti6R%#2;on?R+(S5zsQL{&1KIuSF}xwTJvW$=BF}PMXz) z$>Fr!xhO3{uoCl+X(w?gvt5<=bgu1^Vq6+H7{O++-fjf#7qMd#cks_@6bctby&`=| zovR0fH}QlgUK5EKFmRzviP4CCv3M!0Vk6cIh{hFDjdOfZbvV!};9(qu4X@LosL}^j z0zO*Xg)_4yv5ml4Q|rYyQ^Z5mIOqb|t4&BSLG$0ChxcAgO;aX$r{!tR6x-p`EJWS@ zL!dgT8Bby-d_M(Xu~y2L>#Sw$a}x#PzAP#9xkW-5TS*_^POqsGdQV*^7%^l>l?XQM zQoS#avsK+h5IllP_R*>mm#10rfy6ia0=>Iy!H6nL^W9v_lHAH+Di;NQs`pjRi$4N` z;poPPK(AQ{E-wDibztUKr$j4lJkr1o6H8$=3dtU$%9JH}BN>hqt#W6}M$VlRWp~Xn zpF4F>!#;s{rDg#n`p_auZ5mSdfduA+<1nYC-F42mdZ`P-^1%EdK>y@IJ87rENnyyF zh3pKT)wbCPy^~sKi!T%ygLlj%AOSVSZ^|}VRA)yxP2S7XcCDvtYz9ep4(UaKb44X} zjEtW=JS%Fuh^UPip$hApZ>h+{$Ob>Q@WX;^!c|$MSAIgHTm}d zMOh*GMbm$%6A&~L{zKdXK{s&_Brdwx`dU>T)lUozCpPlgpQLHe5UBSl0FKMV9RR^O z-g;B&J493_n4NnAJ0E3{AA4s+HM%{kvL(2c#yOB?5&Wv=ER4M=`5@QWY*`M9n$s$M zBqoIX5R907fL?$`WIm70p+_3R_%pNhy^YlKg7>_MAMY4z1G#20S;kgDp`(A85b3M* zcxRW#wk@S4V@ae4Uj%lRNfrHpp6W-~YvRoX=bV6y7}z~u1wWT288 zOvCOZ*-@)9+7--3aD@UTgLUlC7RpL4-Ifk}-MK~V#-;OKw>H4HQ1=UcCD}glJQ(L- z-73TC@wVdG2dpmcA#p9b3i!HKg0+qQ-0I*C(H+oGk+9SPHqJ7B~r8@74E&<)@$psxf_JxBSMIg1-AsHz4s0)1E-Z}g$xIrpaiQRqCw7=?uNUuFJhe=H_nj8EgA2B&NiaB&Vf#+YhRY{RC$ubeL(N7-o zXsLb?#fj1J6^=k?^#>8XHi8tw#9nveNj*4d&nOnXchrh3&|w3w+=EDs>EW{^|H^N3 z85M@mog5=QHGkC|9^2JKf-E?#WW&+pGg2J(U)fwaII4MT$-^`s@z@GyJ)@J6)6J%o z9yKHT)|O&En{1=Su6MGi* z;Y1+biAilJx@clu!NkF|)w}O34Ri1O-2eDnXx8O!TV)OlqGy!FWIE(FK3*UX_i1CR ze9zVE{f(Ku+hD$e>|}0QnMmGcZ?`7@rKk${y<(O8+Ekk)rynwB~XXx&6#2yh*58 z$oU-Z_`$KUB4s@+SF^zR{&|t~wA)0UZG-kPhp~E*g{-!+_Gqfd_wB8-jjo=l^Oh?? zQ;i#u1U@Gxq~F_~N2o37n8x0{k5r7)tOU24ScPWr-jY8tHm81$me#v^aL$qM^8C_a zXZjKHRu4O6bK!oGnw@# z2Fr1=Hk|XeC9}K@Xb&qNgD2hcV%s&B5hO)U-Qh^5gS8Rl#yts~Q7+Oc6O_T_8Qyf| z`KI{lC9$)EzV0a|Hf}L^-bS~+&w@r6R2EOJv7x%=s3qgk5b8Ws!eKwGGQHaWc_+`w zw~eJHf*nz5hVI=gku=#df-Cl1aL)A4?7i>dziMcePm=cI;ojK}xjk^$HOgXjRGH&) zqPv)R4BVCV5oc83pmD|OeWuBl%^O&L=9UXueEujfD5(ECB~VcUFrS#rn)?rNL&;!l zLf1Lx$U~4uM9*O>rb_Sf6c}w-qwDl>cO!Nt3~`wJKF&@j00nv|uqpRuAx5WsG>3W* z6}z5edYHayRcw}W(d4Qp)s!pr^1(!4AQv}gyYU(FLSD$!Rn^dRmY*KsyP05o^`W?z zag;@CG zVx=kfZVib!l??F9XOIRK{1ROb8S}9B-h5ZZx;ZAm*0R3cm^2>{NNdhSBD|5&L%Vro&Y$&zt_l_ z4za#Ur+%76m19cV^{%*4e-#s2A$4_{^h(}cnGW(gwC5HrB-^H8@S=VsXWti6`EaCT z+YnUh3qi4N&JBToK<}nLi;Gyk4;Rc4AIKHammVu|(mrgh%-Dlwosx5$4enRb6|6UH zoxiz-sNAvpaGn_&UtMf7g^A?SNE6n0W}CF^{%Dh8Y%mBK5+WV{ALC!7osmjS+C)xTmN{{1*mM2W z`Oqjy-qN1Fu=b?qO>wJ+5tq)yso}dU9+yL4)9^|jjo%N@O0n6PE3FqbGfGUo8rqAzL@z`!K~q|<6VzY}tlaU7WBmAzguk^kBFsyBHt+#pJ_$uySaej&@xW{=-y z@1}?QZbtl8Iy3B2e|0Ss9{s6jNMcZfC3M=KwiK}V!<`P9szH9071hl-cpGYd5e}0Y zn1SaTf=02bc4&m-NoY&5(EH&A5~^bYMV~h;TPkvwamUx3Asbl0c(L{P%dv1X=fiFh zqcAh|kS8%17u&hj+0a9q;jYvrNhd=Pf04m<}{Bj}&Oo6D_xvAleoAskgSIvp;j!0|>eMI7iqX3iL8oS;cDC03nvyK*Ec4$;pa^NCV9wM}7N$%3%qbEDYg z?Ff>0(fe!?lT412Vl@wkaB{yL_JDE}QDxA6jBPn|_xb@y8BhUU7x>CaT{yNn=4zmD z#9?yn(rUyZwh_*y@UUV=oqNMiZ5C(RVL_U6F4P^4l>(mF$9PdxqhMNwy>Z0l^F0qy z6Ibw3-~!!=RfNX|fQ}5J|6#+l{y>udO^g9C$F$hN5s5z$dA?F6G0mh2q@Tbkm1_OI z=9!T2>VvMB3eAP#LV<&dsSBl%c%I4i?VpqL>?Y);4!wd4N(tjfObZUReC3DIq0-$l z2%ICSA8N*CAr*R`J&X<>OyJ_mifa~HA7!#d!|$vGwQtoc7^Y@vou24LXqdAN^6RZA z0rnR~&4>Pew57pnHzr@=@A6fs- zsw0`4NMi~<&+oEPRPCXoHLJr`9jw*8#X~2nq&CS17NBBc@BiHe3Lug#XL#e&D|XJ? z0EbBhlH3xO#SeNVFPh5BrjF?^YVwaHczH1^EBG&^`K}sz3*CG0$_B3rT62Vbd7x|A zz!;s9s(xhsZ2eEUWtN6y)6Ll2n3#kmqH%g5)4G6k>vb5Nx@TrejY@@W3I=~dR*VJF zt)HP@rvU76NkSU-4*>U%D;aCr41%}N|9;3x4b(c=Fr&I3(%V$OPOqO8t1%oWLEU!Y7bd#9k;`}DxknyJS2P}W&$>{BP_ zZT~pDaYUtb>`q9CCna!3bA19pm105lUy+##-Z-MBI0mX%wB3vfefESR-*QMTzI&Y8 z>d238;Lv+=&8aK3WZa~~GvG)vI{qZ3X+g^&UyVkDj5M0(a%O>nqLL=u($X_ZtxUjY z(k)hIH4NC&IC+#FuPRn3!)OE8U5p`^RV!Rkda*AeDsELf0$#IiLx1t+1Jk8*D_6_zvU5?^lX9Cz=F2;nnsTE|}i5YjY!7I{y1`*?Y6@ z7e1!QnmAhAxjI)Xc9!L2bULviYi3hg*a}8vE<0N@P*`@S=pMt4+?ZSV_Nd9;q4c+6 zYV_Jp@utS}Y{w_8uXv?b{LJ!_yKTk9F8+tRFSEf!&cl%x7d-$(7;n0Dx$>O9(Kzqs zQCm=ok0R&DS>u^zGkfDOc-R(P64)8lDP&4mMsc0ZKg*RIA(-0J3|djMLlfF(KU*0! z=(S9VtJi7Q)s*S`BpIDL`zPsBBoe=#$lc@|6mr2KUnAN{p@Ey`|LCO zeAGa0c_-G&MN+`|&}DMe?MnAAg8#+x4!D2E!4(6vIZ-fMya2Q7NYx&8 zx%j$l@`1+qdChc=*q2zyS@7be;gfx}66fieSBtmTsf})-+$%IR7_$GDwtmlGQikF; z8`f?&XF=n^?8~6q`f_SA;#8iqDdba|L0ZAdJ*e6h#k;O5yAyj>mF&n9;_2H9t=g4Z zawU&~Fi}5ZHCoNE z^^44;viliTy~oz^;8wLd`VEI@L8mS=54e7Sbg&9ccw+@pJ{?Lcc!Px_g4dv(fS#h#k%WT1n`k$$E||FqNE^A6RqLrIY^ z<{!oKn|O(?r{IV19t5OT<52Gf&2Kxm) zUCP+CYjDUw8owjG>YBTQVtj9!51Ip*Lh48U@1uexW%e)CLQP5n(XW_-UXvusdOCMx zX59)0cK{%TJ1z^aXMLj}8-;fWr=%=5;%$h$mvnow7`=(ja+dV%5@5}?%uo@dxWXDvX!yS>7G~b|D z9=Mi5Fr(4J`0;wzA;J zveSR`^>6h__8Ul>KUG-IKL$Renqy?Dc~J~5HK#iWi=Cx<_-HQxjSM(_16}8=JCVdk93XDd-@*1BU51uy zVeFPOeEWw#MjN04kjC+yx-meF5}w@1kN@ql6)6z+@;tgQYU| z2@V(4S?w+0l8v51-f_I1uhm>p!)PT81x8wIlUGZ%_d~S~g(q=}{`Oa{3|Fsnr7AN$ zI62Vvnd88)yI1b|nEEl*!dl{j!R^H)LhUU1-Jk;-5`{0uAF71%zmms820_*fnaK!! z*qPi07^g0oK=uExK!f*O&T{TzpK1}hd2CKE<;DYW?@u zJidFfA2}mLuaTF9Q2)9tl>ACmpa_h z$(agxb!~22dP24<{Mshs!n#t1;6MLt zj#wz!->jO7S*<(C?+e;3n~QAmTCQgcHr28x?qH)-%_-7ROHfLR)%gyJQU9h9HH1Hp zJ4`S_#5jdFhqFNFjTuc~jyuYNxB@Lb{wjFuWLa;g>g<6THtc%LI=tuOq}4XtWwb$%^`dy)%0Ki>ipjnU8AD8Xnao%4u~+p?Z=E>HGwqR-&R> zQ^uA|)J|OsI{H}r=t{}{%=f;auT5+^ZY(!7CAx1JVTE+O4f$Mvv{Gh@^m10}T!F@1 z2F`+d3g*gea;B;*W{N@>E440TV=e`AIt#3jyNCg4j*=f-fJ}}Cv;Jl1;bzrb@=>uPbmCSc$ueIjnNs>Lg;Ybs{B9(w50F_M%rv|5)k zG?L`g0Xx$^y`j=1gBNoi;$Kvqb!EiA0N zH;ee?R+w>>mJ^_Y4zf8u$98w!iES$TDv`>AINQnm;oE3}Efx3XEUr@e+^@=6Q9Fm` z1GL6sOXpgNkHnTZ+Gm@5gn#2a&W z?@C6ikdEmj$Eag6222w_Ycqbn-C6MJ$r%cEdw^agO#l$or#jHG;@cU@}y(Y)$;) zbEDmz=M7H$CP&d!JC2{&kGnqwGb04MKVgtprj>jW=r|P>@Pnv~cVnl1Z;95x9+}0g z_0R*WTcC?!SC;j9U5(cgodW3E`y8a81#-bwn&q6Cleu42azq_ywNYMp0r}Eo3VwLF z73g|85S<8bsumg)sn6jhX{^XFQ!8dooS+n|7dn*qh;1?{5T8N3s9B-zaoNO{4(N&S(cTsN@i`kH{g8Ilnv#fW5VK?B#)~V>8 zB9KjuOO3-9B{Thq4YP<=KLLSX%*oxc6I~pz*%N%Ck~N!;`G>a~TlR^j#;AhLc>a3~ zj!cf~Og7XY%GB%F#eScQ_vP?P2=j$XD(hK5#Q(N6&D|AO`I(${Eiwn>3LeZbJr29b zFd*&!TAeUXtWutry@B@?=OoxDme*r+HB#P$GW0*BddV_cqGJbIWX*MtS#l{N*`AK{ zDTqqh*Z2)FfdR)%VE)ma8ALZv!dlGc*pZ(;yk_}xt3r2@2?{+^1KOglYHd0C!h_-B zK}SP6hkL@_w%Ka7qyrQAhVdW-MxYEEkA|p&hG~1apw-}aJN*QYP7y=YVp7DeiWqJxE0voKQlB&8Sq{HLnB)!o#L^<9j{#c;-vJp`llX8r&{kO~6(%5!dpl1o@)j ztbR5QynhZ$i{#A)7wl(0rTx3;X{uL6m_09C1YWl{F{R^kioJQ#kQha` zz@uRpD|k$9T~kWtXKfbCe~>9mu#v2aZCc=#l}TnWA6?sS<)m%C>Ia;AFjo_Qp=#E` zNRVHT6fZ?k0S}5AqQ$};VOf|jpHMNFYu$|PsZV*~sO$9*sQCZRra0x|R!NPzty2`I zl85b43yvnlCjidn*jIU)QoWyXG*ukNT(?uwJjPd*sG~o93#>$Wk9|8459+WY%wI%> zEbO6Auwg)z975=xyJi%*_+zK?j)vr+7|{h0oB}A?>uuYOV|^XHwe~B#yaMur03}9j zy}Q0Z-g4%i1+s90rg1^jm&Ef{Z-jHgORR=E%jw`HO+*?hIeAxmfr$noVRPbJ74Nu)vCDF?oldY0sq|k^fsxMI42<62& z*{6(V7~?Nk82Ns{osZEGSbOUDuqZa=8Yj zk{~svEJq3Rkc)Ki@jI)$Yp#<40+(1&$WV6&9h~!3aT~zNg&IC9JX+Q>*6=8w3=zB7 z8ONo`K2R*5&JQcJmR+p9Nh4wDAiGJ@Sx<%fw9yd%J5b(i4F0G5rOclUcWn_1Y*+YO zpp{oGm;(9|^NOcx4dtbzi+TsXkf>=JDB3MJpNmg;-gg=X0_^o#!1YE$3F7$t-c2+q5!dX;ByypV%wG*hB1yG8NFlDX`4mYSUC2y)9uD&I5@Q_=&h`*uK)PDM{8k zG>V*#>e-F#1fk98;2pFfsU$%EC;o>HG@y~FVaF$3v;Ks3zO?6|?an0MRDt!73Ps1c zIpAc9qH1I4A8f3V4$>3}%5==Nk*i;6;IGijPu zTPf#&5^0hvp~VEAWhA4RBq7t0*YD~6AN!;(b7+bs&GDXxRZay;Z-K-+Es`3ohWqyX zJBo-EtCiVQ$2Cu5*vxgu_`E%44<&ZKyvzF9{be^>uo2?!vmpDJLc;v#OVpE>T0DKp z*eCnX=A2{0YLB}^joK_T7P6H3cE1|~c?;CX7&7SUSvw2CU+cJwb|G6W=Ue+oUSVND z@`^kkGZG=jLFzcrktC?i$+$_V9!dxZA*QhwGc}49K25$aLbG$HC6iQ!}Gp^N7!{t>Rvrdwm{k=6xu5x-rDe3j2_*-!##imr`XoMs!;RG!ws6< z8Mfusdf67h^XGpl)z!#|!|K<+l&TQ&kDfs+#`BeEeHyllC&{`oBQT_1WjAm3!~QBY zuXy_eU1i?t+Wc+Hywj7M3l$Oqe`uO9k26k zY!@*^&ABzpl@?@}J_yw%h~6?gR7yLq%$taku;+IA{fdwjWg}1C)2^R)bxs`F_y>Ky z@;HoLtITv-E;<;BO--g88spg|b*(CMR-N>8S%qe?h#6m3t=Bpym1#P#5)Uc!unBC_ zoHKVO<&77~7FO;~&P9opANyW<5)A}R4&`FZ8ifi-xP#RfgRH%%Jx2HsrKhND^Zg%m zI#T>eN?ZV+A$9r#hxqt-wom4Gsrz>{fNCB_Y4y+{CCVenEZQVhOh5ose%sVROX(3KyuLGhR z9dpOrmEO31cjEe9?eQ9@wq_JSm(xeT0muK_+p?vv785`Bw&0*FQvED+GR?>qtE78i z17vyf?iU->Qp%nFe;l84;XImVyMx<5`@{$tz?{pkWYj+E=2+42iK|%dd{7qS z*~f>O{$P&WZ=yJ{BE)a*c@z56CoDMXuTD`UZVb#mL5`uX%ev~JHSFXyyKR}na@}W)f3QOuWc26l(D<{p z)Xr!5xVwt$Qf=3jL(nmw(cN3~J;?r4``f?%7i(!QF(e-q5GoZ=s&>ZBH z%4<;F&tAWnv^xNt)5R1CTHnaiS}M8BFc!O zYmvt&rQ!}qENEOwxR3l*1Ti2jq4MvRXk6BbjNyM|RHTKw_{4~&0X^&j=9gc-O=7N6 zEwN`yI*zFx|5>dGgdQ@-bUv|`uq@q(vG(W0rO05J{ikEP5hk~6xUS9{z&J!WCnjEh z^>vQMORwev>wT!X3M@ctpn~vOCZImIBr}ZDc?oB*FQn|ab7efY5wP%;rh~^G`J8wA z;q>ksfH|HvxBN2eITrh<6yI5xqF5z*M)NZ`kL>mvO6Lg=Rf$K4JJS!A zne+A^NNLH~(#5~cl&r0yrV#%J2F@flRAT~<7YGtlPF+s>%#uEnHwYc6{uvl<_OuaM zmFJlK7WBUVaDmcIh5Ny3bZ-eZo*N<)5-RYl!TdDxBD*1KDS&!jYTU4(E{k|+{qytC zO{LF?ib?)fgxru^E2mM-Nu@@NZKxD20cL3G+88LB=irEmgTK^#L5gMVhC?wVpUYGr zcPsNh@#7ysd(18?fvQ&kkPO{!Vmk?Y<7{G<4Ou^QK#Y>*?E9bJfdmoO6N0 z^1+p_*T6Y{3S?jH`~A?7|AF&GO6vWIz`em5^GV`uyS$Vgnfg`N2=ade1+GqCku_Wo z!=&wzlZATAddqIm6>|-+=6c+?)xxoxfTH zRh!(loTA@fBnR~c^AxRo}-JVFG6!RMs|MUR1bKpzwbk*YGCk0yHh?PK$s%(wvJb1Y{WQO zlE{}qNm)d();Jc-Tto9D zG1ftBH+72S?9RD?oGfLY?4+B(`;Q!VTu#PqC!0ksGM}@g`vuRw?CFS!Y)p#qNmh z*kU)w8R%w}^g>dFb<0=8)4dwUFS}{BPdmxtN|5v3y7xZAa`uzp27cc(bN%<{ghDg6 zJ1YBo=o_-*Ykkd(R&nzQwh8j%^NqEJcg+Zal}ie}ct*~xbP;Gi%w=1?I}xXnU49~~ z!ISTZ$h9%3__7UH!%_@Z2?J0gom&|fFW9tPmS(H9Qx9bdYS*XPb+UBNoU4j<6(^}x z!Fqm7jdAJ_u--t(?hKQ`#>u?WY{r?~0>@U9kN3)}P#)%qFk~$x$H{2ANM$WnEj#PD zRsd{FnzFe)rm3{A>YINtc-!#mZkf9A+C#+!WGB4~mlW?vA!b_NY`bj%NJ?J6`CBnc zO5RKTS5JIDDJFKkIFavW(Mqjw-mepFyjyyFet!|WczsJRpEWUevD>#Nd5Vy=?FZI_ zr+1Xjv~DKaZuRCj8U)U|;xaPALwT8&SGzH(#YlL2AbeV|B8SpLQU@^-sKy2oRUb~TKkYn}5d z_RcTkFWWgGZP&?oaEY`XhR)tH0npwGIYvXOv@CcWkf6r_{x%}I>$09b^L?Y(TEcCM zkOxdcVsCL0_>EdTgp=#2ZPUOYER4@?;aYDV`&{Q{;yYh#gGal$Z9hU*@|2}|@xsEa zpI+Ym;9THt4Y~0v{~cV*6wOo-kGoL;zVng@X~hwK-@gwZ6-OEs|6AQt-O@OU->`5v zZv+UOA5N2@o~9xK2Z~aLAPk#mp4aJNT^s<*Q0Kj%^_o~{=?ZS1met(5=-yn9xLRM@ z^|qpG)i-ap=Ln%KYdn>GeecZT<3-05+w3-QI8pewEQH`LRAp4Swf5Xcm2?rFu%x@J z#$j7R=|Lr}$D>Wm*7zMaWs~)rTA?>b$=a=uWBjID2iVdf@HctqY<#2cbWzf#X5c#!D9c%W6aZ^SR?vaTH|yV5av!NSK?y; zdHe9_7T`x!@Ni)P0YpD`KLoft!+l*#baxesS6pc@`E=t1g^Y~t^~JPlj;G6dr?LB& zjgZDOQEFp1V;{~HDyluDe>_F^+xF~s^=5c_Hv<}PZ3n}g90o5=PQnLoHVa=3aJlWf zZZz62EjHdwv?J-qV|o$bdD;_O4Bo)qPT;9lXwCKDvPF;X)qo{648!TP#LS8`S4k@C z4RHh3Fp8UKSQ2M4W-=CP-qaeS9ra5`xKi7X7;uZN9KK+geDE2n*JTUC;7K~ch=2}6%KffF5*v+72JP6pX-w9>8ThHkxxF526 z*iK#6zf*kHFy{SgQLAlRhHJ;aNf84tjyWL``wiT2deB;l>PH@{G=242!yKFV>kn!; zfWa}vWXJyPm}vD`BN`+kjkNa?jMo2-wEffoUdBJBal+g-guX!VZ>2pmR#;I``?%hun=_*w?h+f8jP&X8a&e_{uOeIGGZf%(C(#<+i)<8gp#jO%$|gxK1(`BdP^ zimFWxhOLk(fl{8`+v6cg1I~d9YHJ+h`pitwKUvH3_nnqec$_OQ5+gFgb{-(v1l8iM z-j1pooMT6?+}lc=uMwq6FnZX1d@T>Z&+j$?I4Vv4?^cZ#!uiO5Ty{mA9c*i>dPX8#vInzHnVpT*ouj2H(tZCtj@a zG#p~QlHVJdUYo6J%2%uHtD)Wf%PmOXq}JxKuU+mmL)U92p+DpZ2L_N!{e`aL-{L>| zH(IzHMD`(aKy=j#Fr*VS9#)^bG)~P@?U!S7DO5ynAE55oE!Srj`bYVh%rwtsQ83>z zTIQp)02y8EqbZYB#OJQ?K@kr-)T1^DWMUTUH2ugIuKif~asMYK`4_G`%MDR!E|sFK zG}eQ~bzhe8@)8$D%`ePi;mmH0*`=^_rgG_D{@_}11b^O`zelE>{k-Ln*zcKt0cSm$ zd^Pg>joDxFj=A^+a-Q`nkZUR6{zAr@7!eC!3Z;Pap@lhZS6t6wXc{NhBEx~GI#;aL zwl@zsZj)cLAnE`uws`DO`F2!OVJy^EU)8v67H=i{HmuIm2h zX!Y5uaSr)b-Bxyl7KD3@ zR`@6*#Nuj3;irKAos-=&MQ`BVgF0{Jw>`U%g{Jr;4vW+^z}oM0cc~Yg85u297irXlaXjwHNNqA+ydxUV*B@) zpbHJV5OAB^4rpA@;$$F3Jnfw2TK-(VTkA{s5YbsnHt0CrweQty*9uxx;ssTDuq(IW zhs~q=nr(%k(YZJjeazynQ>#v7X!|Hy5MmXf`iT@^3*%nM`+Ynld!_!zl@azL)0mVp)dZtGp+}=fwS@m9?+J+M19=f;pGc{Qgeaq+0{P*U{psAT-5Eb|0&_UD z@=jvlILV#Um|+6qd38H5GpW^zxNd_md5Fq_iHrwkhN>NBzx&dOD<2@#O1oeu7eC-d zT;we5Y}45y;j=1hbx!g2mV%$0vJJ(>d_46+rS-iu4QnU8#@XrB_0p#M@gOQSMEgAf z0kSW9?4g!b^Gr1KcFkt@Ew(T*QU{X~Q`72vNTD75IqkD9H0fZOKslKJ*_2q)gisQz z=z+>3lG7KX|n2q4l1gd#*%4vig&EDWf^aEo6R<6?26la z=V%MGqL$sHwn;|cc^@TM&K7;-`N(6TK}?urE>249JXB;i2S~-b7rJlxQ9rbM*yp@; zJc3tfRgKm^fL6ZyY_Jcb$}~P!`}NPEKPT;=?K@$-0gKH=SR00`&3468Up8h)qHqhr zCLf-~#afL<5{0n%9#2|T>avC5rw}BNeD?<;XqwfV=y)cVEtaZHtIet~dZdF3AtyAF zK?Qd0u9=WnB8zFvgk;QNiyfUfEYJ$G)nvVcKWaVh0?2|{o8+Lhg^%egf5!JsquHU| zIbDChsa3RdTNrNPKtQgYqsg-#X_*htOsX6Ul44VE)t9VBmvw5K)U9k9R=T{G;%SE0 zv}2Hd(XZCbl9yD_b~3WC(-)F*P*oHBR11tYFE29mS4)fq=oHvCC)m>$snldxQ~ubukkf)t7u7EeY^q6y(6018gnJN0C$6^WXM*K=(TwG zJj}+hwAg)Ru9C>OO8x12aZvZ?dv$vot)=EweLJSwI4xr0bsQPMd%URN?)GLqZCmjA zT;*A4I~*MtHV^Y;jT`?`?Qs?&iSTcs~G!o(4tx@_=G0UPF4or z*`_nod27?N0)1W*UeLlv_mPR5b+)K%KEH*JLLb2_h~G?d@`@||iF8{SA>LXTO2HaW zfT)wK%Nyl30})=AFelU^%$+ub2?i@$M-I4vBr0-Qs65%+Th}ter_H&Y*r}9uYp6>8;?_A(Ko_d&a}AWW=Nj~C4Z4mNN6UtR&n0aoZE1!#&7H<8 zBp9hEgc84KWcCF7hivrSE&2}07N?|b;Pb%e!Ldu&6B%(L?}rGq-oKMx#gf5pd&Gu- zZZ&-EKY6)BBDx_K9-~A{SJ=^B%g)L}W7U}nrZbRvOx09`Qh;I{K>l;0%e#%W^!>#< zoyRPJhF+Q{FWle`yb(nmqH^rJ5mEgS?t;$2v4$ebHPxE6Dt<=P@4xt})MkrTX)Yx_ zlcR1Q=RsXF^j#v}i(@@{+qJGF1hW}1!5g68tFvKS=1H%!-YLW&GG?E?#3>5;qj5Pl zHuPzuoeY;c>(UbbSo6* zZx26pR6y{Har)BUoVM(&j;yzDXk_eU57{_4=%69#TtKH(1*6=KC7!o9(6z>oC zb3Fiv^{e4p!~U603*`@X`lWbvy_11h2|S`W+zO0~t7EdtPSFdXpvxBW%Z#$m3^;MQ+|c`|}Yh!EA64d_izf>#8E7>ygM7C+ad8uzj0xU1gO-5a&7ft$u%~=+pb{;kVd%njlkM({H8S zcfNMw^}@mp27;fYPU;=FkbSzaLK;I_q4)hT1Tkw{W~Tp%MEsqFS?x7WWoc;<*%EAW9KNTgSB+ z5bt}W)$rhh-5~VZj>} zykds-7uT#V|70xI{Y61CfR``JgI?#Q+F~UYQ))jLUd9#<6s*5r;`zuMuhNXnnmlIOn_r#|nBHY>K=s@u&g2)W)q|T)*Nit_OCmE6@8& zJg_KD8n`>&k43Kh;}h|Kcgh(011Lq`2&vq5Dp2R;a*Mn)XY836TSef)54_NY${+2z zM1&z_%$szFv;W>i9r`wrRa0cQg$rKO3lVWlWaC8)Asga3b-82jVK~8J%GQZ3yUR6Z zUccg;sg{$okM6JVj|%o~E!20}XoHZWW(iW z%AL9)#cY~*PbhE#>Uc~AHxXE=A+E^wkP6gGbx&103l4BKNtEN(@ zaQ{!oj+Gyb`d%>@6ZXG&zsR$Y_!c+Ul+!=fNXMjqd>jbdloBoR;}i;tstHPCYsSA! zsDU@De9J9_Zs>P)`;pj{Ey+CNR|kJ8j!Hr-#Y@^MVEVGak(ZK;+tDY9^recY9MgJu ze@IDRO)TljIdYMIz4Z5|*P5=5?ryiOd=k}b6Z$tJY7I26GHV(9o$O+*Z^GvbG1}n< z=g@fTVS?zo&er096F~65Y5A`fTHW=VziqWb7-!Qq$+I8#9jgf3CM^@yMcLo;mYF8U z)MRk>$6Xp2fR4IEAVfkjF`RHXGn(K+Tbv}{fdg>Cap1qS0KLQCLXsktm*6dP613o~ zW3-O6$W6)wjbXvmDI1Qby&R9&BH&Er#Oj}HA4X|!ORhcC{8QgW2^_8Dgt&bisP^-H zTp?u)t3Td6*~UPQNVuW`?7PfpN;X~opA%0Ek&*d!&sr;D666Z-i%UvNY7H)@0PAY_ z{frtRNX2R}P;AJs=}7!xDBBXB395O7spR6h5Z!2-ElPb-RBL`4bQX?UI?;DN2V(cw zpLx$?gx5L#`Zpo)yFOsh8LN%~aPgaTynk(>B9rK-OnbL@Sw&OMoaX<`F}QwTq1vPa zFjfR*Wvr!u&(Tr5V^`k_h-JZWSLBy%ow5M!zP|c?r>xB647|6}>c~DyNVge&@={P( z-`B1RuqC*89BG{L-ZD35hcF-X7SF`jRX-5s@LXeQL0uo!THgz4t#|R{d1H4@TGRnN z_FGCHnFOVm6y=vxt`-8@Pr|ae>X)rRtkJ=7wX z-KITF#a6?f=`fEdwbi_fx|Rh#hu&B^N3iG{b6UQPlBFoKg#)vJqGx}K3X`hb)MD}6 zUC>`tC;Ayg-GEH4wro%S$+5tH5&~Ddj^WirNBmhRqF1oQQi*XGJIraMsO?0{f#g@j zAyG7fX+8aUEdeJR+}0bPtxD3yis7Ji$%?U7kHI(oM}uDq`P@lnXgBo%ZO#$xAK8o1p#VD38!_84a10;th0`tVX3(+^ik4Z1ph)3N$>_@esRUY zz}IiEA?q#H@6KDz>9jKclpJO9IUt$)zE~uk&`S6HLxRm8in-%})7x5^#Oz?Ue2gU! zUVW5*XA4FU&8hjR^{ z4y6YnvYk)qLCeb3w%J+-`e0kFt=|Fj8;%fJQj0SyOQ1TBss~N#%9~_1Z0u7fstw82 zWHrFsjhcw^E&qY_-;P zE0URE&iOfsY_8_2qko+!3pmtVS3@es-m-izOwvUrqnq$3<=xgR{VJ$dTGJxS>6@&( zAIcxvJdXI$_d+Bj%v3kACE?{4-SbFBFR&Wq@Pesa^?wo>Hbuj zTyWyk7=EFhP+HnLnP;`sQ-+|(Ux__H;KLwWk+&IqkYY6lr#Q$~e%y-pO%ReP=(zb! zR6-zIOL}y;f>i2tPc)2fjxfg!SMZ>14O26Kfv(rbjtE*WeWY0>Z9$jFYa$~KFOC{| z1F2G5(WQjHQKCM-=bqOI9s{&q+fP?S1kt2s(-CiH+63iC6l40AWEaKs96ZZWm9|~` zk(5OKt)DaXX9xs}(Gx7ITA+(elKVBdX&{A)nhKGv*%+LX+9Y77Pn$OVHuM1rNEUFK6~Sav zTSLuvDOHmyO+%OpQ0B4h_i~IOj_y)Gs>i1<933Vbnp{uF^5X>^j-9Xu_)XUgTOf+kT!zz&(V!1ex6cLOz)klyx zQz)*?Grknyu1U{rQU@h(A47{xBO~2eb*07~<$3px?qCAN6CRf+m;z4lm!`#WUc(p% ztg(V@8-$FtN{~#L|8-(4O6?Hp0}tGOSJq8k8n7MddH$%ALKJE1nW8yb;qppl#v57u z(K4H8;UGNt^jmP2sGren#crI#X9AKcBmMxK#yR-sA_!7~{4Y!AaGL+H?gweIFW5s8 zD|LqR<8Gmc1{vZgYiWz=c+VY!jdq3RM@E|%ZN2u}4L1Wgd`)}y-_c-pz=DxF z*-A@j^6i=oa=@1-ZLvbl=AD)hUPd5sW?W!$rW?V)1eMn}fXJ=3%#6ZdZ$tqol0#dt z0M|d&lnq?GVKq3vkmVuz^)fPH{0m;3CxQpV(SFh@Y5L0ssLnxh+UdGOooJV+w}Sz+ z44U~;ZHp{;1V71KgRqx%cY*rf^- ze)dkjs8p$SblKVwR#fw;=w^7bg^c{NK<7?C>3gW&4+YxdpiO}Fhzce&(T8YZO641k zTMHj-j23^s935ynqk#*b{S<_kFEJC16`J2f&2gj~Z7%gtNK`^-0t|D1!FcR{z&P&g z{tEXQ*hx*-L+;r#aOq<7r&=3ma%>8Y7z-Bd+}|?q7q!NqsxGBqmO2q2&7Av%z&L?b zUAVXQ<4N9-MJ)rtyi|hSr`^aw!KRnoq$IK@bla+{k+}SB8raDp6Fx1XbV6hqE6+I^ z6yQHbx{Y_RnsYOv_$e`8*gHfYGyjZXKk3l-=|aha;kCj1Nom1!})I|TIY=&)OKMBsAb8)fQ#yn)&L zILz_cZ2WDI3?Ph-?H>tU|C_R=d4E@E-HaoDg{y_AX&nra#k)#csR6NKlJl}qcyYh+ z@cN4`kb+tmL*|3}vbL5KRj)(#);U35@Sie5QLlGLwpnx|I0?6igg7Y1k>)%TG}k|Z zD%1qHMc#u-WIyOAbW2>O^VkJW*S*De1UqYVgwJ;9y5Qn3t2d7QROnoS-@=b9t3kwz?q6ZgZ-6|g(8#=3k z{C?Py#d6}uv(UK6q6yeTv8?}(Hv0Wo0(jHEDjhXh-_xr66r0ym@Z1L@0GlLah&9fg5ubn?@}8! zHICSpnseA*zY)QTVp|;rFBRj3VE5onWv3JcUsrkSW)LJ6dM*Al&B92*)gMHOV3G;Kr@*JyNy5L}?r@NkNdmg#&AKS!3~A5V$3o2k;w zo+EEVXuq~y)pmaE4OM94dLLW&BhXFOjl*l=Np%+ngKaPrL}@krGSDK@D#!r&u1c)D z9ZtylvDAQ5nxP(`ylPpLh2=0X@Ht^=cTMVSyS1EyRiYwNscXEJ{)>Ct{*4|kRF+qG zxIANtU8bOUqv_2{Kf6FHXt#^7gHv%Gw*x}nsB>3M3^v}ZAm6>5 zJ-RRq)^U2zghcp8s{4MeajH(4L-&Yrs^@;~Mk=U|l4;4;IEG%Ew2}Aj!1KCO=l(Jl zCpdCh+l`wGyw(z%;&RyyOi{y$!b--vEfy%&jvzeUUMc1~I}j*}PsP2w$|x2{;J4nJ z;~;fDUg99r<}kk94GE|wb=gVlJH#ko@HpIiy6F6K{+eT;jig`!#=zq+8Y0Vbcih=O zy4ZMr>uN7#=WXvTP_yCfdgOKVxLRhGJ!|NGg{NBnZZ%BtVDNp6>mK2Gdyy+I9PWHD zoktD-Wg@EGPC<1x|NM zb}Kj+R}#!!lBzc=b~i7hRA7#p#r6$@XI!JzCUe#^z+WCAr`4Oh-BZp*=&pW7_xx;Pap-uAMAI>;%@26WQx84 zgo4q9siEB8XhjQDMJnr0zUwu_DKc%lV2*{X1Gs$s4WUo7RJ~_!vMl@%#_yv+J4;#NE<@4NjuUg#tgPLTg_*!pj33Hso zV@K7w7g+7ik0|HiuRUJ*)2rzwg81mR@#17B9EsMnd#LSGadu7zjVU$^o?YuhNPoAp zA@zStJ-Y-T^WUee)H%*;f2QL6$w%4_=OW)(uRm%#9^}6~YPFVIB>v9fSfKOG8nKt^ z61{lU-meor&9U8dy2vy6lHX|2WO8gHM8!4ySm2a*_S&H_>hU(oPSVR)1*C|*&}b{2 z`J+qARN0%=u<(PA&pw4?%?VkLEaN&-uFMHhZv(1_d{1h8EML0AB5^JIf0H>6B zbZHDsnEYmC@FO1kE>dPhQGHyr-?5awXg0>Nrc-L5qg+xej^uunzHHHlbtom6+9pp zY~P?^A$KakbhsHoA^ZxX*lwN&9FDx|kc=(mRwaM838f$P>7jDWDrBU$O2f;$j?lw< zaK=}pGXRDZ zmbAXxGSnpzJ|mTeGvW}-RITN5`xdJ~K2q0C)mlOZ1~1d<<`6~et6J6hlh97m!Fg=()pwZ%k4f>-s<~r9wTTBihT1iQbkiUXp?_pu@@u)WrlEDIK z;y2BUjWF3f?xoG0Tg0tV{iW zbbV!5lwI5IV*n~8NC*-tB`w{cAR;X>jKB;?cQbT6h=8<+Lzi^U2t(J~#d)3UTIX7e3gwkLvP$X7wTp|>+lh#42G3Ua9}6L$nF?c= zNS`XuXIu}Pl&g8Kdw#G5#0vPq#Nvp~?*?n-C%|_n8RiZaJ$|8x+%AHW*F}K(c{Scd zLig_1eFZUixJbQ6-B0*I$Iqw{>_dq&r{Y| z<)Z7q1Wc{JM?`U@(0NA8s7G+&Q?v?plFVV|Xij3WqL%cKp|wA2TcV>b>Wls_3B6Q> zXAr%aPS+r8Yh9J<=`S8`YhRZK`5-A-#S%uOU73JsoLR^h-CJtFZodee*oURmYSSQF zpJjg)8M95fP08T>YLN{X=Ad6FAToJp9$_@th@YapPdFkpdSPAGYDz5DP^j>)!Q6DV zJTiKFQh24ct`6zeRn+-YgrnT%O*(W^h#C2~#6j!r;l**k2exusbg4>(^O507w-WW* z82nG`L>VeSDb$8@CFIK=R~8h#sSCzMXuN{#iuD#D+~$1w`od{zT6yOaW|8VF%E|uQ znk9@*3V8i;BJ@@0E7@j;$onmS#AH#a_GZ-7s=9FtTbw=QFFMv0LcQQJ0hGEgo;By{ zH_BcuC@+^7K9+K4>f9n%lo_Gj%38aF!08?Mf&<=L`x(^+CmC9tmH)iophlEkK-=-F z!q+)wj#s0O+0AO~^ZWQcaehbL>d{Ul#&x1hGE$pl;9tPXQzvebyw=T!W5eGH_Jh z`h?jK`6Y}tK$6{y*Fgi1B>h}YNcyY+KqIDr_6PguhLb_GZeX-x@6FPPzd|n6H#?fy z>Y?gg^y!b;wqBf3F|E)27Ihmm@M6#)7QEFS0cba4PXIL_x8I?t14lz+_2I!(cZa{1 zxBD1RDAa4N`4C>oTX5{`@vVbA+gOQ3g&mQ0GQ)+d9{N4KY1zD?9f6rczHqgy zPVUpMP{nV9;Oa^>I47h|3SFBcF*DD?d{_1$HqCUz1sYZ264 z;dwS$(^dMVd&N-H6KE6Wz}m1d8dx;nG8OJMv^Z`Pz3xQV@EXXoXArpkd53F=-efPW z-=}i!=Y(yVfXXYb4JY0tH?74onZCH;+%3F3TJpn6|JCq7`RnsvVt-(t)#B&JR3#t= z=K-bQa;(TQ*c~{RQzb_q@R3;E@?o}l{P@`CCqM~+r$D_xFsszP*__y_b800eu=D0* zO>&;xdLhhyYwDbf6Pf~c`!5Q|@?XsENQPY-QKA*pefZ;%Gu&nxfuP?L;z6eYxz(D` z!tI)Xs0m^K9Q0PXk9&j?ahXLLAugDb5A%788FjWCw20GZ0R&$&M`AUz6`pu%0qSiz zbu#tmtxZ0r_mli)rA~rm?)1wCFAw^!^XcU^c*M=qdf-Q`^V#}L*rf;l8QkPWrd55% zI?U((5O#3)R@PDUHe!;cR2h+6IFXZ=#xY^0WEKGRg5!p`3VWe;$!Q4SW?4#<}+6XdI0uNUN%V_qaVe==Dfd62negrMa=w`%%rsh1}w07q>hJvb1=LZ zuSK7`w=smZbr{5I`@cjo$)VAWK%i=CY*^7cyA=Q{YbY$bfupv(-$O2klLPb&3JhW0 zxzGe)B?bR`Y#Q8!7Y!NYLkcu_B_AXgmpIStrn+p+o5c5%Is7OwY^^DFZN)kf0$crY zPRx?qpofo&&)pg=v%ghgX&jspr`&fqhvg%@a(|}p?z51OCO)eo{=Ukl{4zcqUrFpCr#HV?%n1fuq)2}JvXD3C`4Z{AarjuF7 z0cHQ?$dI`dR8+Owb=|iKT+ukCn%ldp&AZ=ibAUbDfm$#BF0;dLD4yct9mM*OvNUYa zN0Y4o@&0-gxJ-FH3S)xtKaM2y^u4U?KWXkvv2i$2(_ar$^;<|BefG^&Xc9I2|Brx` z&$Y7_1HZ{QFpr{La{%SK(^m8!lW=cvPD17Tffv+?aCCTyDnDm6PH$mS?4|yKwWFw5 zMweQ;UP1!j^0RF;o^_=7Kjr|5WI(Yxpbt#0Ww{Mh=Tz#GQ1%9A7eX2ZfE9dB3*xB| zK4VVLFkG8>o^hRd93->Rf-H*}@Nt-d(v`?3&D|qGj=Eq^SLy(hQ#>L0>weX@3EXqsqyn3aMEDw+J{B3~Blw3w! z7%z-=q~_fv{7tJdBh=9UTWkjD{Rhf7G*QZsN9!nMWo1xV_zxu5BEFwyZY{zNmF*fi z!wIha0I*FeC5!~AZK~dOJ3yP@DgSrHsmsdX|78#Q^%W!Kv`XxAKv&ty^H*0%s0|-G zYS*|;d7HA0;12C=+B>XwIPOs0q5KHP{mG`xhC@m@jqny!>)%-osHV!{sX+wV>?FR+Imr)}*BB^3>ENI#qk6id{X3*0yw#ZvaH z3EK=nQg=+z-hD)=sDn7To@bt2nla=79oka~6{oN|d}rDlV%T1kxZc9<9_ zmn7&!cama>%|wHN0XlD~!9bh5WUU#QZjohRoMBjIZWHG4_Ja{L%Eq!--ZKWe{PcVV zZr-z2Ya#RtnQrd4V&4bZO38Dn9JN09`a^yMqZcn|vxCwXFj+9(5nM{bzqA!uN=huk zsM`#>_lHBi?wG!n@!d+IVZ)!?F{l=uQeE2|jpVdYpUHO-p0ZmoKA&O3M5B|)*GtE0)qQ^ANIn9nXo$jJ^A-^Rc=|sJE zKpJ|WNUYR!$hrC+k3w|6E_`5~(WEHQU!`_ukwa-ul?Wnav+gt9^)K_ZZ%9i&LIAy- z|B3j@EH0^pBbypn!y7v9<(P>~I6sT%Z2i-j9*L4++XdsmGa-SLgQE$&P7xd4bQ@@y z?x~%+{8)SV5BcS%&SW+D`L#M^HJ>1_c%D*If-r^j;hcRhI1a)RzT;o6+r0b^N7Wfx zCwnSBx_ER5an5>rk508Pko;#>&Ox~CL{^+I!m5%L-^+BImOIr!3o`$Z+~QsQA~t?H zpN;%S?mJY)54S*n55YU5nXm?xB>}vMCN79L;{#RZSo;UbZzG_#m5Sg|+6v1(PSNyT z_`{Hg4$m8Y9vjUC`ExNN#-BYd_%Y5#nQc6Mt3cC-@ATosE)gvGx&G5vzM|T55L{+ao$s+M#9aI?Ur?VvolQbr}*Py*&C-lq2|RRWojG z8(MO?F;u&o#id9wBuW>^SKd&}psuSrspnUIEgtUdwsz`k!MIdxG~IGpAoQiWeOqFU z3N6b#1^j1^&UwhI5Yg(~dcy4y9`cn*CBp4b_h=6l}p$5YG28KtRZk!L4KO`EojI1WM-)!F4gq@e!Wbw+|UomkPbjI9Tu($=;a1 z3sFe1QJLBQ@v+uSBW)?ny^KG=HMvkl%LfiGwlPbR7fYAAp$J)jZ)zMX|1?%1*Rae3 zFWvFtUdKxxmfcX*X9%}Xj7uY&&%X5z^VT#0GQ8LbE8`3CXND?dOThX|hx8n8qN>Y9 z{qqsNm>17CZ$50!@H6r=VP(NTv)lAzzvQTKWFy-61{JdL&U?9_C&TSLG#dG~`eA2v zONEcUW4%k7wRLE;C~|bDM~qk0?{wN$Z(_1NVXcRke6 znbk>zZIx$_XpOM?y5IUe`YF-KbQY#KxhJ3SRvsXuA8_)0)|lj_f!uU|7++n82MsOI zOHp`v_h$h!e(f@q=ow4AE zlGMKPxLvnZT`ch#pHbm&9%JgU8SVe^RYlnUr0~10-`w|mrHV$o>z|57iOnqpRpFxi5Y9!K}B5Ic41v$OX%?!HM@pKBDTRT6#+~Us{K|ZSpyBo;b@vHNh zgv;UFa_xQtVo*T#cY}cOX*U0w%*g&cHaN4hrVYf6P;o*^{^si9L^scUd)_+2^x$_( zq0x0bSs_(My7*+982JQGZo@~A&=VTiqE1m ztZ{|SWxUHuaq^zw<9+Xw-mtQB-ZIrx5&JhpPi5ru+T-WYK(UNdE)$n9GV*Q9w}kd5 zbl~`D3d8Vy5Q+%f8@umXRqBrXeXRkG*v*7ra6P!e%C_mL9fh}NHe~ykvN~@ zitUczU`R}pgx!kmTLLl4UQcOQmM0zs0*(UJEs5o67eL<(zFiBAXW+wmK;)O)mT~r^ zMd!ZMu1|8sOFbXTBcBOQUFneXS^e^N(s$T&-hQxuKrX!N(%c{D5?Z8Jjf{{9GXRdV z2BEmRMNaCLSF4@fCC{IMyMC@Y4ZmbFVO*Lfcj3!kCJNQxycc6zE&Ft;Z(b`rPQJ#_ z*`u6)?{j;AR3E|u-#xnkuyk5xKw^)8&_j$fWRHX-bZLS*?Dir}tw=}tO$FhF z>|!F}65#Qgbu}s;aeqXR_V7ey$;Y_SEv>%i;cW{E_troFhC>5>4362|i`IcxhD#Es zwKcu1T85=X>c6P)+N0@_OMiW-R1s^UUB>_w9A>98+wOP_k0PX*39jWi&tDm#pd=tNZgu>UOq7pu9^>}j)Jy{UK`7t$WOYyng93XI6yNJu`a|CjFiw!rx}m8^HucuS70d+Oc_C zs0UJey{!Y}G6^V#R}krVDHF*@mPT9>9-R?*ZF#2D;H5SoKX#*guYg?9`URUdqEPC;+6*`2)Qig%VXb2nPKH z-kE~KGL^YWi?tL2q|21m?w0tRQz6B`=sYBFD%pv0kQ5%W(E}Ih>%2XoItFxZ&%}o`^q>?@$Lv7jgPF$jbaJ zFRS^dG>;Sv-WZYh6D+x7TtkazPsWE*&Kx=e=Jsy&`I}v$o)iuHk|rX|2o2J0k3nVU z&On3BS$_K19VPMj@Bq$NWd(q#Aw?qHY&G|Sxk&Qc7KYxjg9Th^A~jx0_x ze)qJ@C*pHrnCFpVD|+R-3>>I7ht-8evk7Wvg^NhwqDM7H{!lC0$FYErbU7UyU)5Yr zvKrR$`^)sMq)1@R0_B1G3eXfYmqddl8nr( zdy=PoptMWpT$=80VDr)L+d9Xq2ghv(ZA72%uECttCDZtn3GnR}Hlpv!3G>zkukCWA zHRUAK_SEmc889>OY>$}vEZPOcn3KCCruQLfv(!v(lnY-QCxwd#)S0Gg*}LG;f)>2s zPkC{tb~SzmeR&tK)zr^60f7usw-!GC6~w>q##x*jut&vI9EJK|oSoKiWjr&p_5lv| zHhge7tgU_RQxdzbkAFx=ZDPk{(kxNIv?c+>6+7*lkoR8$V=*k$JCfCIPG_|oH}{C3 zV=6%7UToxN=$mc^Qy|24Ew>|e`o}@3RZmXWJokC>Zwc>B6RidlR3tny;PVHo;DRfA ziz98e{VOk@K1M1$)h388Y&$-KY_IKZhoI@I@fpgMxLrFk;k&>6P5L`pD!ub*9w)Og z?KjkYj+N)R(nQL|9Um$-b6N<@?$H^X-i17-QEz$syPMNuvjs3w=(b0+%BH)=Fi%~2 zp)=nH@-|3Bl(R=n%D0Uj!32RTy_1ir|CxxUczBQob;yczAf9)^NACDbHBA8a(iU5d zQE?vQn~RHS`QpCfg^{Up&d;~62eJ==<-~zo*hV@IX;JB@XmsMG`?ki8?F<>&F_kgwE7_u6!v&P z1dao}_eZ!;Gdu^-?%@8+E|*AP-NMRgo%6NCzF@+Bj)xZpnE9bmFpPTOY9RYu?!`rF zdO#Nm)u%>}9l;!J;uZbV=uy_7Hlky$6imC+-g^qBgV2E9G166jki(^5T5H{`;a}*& zYEN?_IZVK=oya7h2qZeQI~6rRh}Hq*Hzq+A5?NOIA>ry=>0rc%1>eA9iv5sVUv}-w zo^{&9S$=%b@A2Vv9^Jv7sG(gt)Uh7GDuHoQ%CSQnD}*uqC-tsy%*8(Y@9b*|+Iclz zH6Lby6AP|V`lsJWAB{OkwXlAUXkP1MCs10L33LZ$BNufZ>Yzkc{BHU^A65wANOt05 z840MEKA2B6OTNg(^HEYsimFQcqP<64a&sc<>duEO%}JgEG-{-=ZKRIw9yf<;zj4Ut zZTQY@HZfKit%&RkqE!Zm^PZ11#AgX7IXctS{%d_Hc~fs#JQowq!PaY*%1}p1EWt6x7AI{7o|K^PZ7&=-m`Qf{o zP&f)I;nwJaSP=A56MM~n!OFm3uxweJa0|_o(!h{-(kIFKZ@%fDq|{pbw6^25&deP5 z@?Wv1F^YVua(w#WG+a`CGke29^|Q@cp!$(P?;Mm*K@+`v<&N-yN;TNnFK5o56g)!S zx9sj6vtye^#;sXAcc}3U`W3EzT-$2VN8xrw^;6%f}`DWO*N4oY9J#vbCeD>lJC9OMtm>yQD_^$dBwKGjqWWST`T~VR!yZsZTk@?lTKT zpoSv*i)g#_NBM|)a) zZSf{Trz%9fHqHQe5xXu#>a)Zmh)-2l>aC;(G>L-V3|pCA6;OjG9Lk}S#W2uk$?FgP zQxvne45S*0Ua#2(`W3)m`l5vQosq1He)xHlaQatMZ9C#x<}-TtJ!bhs)e4;j zGiINls$R}zFkEk>2q8*&mkX)f$hctf-YYek?Wb)N+(Yz?Umw>lad$ThZ9*pp+V6J- z1Eudt2(=ZBvpiM5hrE2Hi8LRjibhPk`07Nh$ML^f?2SKfgb6<2ca8Nhe_ayeY#vP8 z>4T@HN*LuZZXgXtxWiIG^71Ty((&Lw2oJ}l<;=z3W|+NM1N%r7M_>Jju)3A9YX!)) z@qmJO=(NAaT%*6HW8ZV-Ym38De1B)1V($Vd4et%>Ti3QC^Q7sZVo^fT5oUh&T&qtL zwS@=52^#^r9Y5PZTphVb2kw*?J1cK2=DRUrHH6dy5Gfeb)uEc)#w+hrnh1-&NvKP$ zqz_ze!$ojD-hCjc#%p1*;L)Cg%Fm=1>@aF^X62c7MAlLSbUTWm;UUJ+oc62g3WR7= zv2B^?P^`Y&XJa$z>w)5-bhri9xGLnBRJ2`vP1X-TEAjKUbCF~Z^Wf!#V?_`)fz)L{ z>G)>pvKJTw(bvA7!aQ*caO{e`3*ir0RK+*QL*D2kUp?dWfkHdGq-cOpB2+6esj zyRQv^p9x2S`+(ozW5$?(jQ>LT8rsLcFQToftqGZ?dheaYCDvkG>atG@n`gIsebi-& z2FBxUJhB9g?W(j-OVbDFh>opwD)399rmNH@g`LhnrY^Zf&Ip*NLrePtV$9+wS3^SR zX}ny2ga)t?q-@M(%w>Bwg2Tsiwr6;3Dy>D1#gQ&L$kLtlt33P^IuGMrzF6y#LZgsA z&h5+A!rzT^c42nm+t+=5RdU}o*SvO@47#)|GpCBhXS%am`C?M{nhqV_)}^glJLYT+wJ|&)$!8A zN*>!i|7Pbk?kT60O^CF!9mX&+UUhN$?o&ng68I;Uk8yi13a{1Qo$B0j<#K#h4xCNs zP$@6metc;U^4eisrEDch)=AVRl@{J@19Zmfhf*b&e3wd$=azZjxP3e?%wb4;u1K-! z<+b*8>@77SN%7?$gRQL`y**r{5nPoM7F8C#h*F78yY%mNi{pyA+car1(qA?XTC;p$ zrHAlMHpKIz4M&SpP6`Z-0gIs(V*_hRdCmc-q@rpLN zMj-j8@tlH658HLXzzP5E!E3yvtJT{N(Keg2L+!!@3fc+aq_Kw>+SHA?eD_9hk@>Om zWM8f7&BxbjQxaDwj<}|B!M~&UsGHjGj<( z#lWHT$eQb%SD;>&NnnvGN8eTbTC}%vi67eGI=>;yeTrIi`(Zlv%T};&OnGB=Lre;E zkwj6b?nF;(fM;clwM-de=Ti~@UDSI3PO9$C7GfrJwb;Bma?5i%N>&UVEJu*85 z{8GXv%m0q}4s4WLLbu14jCWkTBJlzBZL&70jH#|U)VONko8v`P=oG_6JLe1=aKI$5 z4+HHZdIrY&?e%m<4o9x4e38MyU;Y5$_O1Hwcwqfci{E+!F&b#&mA=G*Iga+dYAN%? z6gsCqd}rtE@sshG;i0Z+orOw(Nt#Qge%$#0X{h)|Q7vXX<31v*r2J%-MP`joZj~=b zH93;X%Yf5yUr|bfGvt=V+;tWM-!yudxH!=NeBY&4y*+j+pP$A!mnS|InwQPU5ZZ7Ip($>p{t8&GXr-}yz*4&#y%c*iB%RbvjS z3S=9OVF8I)^k0bxLwg~dbG)74HmlYM=bF_|9GF3-%l4pbIe10w_bz0L-+I06N_ho|Sh?ZW2@loPTg zz3!9J0W)2UjHe44p+)h{n#;4MxGou)kFeY*D8O;W>RwsYTe}s2b>QyG!$}XoDb-rF(iT1i zpr9J@9GhQc8tcw|3rd*YG03{o@uE`vOR1RfGka?sUB%5GbGP;W%iaZv-5`^Cf9)tv zaEx zQ*d>N4+|cIN0EvPKZM9VRVtu7=M1^203uSpbG8fzD(T%@XeutFi=@)vJ?s$UL$sv8 z=#Wj@%R!ryC%Nu4W3;3023sE_7&a$`_b>$fQj;d})r&wB!$Cj*s}0`=r&VvaU49Wa zJDg0}LrXWM?8#T6=h)bx7H6N7;x$V0UByIOAraBS7Nd*qDtf}7~#n3J-G!`pW56&e89-zp1L4L9&hRRU}!hQ*?Zc+NA( zX3%MgR^EDI>sH0PO2pHOree0nTPTAIE>VWwuunHp(mwRPN<0BE*r~@q+r^kWu?>&u^I1*)`YXvy>s5O_$eI1;;f8Yu*Nf z_S;SxsETtI;>eyE9+<TkzET^=&E^aGUW_Ym$`ANdTv_;X1UWX4 zshgXGc&E6#J1y-KNB%qzF2SXiY-FB}ufjQanfEfTYJyZf@lAYQw+#yM2>l*5u1hU?Yd~Xtnt- zvJnnc_d>|_smsX|l+sAyH-!kBU^;pq+d-$S$s26M*t7JFWANO_8L@BwZ}GXM%;1i8 z2J+UQcWon#ygN4O;2Uy*%>*3>XqKFl}~) zzhDFD=5PRmNr-u?L`6SH5q#&w;N`(`yxG&ngM{)@a=-oHaat)bk9YnOTJVcwqUz<3UkPcZCfXxaR6`F0m;6L`}Qq^$sP7 z=MrCIrBL8&5oDJg#s1Wqq^gAsnWS&)R^G{->s+uz#ro-K)#vv;YRj_S_A%TE8um^e zKV!ay-NUpqY*6y_l*JH;%au%UuJMqD-$2E2{ezPBj^X1RBj}yv^BYJoPUcsJ)ytA_ z_S@0Aopi8%T{lW;4YoDw|E{Ad9}L%}n%bm6nMj_%H(E~)C&K43U-0I^?|P?KP++%U z118dUaWGtRH%{NYcvUMvuJ5Qvt$c@SBIj2W*fjoE1h|i<(LuAF2gdmp;*Z&hxW3{; zxSDzWfb&c{DXtGY+=nBEyybJSp*z*XhkaV>26Y`@hUI-laFLi(hzm{Y4T&n;8-znn z4XwF?J`lqA5yDyi6|C6#u>Nr&M5v?tlqQTL-ddqvRHCixo0`S1ld?md(tM z#yjg%%HT0%848UD^MOPjPvbbqas`&jT0IsMk~~jISjCXP9^Xw3UF+a*Sn-*YS)T&OyCdP=S7rfbmNb;JMAX4FP~p+y%0S)fIQDfi6nQg54YG2)ri(`#CIv``kKB0Ajl{M%{oW( zc<~Bsv0B!w;iVeMd<}zH9D1(Gf7IcaTKr$=o1=NLfqg;ZG0wL_b!{`TPwT|1{52cA z$uSwVl=;Q31>Hlykp?zUPXcnz%tbD3%G1^Z(Ogc-6ER!>z(BmdD*^2^GFK z6F}DO31j&L=2jwDA=+_bF|Rt0oI+JnQZegyd6@heB0r%GK0ml#fZ-I|zlJiBXIrtM z0rVq1EbbRk{e{q8`fISvcF*-e8h_6%rsL>IiQ2&@b9XmeY8-!xI8`YB2kzTo`9FAI z{U`2oIt}DwC2}w&Y$OMiiS&~ahDqiuSH-Xgj_A{>9?o%z1E3!4!kV07 z-XAqA>d)t=l@4Bq-OyC8QvcyTX$Np%-1J{Grl_0Z;Fi~|YCM3kQ1GltV(8pRFs(9* zqh9M;Z?Q8~EY>+^re4cpfSYwgf-w&Rb*a5R==%0wjP#Nx?-dQu>|$lrcZ)hs1|4^c z2A>RwjWCT7{MHj9{OxsYH;BzqXYrD{h_#rifg;5EJyW&n=A#JezVCmxXqh};n!Uvo zZg&(lYnQ%L?t61M?HkH?huI>&+F!Gir=6KN28t=HE*#OHa_^`@x*BaMDp5OYG3Q{@ zWaM)I7S0Z=u0YnicbApojb>6y{qhTtNL)xgvch3_(%BI%v|vpC!KQ6<4$ROKAob=W zKjnjE8X4f&rZ*ZZvfBmLaI6l{dUR5j=3bsAX9MJNu1J+=ARkI@cFr7~Z-&{CM4c?n z%C#1$0I8@vA@7Voy*t?l%w@$!N?Q)+d1I@d`(jIX@=6FQ46g5whTXkY(db1r3Q&EQ zS%KB2H_x%l|2)S!i#+eixav2G`#BGA&l6b9qZ63(KfoP*%V~U`{kq#381TRc13se+ zT%j`<8J9Vd8mVJ-@Z61$8be51c%_fn2TL$m(Q^4x=fOaU4769B4Fn1lQ=6zMJHz=q zZg#0(*JHL>EO3Pmm?82g0p_cXt~IczFB=XjwoqJeX;jp#{rqj1CI>qf&6Fwa5y%Nu z2>CoLKU0wmz*8;Fe=OCAOG5kC5B|(Ns*vXxSltC!2H||Uvoe+CJfo*jZ*9*m5wkK) zzJ^zQxWu$>pu0Uk9<_%T1r%-+XQ{Mk=Jev=E|Xj|NpmQK%Z$2!#=Civ~O-D8E#vprJWDxSo1uXjV4a}Eo>JL6Hq zisO1Ra;-hwBvH>wXMv84O2AUo-g|JIcWwNl6*N=80D!7PAScH-_szwY9HNI0(eX9I zhhQ1a+Wb0a+~`GsaH2=P8Nf{c%rc!lUa;0bO zD~Kl^qOrn{AOrEF^pASNZ35Am-8p*TYBJDEpmVz#SB7}JMwJ_s?_^}KT)T;D-lXx# zSF()f=&VrvX~Nk$mXJxrU}Xowr33{9^s#Pih#qQoK`SzH7}g2Pf1}y2`V=G8D2->y?cy=CHQL5miL;9B%DI=&-~V$Up4r ze}J7cxd;+O0$E8y3qCX2cn#x-H%iTS7Wx&=fteiwx&yd);;;|Zk`=0Y&Ph>I;X*fx zXPUZbo88)4s2W8qE%wi}cu`=%4fqY0TjIBTy5yo=UoUtq9DrpEzB4s$6; zoxm>uDSIx*Un#r4Wc=DyXL$anFiUoh%%|3`U4>?$U6d8f`6Hh*+C#7@U z&k%Fq_1nwW0E{%yKF+(+sIs*S3b61A3pYBgoi`uD(|57uI;a|$l1A!b`28o6AU0ci z=?Ru|_{4R^AkMvI>IxSUZw^65zbGlOoFKf?yjRCmlt~XZkr3{Omvw77a0#io z8deOmbrip;sTlNGO962MkL3EJRuVYHaCG>vNXK4}ZmZ5ktr$1i;$2t#${m$yX}C4{{qf59 ziVdwGl^tUAIIO3_O-UJ6**zter->ev9P?aDQ4I%|K*}dL``NdS{I%ek`W5z}p_=+Z zFQZgHbat@xjPv}oewSu~vMn+443M|{WaSj9X!*&^FSM)neb2?cKv;9-#NA=Wt(Zdf zG7MgP!aJa8@--oWDkPZVT>=%azW#foca64oQISTc)AT*#_7HMxs)?4-V6mRDW=YX# zF33XfBeK-;U|%SiYDu3m3B0kBVgirf*!&u2vqcHEuD5gv?ARCTE-`vJSrKz(E+4w> zrki+VuNbr{%_k(TjH${m)+?A5y`VcUvsaW@dvz9qSE5(r+F(f07z|3&NOUXlG6`3T zR4Z^o2sMJTWg6pl@f&>uUn5}UJHvg+Ah-%%iz(G6sP&>;6 zUgavKL$0Hn+u&TJPp+d~;PmdZ7Ol>3W?$=o(aQW?Y^c~Z~4h!EKABrTiqDfGdunx)8Qyg!wHVDS5!Eb zDL*!HcrbpGaB6ewU^MB)bIkH>()+~*BLdP-Gi2wAE!WRfyboU^MTQ`)jYL)Zq zP^Ue_?6y^}K4`6G&DGA4&Ku2bw^!VVd`~kDDHND(vDPo2$S0C`m3=*YsAe@R@lkN( zUZ1{po5V*@n!3un&-L`O?~`7zZU#rFL_pzZ#I)TdEsv!6HX~&ZRJ@Hu^c(R?#o8`h0;koMXPHZ>;vV%T~_Mfb4reUZ(63wPRH+^H9+ zuW%k*eB8l8SZO_$Q_VG<35s9%?ig+W3YL3cZ9wU9^Zqu>Nv z+q3M`HZ^Udm(qMXp4}xm9T%_0GCy2X>Pm;IYpYo)V-(KT^h7Kl!nr3S#%`+Rh%dDf zs1=A*sta_msxj3S)6)0nuGoB^-Tipjp7!*zVex0I?$DOd!+Dyy;#!`c-zD4e+Vm=Q zig?DS_~@CvC37P-6#qy;t^neP^KlfpY6JmKxxdAF}y7r1GDlJj0-+PKbp%*CE;!NYsbeJul zM5sdAjcv(Cd7rDkMK=lN8}?w{Q@8m?yFpRb74`x04dwyzVal&wSl&6Z?C~iP-!0M= z8lMs{DBvFV-X_VZyd7O5F`7g!$$+|SlPj>>j(X>iBRp{OZpR!naI$rfVH2R6+ddDn zeDmtX2ZaEDUTR&m($SGKbeQ_Gd*&^L4A1NTo_EEGAEc#!H+A|K>pH)P%+bRi{? zMWI6he2`{QOxM5D+T`}|nb6-o(AhX>kR2x*IJ$>me8ZvZgZ%Z$%CVDh(|FsXp9heY zJ3L>xHAyiwFdy3y)iH6~`mVjl%gdW1w-0UB&ooUo!(y(AWN)^vZ9A%_)wK#weW&X` zxfy;t`qMuii_lor@QMvvR1r*s$4Lb_9(l)61&Q&IbpMi=6tzxvW30AHU0#`k$QISi z)%hDwe<56b_eW^}uo<^ocuHWF&v!ejX?KdN&bRSLIj_(vVEDM7^gGlnZk>sww7R=s%cQ8tz&xm0!Tw(8UQU@ZwswKmx=#?7(+ zEU2ROSrEFUGfXxr7)&SGAZd5Yp(6kL%!_d{(%ids0oh+b1<2Vuxn1ImEQuYIq)$`> zx2z;pZ#ld_Uc7rxh4^Ln&8elfKO&)--4ADYxlV^Y`6>-}m2?8+LluKtk36v-Zna7+ z*`W@%v#suk(Jo#@bb3>tzWAE;q#w9&n(IJniP~P5y583yOgZ$f^4MD&ey#@;-2H61 zmZ39yJ(y23dx=Mz$+FF+hHw_EdAhb5mK622E}97amL2}dr3dM6tSE`{xb|L!rS2uX zVK#XWDm5i4W_#8;udN{1s-Hpgv(0MxYlncqy*^#@*KARz^wz30B*80p84z8Ab>$lw z?g3D1Rk9n=$Dh3Jq#1)r*!~BU1li=;%76$_RgF-rg>&J;EUQrcby0r&d9DUZQ`Ni#2Bc z$b?Aq{>EZm$wVPx)pNV>Jx;-FJ@@UXn}iL8c=`|i4^?j+7ghIufr<#I2)>Abv`V*h z!;2D zeT1Cr7X!1+tLZq4IJ@lS7IsjinM*T&=r{sMY)i`VYDem63<}6Qwh2-=L+8e6m4=KE zI+ZK~7T-m_J?4L?Wn0IbvGvt3Z0&x7gLS#3Fr@xK0k4fWo2Rd$NF|R&uy#ujmC5~R z=^}gzoM@7WJ-5NNHKDJ_s*d527?IzozrP;(K~^A1sAsNDvGBpuA!C7g3Hr?@^0S;f;z@+HMZs;YtD^uklw3Y})6Gp)<>;#&VqH0rg?vO+Iq_2yOp2H%b8N!R zCY0!Z?v0_G+aJZ<%7kDQOc(ZqYBaSba6qFblq+r80iuMMx#F@w!RYR}bS_xJK*^_B z4mkwh7(I_}YY{cI$6Dy6s}{8H0-jRwxaG~}9dBwhxp^a} z?@{_6!-K zPfm1-bmY-GW_RF$&5O2tLqmX79J42H%*3LW2-~is1(||sp5EI6Z@zt`Tb|&hxNyu1 z%ShAjj^de*jZ^5GpI#SBoooOp2?*{D>zz(c)agR296S3*W9m|HZGFu`6kn5RbMx)> zgoIHX2-j8h2R}6GY2MNc(Z(Wvzu@&XJ7S#x?Mvh_{;PG;XgoYdH_8EJkbiw&q_E~s z#QD@^SINR<-dMqC;h43Cx}~4aAD}PO)Y7iV4QXH0sgk!yUbD2ki(-qd^sV}Ryd;Pc zuIH~~QHW9)`q%iOV?){DldLZfFUTOBWx6Tz{yQ&y#xRDtSY<3(xwy6RfauHDGaPx$ z8&2QT1JN5>)K0C^xqdP6U$$${t_D;TN{VpN?!oGZM!|7mUWN-|dSu+@|}21+wam-zk+!CwpUR-Uu6u6*JUr%u}9L; zy8VX-k?)wO3t7$sAMtZ%HeF;lr+4!*|H1xp5SGk8gR6k^9{`=vR1$>5WD0t^% z)h8QQ`)ixrJ<6kvFmfvQkCOLz3bAzUg+&p8xPoM?9wdTfRVSuvPY)zV{qDU9UcQAw z3m^F!neRf{E7wV}9S+DPrIftTCz5Sfi7Jy$Mq`rNh>WJ$#kpk1jqS4ss1i?ue37O0 zAAtflAF90Z%RE-N=u)I6L2O8g`^U6HiMUrfCs;e0Ifm74CSpXR>?d>Z`K&0!$ zWkt#jprt`2`9CVCpmT)!FGn#o|NI;+zn!9std5mdjMSvP+W5-P9#~zslJ?MO<{eRl zbA1^D?AHPbvYMEfmQi(U?hoD4Zlw$p8WZ0s3@#}fXnlKvYEcoNnDb4XdwAAN@G7H9 zE)pXO8dbF1+_9%7s^wub8xP_{JV16W!TS*X-PK&p3Trk*LO0jkZ;G%}OZ_zpyh(=E zqisV$gorq%Wrz-;&9-rs6Ps?nCYF0sc!_E{u zF0(KXg48M38Z3YbGUH_0D;?*lReLiw-C%~q_smjg*CJo?A9V`Q079bK1gJ{cIkO-V zSuTL$Yq;NnE zzfq(aDo5P^r;a~MLie9R?s;$U8fi0|605B*VhOoZGW|sPdYh78gv%-NdA@q~utu@= z$9uLSdNU{>H*^^r)so)WBKv}TFXL5`G$>15^`n4uPP-|0HlyiX+G$?BgW(!fyWxqn zNS4v!9aFiQX*k(>8n-&ilX(|Oyn6}T6^__8*>F+dmR;clbmK|o{&>`Ue0vD0)z8q; z=odMU-F_YqBPY}Ef0-YFiEY-KDa~PfJW*;v257kaBD$lLJz7SB{&UkZ&hCLKW#6DG$>^{d^)`+TH-);0&pw0-&aKZ@ZGMdUOMa(@?2`cA9inx*aTUm#DlGxGPxK16gk+ajS@6A5*} zWd%5lGgf1iV*uNGN#-goEa`F7m2&yyGVGrBsdEznM#-#QPuY@w9rNYc>#HWdVDP5K zIyZ?(w}L+|)g#h<{oPB^-H^KW`kR@(K!Wh)1Hh!ACSrR=w@zKyC#?0|svPY{LcnIM zhArIO!{lye(it~-F#;A6`A5NDX|BVl#qhxF`66Y;Ee`0}kCBF-gpt7Z*00VC6~L$g zf>U-UILB!CsPx>CZZaQl?Gcs#GI`v*276y|s65)L8CLQ+(TP<3dHB0IfuE{Rr@%=d zaKgQLuJzz~s8o`6kUwpvP<@)b0X|21^d_Eusm?uyf1tb(X)L!i>of^{?{n$%QTy@x z{x26PoN}Xd{j=ZXVRblhB96e*e0D91eQd8R_<0`8y5<<{wk@t4np#;v{^lHi|HPhV z-Sku(f^)h1cAvd=zvu4hB>|+~E-Rr_wR0$gg&m2=BB{qy5i8{;pIIU?39P6kLI$et z)#cWx5j!`_?HQm+8pI9z*~G=Yd*f$n_HQbZgWsW$vq8X=Q%1xlt|fPqZ(tPLAIJZj zHWPcYc6BZb63Lrj7A1!U(vr&XU0#;Z)8@>aJJZrxoRElHNv!n}YxK9L{_n=(Cjk^m zU#C2qnFF|C9*YYFrn9d3A_Z>QJh0p*RUVfFIcJ6d&3gHG7HyGtnkt>^LPI@4 z8ifVJ5IM~3k{aY1>1G%N5b8BjyUwzcc|fu4`8_4?IXEF@5xZ^gj=u6=hphu|@6B0K z6OW@HXVl$oNg|e~-)UwE_liXemX5ik*`rpN7(+qF|<=hUvw)Q(yCUv6$;l#^p}o2c6xs;EiP;Pd+OS?!7}atZScdfb+)!;Hyk>o__qMEbNhHst&Sf5WUbSi3dY_H1NEz1FS?smZ zr8bl;%NS)>8s6Slne6`{2+jnno&Wgju9^+|O zI2;A>c8{@NoVI}GfTbYf!<`%51|AW+76kCi>^rlNz^`^2h_m%NS{bvId?0!5bKo95 zH;Oe^f1K)Loz&oEP;ML29|oVRJHKKPucg?ibL$7s=$__J1>9#o_Rp^W^g8S2CZ}kF1(CY^;Z3 z7MqW(X*?A!HWXYhSDqLr%tEgC5r%86=&mY>8pSO~a9W*kH;7*(S(SRAg2k_{8f-l& zU(_2PwNu;Z?e}hQirE`<79qe}sV=UAfhAyRjSW=KpVchsZ%UyOeo;M|9?|ZSeoCRz z?^2V-Mf08eLqs@_cQdYrT>9l)LJxxW&Q^{><;AY;kn1O69*Y|dHz&x{X+r&|)T=FE z#J5~Jes?FjHwpo(=_rDOw?n8BhLA4ll+j`e7lSIDe4BZqqc-oci|I|Te08{Xg>0e3 z;{H$hckKA0{I$(Lr+*Khj-RFz;)A-%gaqiiZFWLA*XM&XJ?{f|2t7BCq)5WeIT%78)lZkT-{2Fz_ffSI3U9f!JG)ie zEZEy;Tl1t9Eet7wt+-oFJI@zn7A>>^4cCY7}FEYQZlNQ|? zjP3_1x|jks_UWU5U9*`PD!u$+7q<`ak1+K-$AS|8rI#N+#2ZF~YjvUWL7!^8R)#U7 zo+^qM>sadvoe%#gd&glpW;uG&FuH11Y{15>XnB-aF-Zc;8y4rSY0c=^>}Y)#bpq

0uG)HnC1h95?bbJ^3JOx>#-)!>sKk7q3ZY?x91 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 F{{Z-N{o?=t literal 0 HcmV?d00001 From f3abf4e3038878c5ce86067a80d1be6d35cda7c7 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:18:14 -0400 Subject: [PATCH 017/100] Import fix update - bpy.utils, autoformat fix While rearranging and cleaning up the utils imports in the initial 'Import fix' commit I deleted `utils.` from `bpy.utils.script_paths()` in the ops.py file. This has been fixed, and I took the opportunity to reverse the autoformat issue (changed line length from 100 to 89 between commits), and restore all the code to its previous state. I made one exception: - comments that came at the end of the line that were moved to the line above All functional code has been restored to its previous format. --- scripts/addons/cam/opencamlib/oclSample.py | 9 +++----- scripts/addons/cam/opencamlib/opencamlib.py | 24 +++++++-------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index c9f6e287f..e29767326 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -59,11 +59,9 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): cutter = None if op_cutter_type == 'END': - cutter = ocl.CylCutter( - (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'BALLNOSE': - cutter = ocl.BallCutter( - (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) @@ -91,8 +89,7 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): for chunk in chunks: for coord in chunk.get_points_np(): - bdc.appendPoint(ocl.CLPoint( - coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) + bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) await progress_async("OpenCAMLib sampling") bdc.run() diff --git a/scripts/addons/cam/opencamlib/opencamlib.py b/scripts/addons/cam/opencamlib/opencamlib.py index 7eef7a115..8ca9e4150 100644 --- a/scripts/addons/cam/opencamlib/opencamlib.py +++ b/scripts/addons/cam/opencamlib/opencamlib.py @@ -85,17 +85,14 @@ def exportModelsToSTL(operation): bpy.ops.object.duplicate(linked=False) # collision_object = bpy.context.scene.objects.active # bpy.context.scene.objects.selected = collision_object - file_name = os.path.join( - tempfile.gettempdir(), "model{0}.stl".format(str(file_number))) - bpy.ops.object.transform_apply( - location=True, rotation=True, scale=True) + file_name = os.path.join(tempfile.gettempdir(), "model{0}.stl".format(str(file_number))) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) bpy.ops.transform.resize(value=(OCL_SCALE, OCL_SCALE, OCL_SCALE), constraint_axis=(False, False, False), orient_type='GLOBAL', mirror=False, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), texture_space=False, release_confirm=False) - bpy.ops.object.transform_apply( - location=True, rotation=True, scale=True) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) bpy.ops.export_mesh.stl(check_existing=True, filepath=file_name, filter_glob="*.stl", use_selection=True, ascii=False, use_mesh_modifiers=True, axis_forward='Y', axis_up='Z', global_scale=1.0) bpy.ops.object.delete() @@ -123,8 +120,7 @@ async def oclResampleChunks(operation, chunks_to_resample, use_cached_mesh): sample_index = 0 for chunk, i_start, i_length in chunks_to_resample: - z = np.array( - [p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE + z = np.array([p.z for p in samples[sample_index:sample_index+i_length]]) / OCL_SCALE pts = chunk.get_points_np() pt_z = pts[i_start:i_start+i_length, 2] pt_z = np.where(z > pt_z, z, pt_z) @@ -154,8 +150,7 @@ def oclGetMedialAxis(operation, chunks): oclWaterlineHeightsToOCL(operation) operationSettingsToOCL(operation) curvesToOCL(operation) - call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), - "addons", "cam", "opencamlib", "ocl.py")]) + call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")]) waterlineChunksFromOCL(operation, chunks) @@ -174,11 +169,9 @@ async def oclGetWaterline(operation, chunks): cutter_length = 150 if op_cutter_type == 'END': - cutter = ocl.CylCutter( - (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.CylCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'BALLNOSE': - cutter = ocl.BallCutter( - (op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) + cutter = ocl.BallCutter((op_cutter_diameter + operation.skin * 2) * 1000, cutter_length) elif op_cutter_type == 'VCARVE': cutter = ocl.ConeCutter((op_cutter_diameter + operation.skin * 2) * 1000, op_cutter_tip_angle, cutter_length) @@ -201,8 +194,7 @@ async def oclGetWaterline(operation, chunks): for l in wl_loops: inpoints = [] for p in l: - inpoints.append( - (p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE)) + inpoints.append((p.x / OCL_SCALE, p.y / OCL_SCALE, p.z / OCL_SCALE)) inpoints.append(inpoints[0]) chunk = camPathChunk(inpoints=inpoints) chunk.closed = True From 9ebcbf247191ebae02b3208b88efb28629f60fd3 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:19:25 -0400 Subject: [PATCH 018/100] Import fix update - bpy.utils, autoformat fix While rearranging and cleaning up the utils imports in the initial 'Import fix' commit I deleted `utils.` from `bpy.utils.script_paths()` in the ops.py file. This has been fixed, and I took the opportunity to reverse the autoformat issue (changed line length from 100 to 89 between commits), and restore all the code to its previous state. I made one exception: - comments that came at the end of the line that were moved to the line above All functional code has been restored to its previous format. From 94ca6d1a040c2891e04fcb216ac906debfbc62ca Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:40:22 -0400 Subject: [PATCH 019/100] Autoformat fix Caught a handful more lines that needed to be reverted to clean up the diff. --- scripts/addons/cam/chunk.py | 6 ++---- scripts/addons/cam/joinery.py | 3 +-- scripts/addons/cam/strategy.py | 3 +-- scripts/addons/cam/voronoi.py | 9 +++------ 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py index 0a234eebb..b2071df38 100644 --- a/scripts/addons/cam/chunk.py +++ b/scripts/addons/cam/chunk.py @@ -233,8 +233,7 @@ def adaptdist(self, pos, o): if self.closed: dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 point_idx = np.argmin(dist_sq) - new_points = np.concatenate( - (self.points[point_idx:], self.points[:point_idx+1])) + new_points = np.concatenate((self.points[point_idx:], self.points[:point_idx+1])) self.points = new_points else: if o.movement.type == 'MEANDER': @@ -293,8 +292,7 @@ def reverse(self): def pop(self, index): print("WARNING: Popping from chunk is slow", self, index) - self.points = np.concatenate( - (self.points[0:index], self.points[index+1:]), axis=0) + self.points = np.concatenate((self.points[0:index], self.points[index+1:]), axis=0) if len(self.startpoints) > 0: self.startpoints.pop(index) self.endpoints.pop(index) diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 425a922c6..85944c097 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -325,8 +325,7 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, if not_start: while distance <= pd: mortise_angle = angle(oldp, p) - mortise_angle_difference = abs( - mortise_angle - old_mortise_angle) + mortise_angle_difference = abs(mortise_angle - old_mortise_angle) mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( math.pi / 4)) # factor for tolerance for the finger diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 5938c9144..d4fa6ec5e 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -567,8 +567,7 @@ async def drill(o): elif ob.type == 'MESH': for v in ob.data.vertices: - chunks.append(camPathChunk( - [(v.co.x + l.x, v.co.y + l.y, v.co.z + l.z)])) + chunks.append(camPathChunk([(v.co.x + l.x, v.co.y + l.y, v.co.z + l.z)])) delob(ob) # delete temporary object with applied transforms layers = getLayers(o, o.maxz, checkminz(o)) diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index 529d0c6cd..58cc84d41 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -129,13 +129,10 @@ def getClipPolygons(self, closePoly): for edge in edges: equation = self.lines[edge[0]] # line equation if edge[1] != -1 and edge[2] != -1: # finite line - x1, y1 = self.vertices[edge[1] - ][0], self.vertices[edge[1]][1] - x2, y2 = self.vertices[edge[2] - ][0], self.vertices[edge[2]][1] + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] pt1, pt2 = (x1, y1), (x2, y2) - inExtentP1, inExtentP2 = self.inExtent( - x1, y1), self.inExtent(x2, y2) + inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) if inExtentP1 and inExtentP2: clipEdges.append((pt1, pt2)) elif inExtentP1 and not inExtentP2: From dd25498ebfa37f4718008e3687a68cb5f48e9615 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:42:09 -0400 Subject: [PATCH 020/100] Autoformat fix Caught a handful more lines that needed to be reverted to clean up the diff. From 8c905d0f684c808bc57c44f9187db0c43a10d312 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:43:14 -0400 Subject: [PATCH 021/100] Autoformat fix Caught a handful more lines that needed to be reverted to clean up the diff. From c9c92804086206421895daa0c7b217554eae64b9 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 23 Mar 2024 11:44:22 -0400 Subject: [PATCH 022/100] Autoformat fix Caught a handful more lines that needed to be reverted to clean up the diff. From 2a6961ebbfaa5d7eb81e2540b7a61114b6c022a5 Mon Sep 17 00:00:00 2001 From: Release robot Date: Tue, 26 Mar 2024 12:56:35 +0000 Subject: [PATCH 023/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 0c2bd3662..91a1f473f 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -100,7 +100,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 7), + "version":(1,0,8), "blender": (3, 6, 0), "location": "Properties > render", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index a1acfda29..8da9bfd97 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1,2 +1 @@ -__version__ = (1, 0, 7) - +__version__=(1,0,8) \ No newline at end of file From 07be079d2346b4193470c20802b929fda75d183e Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 13:18:14 -0400 Subject: [PATCH 024/100] Update __init__.py, fix 1.0.8 install error There was a space in bl_info and that caused some bugs. --- scripts/addons/cam/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 91a1f473f..1c358234c 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -101,7 +101,6 @@ "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", "version":(1,0,8), - "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", From 9d2dcbf7ea8dba79709edd60a3ba07ef83e79c22 Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 27 Mar 2024 17:18:52 +0000 Subject: [PATCH 025/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 1c358234c..15930386b 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -100,7 +100,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,8), + "version":(1,0,9), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 8da9bfd97..409a64955 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,8) \ No newline at end of file +__version__=(1,0,9) \ No newline at end of file From 46b91b72cf05cfec95d9cd861401f05444da639f Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 27 Mar 2024 17:34:48 +0000 Subject: [PATCH 026/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 15930386b..6d5d51159 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -100,7 +100,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,9), + "version":(1,0,10), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 409a64955..1c420147c 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,9) \ No newline at end of file +__version__=(1,0,10) \ No newline at end of file From c5fbf903c7b692284c8c9177b0855b58fbc8d724 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 18:48:48 -0400 Subject: [PATCH 027/100] Update __init__.py --- scripts/addons/cam/__init__.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 6d5d51159..ba6ea6e11 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -18,6 +18,21 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK **** +import subprocess +import sys +try: + import shapely +except ModuleNotFoundError: + # pip install required python stuff + subprocess.check_call([sys.executable, "-m", "ensurepip"]) + subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) + subprocess.check_call([sys.executable, "-m", "pip", "install", + "shapely", "Equation", "opencamlib"]) + # install numba if available for this platform, ignore failure + subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) + +from shapely import geometry as sgeometry # noqa + from .ui import * from .version import __version__ from . import ( @@ -77,26 +92,11 @@ import os import pickle import shutil -import subprocess -import sys import threading import time from pathlib import Path -try: - import shapely -except ImportError: - # pip install required python stuff - subprocess.check_call([sys.executable, "-m", "ensurepip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", - "shapely", "Equation", "opencamlib"]) - # install numba if available for this platform, ignore failure - subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) - -from shapely import geometry as sgeometry # noqa - bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", From d28f3044c69677b2d46ea55a65b0454592433d1a Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 18:49:10 -0400 Subject: [PATCH 028/100] Update create_release.yaml --- .github/workflows/create_release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml index 6980fd756..6d56725d1 100644 --- a/.github/workflows/create_release.yaml +++ b/.github/workflows/create_release.yaml @@ -62,7 +62,7 @@ jobs: with: type: 'zip' filename: 'blendercam.zip' - directory: './scripts/addons' + directory: './scripts/addons/cam' - name: Write version number if: ${{ inputs.version_bump }} != "overwrite previous tag" run: | From ecd08e39c118f20455b2423a0a8c4d8123e9201e Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 27 Mar 2024 22:49:42 +0000 Subject: [PATCH 029/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index ba6ea6e11..2965fa550 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -100,7 +100,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,10), + "version":(1,0,11), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 1c420147c..938eab150 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,10) \ No newline at end of file +__version__=(1,0,11) \ No newline at end of file From 35c8f656638f311c71a6889ee87c83fd3a588219 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 18:50:47 -0400 Subject: [PATCH 030/100] Update create_release.yaml --- .github/workflows/create_release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml index 6d56725d1..2ada0ab6e 100644 --- a/.github/workflows/create_release.yaml +++ b/.github/workflows/create_release.yaml @@ -62,7 +62,7 @@ jobs: with: type: 'zip' filename: 'blendercam.zip' - directory: './scripts/addons/cam' + directory: './scripts/addons' - name: Write version number if: ${{ inputs.version_bump }} != "overwrite previous tag" run: | @@ -77,7 +77,7 @@ jobs: - name: make release uses: ncipollo/release-action@v1 with: - artifacts: "scripts/addons/blendercam.zip" + artifacts: "scripts/addons/cam/blendercam.zip" tag: ${{ env.VERSION_TAG }} allowUpdates: true body: "To install BlenderCAM, download blendercam.zip and *don't* extract it. In blender, go to preferences, add-ons, and select 'install from file' and select the blendercam.zip file you downloaded" From c4ee42f676dc25851e03071fe5ebe086dad370fd Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 18:53:05 -0400 Subject: [PATCH 031/100] Update create_release.yaml --- .github/workflows/create_release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml index 2ada0ab6e..f55bd4146 100644 --- a/.github/workflows/create_release.yaml +++ b/.github/workflows/create_release.yaml @@ -62,7 +62,7 @@ jobs: with: type: 'zip' filename: 'blendercam.zip' - directory: './scripts/addons' + directory: './scripts/addons/cam' - name: Write version number if: ${{ inputs.version_bump }} != "overwrite previous tag" run: | From 684abd5f24c876231a216e88134b794ef3c44651 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Wed, 27 Mar 2024 18:56:30 -0400 Subject: [PATCH 032/100] Update create_release.yaml --- .github/workflows/create_release.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/create_release.yaml b/.github/workflows/create_release.yaml index f55bd4146..6980fd756 100644 --- a/.github/workflows/create_release.yaml +++ b/.github/workflows/create_release.yaml @@ -62,7 +62,7 @@ jobs: with: type: 'zip' filename: 'blendercam.zip' - directory: './scripts/addons/cam' + directory: './scripts/addons' - name: Write version number if: ${{ inputs.version_bump }} != "overwrite previous tag" run: | @@ -77,7 +77,7 @@ jobs: - name: make release uses: ncipollo/release-action@v1 with: - artifacts: "scripts/addons/cam/blendercam.zip" + artifacts: "scripts/addons/blendercam.zip" tag: ${{ env.VERSION_TAG }} allowUpdates: true body: "To install BlenderCAM, download blendercam.zip and *don't* extract it. In blender, go to preferences, add-ons, and select 'install from file' and select the blendercam.zip file you downloaded" From 835e26f9aa618493c5a1911a2582a7f6583d7ffc Mon Sep 17 00:00:00 2001 From: Release robot Date: Thu, 28 Mar 2024 00:25:35 +0000 Subject: [PATCH 033/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 2965fa550..d89d9a7b9 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -100,7 +100,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,11), + "version":(1,0,12), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 938eab150..9490de2df 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,11) \ No newline at end of file +__version__=(1,0,12) \ No newline at end of file From 9aae31d7ed7ed49316d8351d9dc0588a5b6d2d50 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 09:14:07 -0400 Subject: [PATCH 034/100] Update README.md Updated Files Organization diagram to reflect current state Title case for titles and headings (important words capitalized) Contributors section added. --- README.md | 105 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index e8ba473f7..22aa9834a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ -

+
![BlenderCAM](documentation/images/logo.png) -- - - +- - - -### An open source solution for artistic or industrial CAM with Blender 3D +### An Open Source Solution for Artistic or Industrial CAM with Blender 3D -[![Chat on Matrix](https://img.shields.io/matrix/blendercam:matrix.org?label=Chat%20on%20Matrix)](https://riot.im/app/#/room/#blendercam:matrix.org) +[![Chat on Matrix](https://img.shields.io/matrix/blendercam:matrix.org?label=Chat%20on%20Matrix)](https://riot.im/app/#/room/#blendercam:matrix.org) [![Chat on Freenode](https://img.shields.io/badge/chat-on%20freenode-brightgreen.svg)](http://webchat.freenode.net/?channels=%23blendercam) [![Chat on Freenode](https://img.shields.io/github/issues/vilemduha/blendercam)](https://github.com/vilemduha/blendercam) ![Last commit](https://img.shields.io/github/last-commit/vilemduha/blendercam) @@ -20,31 +20,31 @@
-[About](#About) • [How to use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-disclaimer) +[About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-disclaimer)
-![Blendercam](documentation/images/suzanne.gif) +![BlenderCAM](documentation/images/suzanne.gif)
## 👁️ About -Blender CAM is an open source solution for artistic, personal, commercial or industrial CAM - Computer aided machining - a g-code generation tool. +BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). -Blender CAM is an add-on for the free open-source [Blender 3d package](https://www.blender.org/). +It offers an open source solution for artistic, personal, commercial or industrial CAM _(Computer Aided Machining)_, a G-Code generation tool. It has been used for many milling projects, and is actively developed. If you are a developer who would like to help, don't hesitate to fork the project and start generating pull requests. -## 👨‍🎓 How to use (Wiki) +## 👨‍🎓 How to Use (Wiki) -![Linux](https://img.shields.io/badge/Plateform-Linux%20|%20Windows-brightgreen.svg) +![Linux](https://img.shields.io/badge/Platform-Linux%20|%20Windows-brightgreen.svg) -Blendercam works on Windows or Linux. Probably on MacOS also. +BlenderCAM works on Windows or Linux. Probably on MacOS also. -* [BlenderCam Installation](documentation/Blendercam%20Installation.md) -* [Getting started](documentation/Getting%20started.md) -* [Panel descriptions](documentation/Blendercam-Panel-Descriptions.md) +* [BlenderCAM Installation](documentation/Blendercam%20Installation.md) +* [Getting Started](documentation/Getting%20started.md) +* [Panel Descriptions](documentation/Blendercam-Panel-Descriptions.md) * [Tools](documentation/Blendercam-Tools.md) * [Example of using Profile and Pocket operations](documentation/Profile%20and%20Pocket%20operations.md) @@ -53,23 +53,23 @@ Blendercam works on Windows or Linux. Probably on MacOS also. | | Blender from 2.80 to 4.0.0 | -------------------------- | :----------------: | -| Several milling strategies for 2D and 3D | ✔️ | -| Cutter types: ball, ballcone, endmill flat, v-carve with various angles, user definable | ✔️ | -| work with 3d data or depth images | ✔️ | -| Layers and skin for roughing. | ✔️ | -| Inverse milling | ✔️ | -| Various options for ambient around model | ✔️ | -| protection of vertical surfaces | ✔️ | -| Stay low - option for movement | ✔️ | -| Material size setup | ✔️ | -| Simulation of 3d operations | ✔️ | -| Arc retract | ✔️ | -| Pack curves and slice model | ✔️ | -| Automatic bridges for cutout operation | ✔️ | -| Chain export and simulation | ✔️ | - -### Pending features -* Helix entry and ramp down are experimental. +| Several Milling Strategies for 2D and 3D | ✔️ | +| Cutter Types: Ball, Ballcone, Endmill Flat, V-Carve _(various angles)_, User Defined | ✔️ | +| Work with 3D Data or Depth Images | ✔️ | +| Layers and Skin for Roughing | ✔️ | +| Inverse Milling | ✔️ | +| Various Options for Ambient around Model | ✔️ | +| Protection of Vertical Surfaces | ✔️ | +| Stay Low - Option for Movement | ✔️ | +| Material Size Setup | ✔️ | +| Simulation of 3D Operations | ✔️ | +| Arc Retract | ✔️ | +| Pack Curves and Slice Model | ✔️ | +| Automatic Bridges for Cutout Operation | ✔️ | +| Chain Export and Simulation | ✔️ | + +### Pending Features +* Helix entry and ramp down are experimental. * 4 and 5 axis milling are only manual @@ -77,7 +77,7 @@ Blendercam works on Windows or Linux. Probably on MacOS also. * GRBL * Iso * LinuxCNC - EMC2 -* Fadal +* Fadal * Heidenhain * Sieg KX1 * Hafco HM-50 @@ -90,24 +90,25 @@ Blendercam works on Windows or Linux. Probably on MacOS also. * ... -## 📒 Files organisation +## 📒 Files Organisation ``` . -├── config +├── config +├── documentation +├── Examples ├── scripts -│   ├── addons -│   │   ├── cam -│   │   │   ├── nc -│   │   │   └── opencamlib -│   │   ├── GPack -│   │   └── print_3d -│   │   ├── ini -│   │   └── machine_profiles -│   └── presets -│   ├── cam_cutters -│   ├── cam_machines -│   └── cam_operations +│   └── addons +│      └── cam +│         ├── nc +│         ├── opencamlib +│    ├── presets +│    | ├── cam_cutters +│    | ├── cam_machines +│    | └── cam_operations +| ├── tests +| | └── test_data +| └── ui_panels └── static ``` @@ -119,7 +120,13 @@ BlenderCAM has been used for many milling projects, and is actively developed. If you are a developer who would like to help, fork and open pull requests -If you need help or want to discuss about BlenderCam you can join the [Chat Room #BlenderCam:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org). +If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org). + +### Contributors +
+ + + ## 🤕 DISCLAIMER @@ -133,7 +140,3 @@ codes, and the authors of this software can not, and do not, take any responsibility for such compliance. This software is released under the GPLv2. - - - - From 9d334993305db3416de5683cf79cd5d6703618f6 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 09:27:27 -0400 Subject: [PATCH 035/100] Update README.md Centered Top section --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 22aa9834a..ecd1f920a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - -
+
![BlenderCAM](documentation/images/logo.png) @@ -20,14 +19,15 @@
+ [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-disclaimer) +
![BlenderCAM](documentation/images/suzanne.gif) - -
+ ## 👁️ About BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). From b705f865bb19650cdd12672240691a9cbb219b86 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 09:47:18 -0400 Subject: [PATCH 036/100] Update README.md Updated Files Organization with syntax highlighting and comments --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ecd1f920a..bb4a53ae6 100644 --- a/README.md +++ b/README.md @@ -92,24 +92,24 @@ BlenderCAM works on Windows or Linux. Probably on MacOS also. ## 📒 Files Organisation -``` +```graphql . -├── config -├── documentation -├── Examples +├── config - # 'startup' and 'userpref' blend files +├── documentation - # Markdown guides and images +├── Examples - # Bas Relief & Intarsion operation demo files and images ├── scripts │   └── addons -│      └── cam -│         ├── nc -│         ├── opencamlib -│    ├── presets +│      └── cam - # Main Addon Folder +│         ├── nc - # Post-Processors +│         ├── opencamlib - # OpenCAMLib functions +│    ├── presets - # Quick access to pre-defined cutting tools, machines and operations │    | ├── cam_cutters │    | ├── cam_machines │    | └── cam_operations -| ├── tests -| | └── test_data -| └── ui_panels -└── static +| ├── tests - # Developer Tests +| | └── test_data - # Test output +| └── ui_panels - # User Interface +└── static - # Logo ``` From 4cd987f572156b3a6b008ca2b75c7d91d25683ae Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 09:52:52 -0400 Subject: [PATCH 037/100] Update README.md Added Warning style to Disclaimer section --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bb4a53ae6..9eb47ede1 100644 --- a/README.md +++ b/README.md @@ -129,12 +129,18 @@ If you need help or want to discuss about BlenderCAM you can join the [Chat Room ## 🤕 DISCLAIMER - +> [!WARNING] THE AUTHORS OF THIS SOFTWARE ACCEPT ABSOLUTELY NO LIABILITY FOR -ANY HARM OR LOSS RESULTING FROM ITS USE. IT IS _EXTREMELY_ UNWISE -TO RELY ON SOFTWARE ALONE FOR SAFETY. Any machinery capable of +ANY HARM OR LOSS RESULTING FROM ITS USE. +> +> IT IS _EXTREMELY_ UNWISE +TO RELY ON SOFTWARE ALONE FOR SAFETY. +> +> Any machinery capable of harming persons must have provisions for completely removing power -from all motors, etc, before persons enter any danger area. All +from all motors, etc, before persons enter any danger area. +> +> All machinery must be designed to comply with local and national safety codes, and the authors of this software can not, and do not, take any responsibility for such compliance. From eb15c1a44fbd337c50be9ece3d4278605991882d Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:08:25 -0400 Subject: [PATCH 038/100] Update README.md --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9eb47ede1..6936a868b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,10 @@ BlenderCAM is an add-on for the free open-source [Blender 3D package](https://ww It offers an open source solution for artistic, personal, commercial or industrial CAM _(Computer Aided Machining)_, a G-Code generation tool. -It has been used for many milling projects, and is actively developed. If you are a developer who would like to help, don't hesitate to fork the project and start generating pull requests. +It has been used for many milling projects since its creation in 2012, and is actively developed. + +> [!NOTE] +> If you are a developer who would like to help, check out the [Contributing](#-contributing) section. ## 👨‍🎓 How to Use (Wiki) @@ -116,11 +119,14 @@ BlenderCAM works on Windows or Linux. Probably on MacOS also. ## 🤝 Contributing -BlenderCAM has been used for many milling projects, and is actively developed. +BlenderCAM is in active development. + +Originally created by Vilem Novak, the addon is currently maintained by Alain Pelletier and a team of contributors. -If you are a developer who would like to help, fork and open pull requests +If you are a developer who would like to contribute to the project, please fork and open pull requests. -If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org). +> [!TIP] +> If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org). ### Contributors From 015444eecd0ee283b95f87e8bbf2465e388a68fb Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:10:45 -0400 Subject: [PATCH 039/100] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6936a868b..04ff8a97b 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ It offers an open source solution for artistic, personal, commercial or industri It has been used for many milling projects since its creation in 2012, and is actively developed. > [!NOTE] -> If you are a developer who would like to help, check out the [Contributing](#-contributing) section. +> _If you are a developer who would like to help, check out the [Contributing](#-contributing) section._ ## 👨‍🎓 How to Use (Wiki) @@ -126,7 +126,7 @@ Originally created by Vilem Novak, the addon is currently maintained by Alain Pe If you are a developer who would like to contribute to the project, please fork and open pull requests. > [!TIP] -> If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org). +> _If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org)._ ### Contributors From 1ce9459522662e1547eccc70e76a5912982838a3 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:18:45 -0400 Subject: [PATCH 040/100] Update README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 04ff8a97b..2018e12ae 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,23 @@ If you are a developer who would like to contribute to the project, please fork +### Acknowledgements +_Additional contributions from:_ + +- Hirutso Enni +- Kurt Jensen +- Dan Falck +- Dan Heeks +- Brad Collette +- Michael Haberler +- dhull +- jonathanwin +- Leemon Baird +- Devon (Gorialis) R +- Steven Fortune +- Bill Simons +- Carson Farmer +- domlysz ## 🤕 DISCLAIMER > [!WARNING] From eddb0a19c2a6bc1b8f18631310ae599a3be0752b Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:21:27 -0400 Subject: [PATCH 041/100] Update README.md --- README.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/README.md b/README.md index 2018e12ae..1777c4eb6 100644 --- a/README.md +++ b/README.md @@ -136,20 +136,7 @@ If you are a developer who would like to contribute to the project, please fork ### Acknowledgements _Additional contributions from:_ -- Hirutso Enni -- Kurt Jensen -- Dan Falck -- Dan Heeks -- Brad Collette -- Michael Haberler -- dhull -- jonathanwin -- Leemon Baird -- Devon (Gorialis) R -- Steven Fortune -- Bill Simons -- Carson Farmer -- domlysz +Hirutso Enni, Kurt Jensen, Dan Falck, Dan Heeks, Brad Collette, Michael Haberler, dhull, jonathanwin, Leemon Baird, Devon (Gorialis) R, Steven Fortune, Bill Simons, Carson Farmer, domlysz ## 🤕 DISCLAIMER > [!WARNING] From 781d4881421fa283a6e59270663f124109196180 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:31:36 -0400 Subject: [PATCH 042/100] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 1777c4eb6..aa51dce77 100644 --- a/README.md +++ b/README.md @@ -133,9 +133,7 @@ If you are a developer who would like to contribute to the project, please fork -### Acknowledgements -_Additional contributions from:_ - +### Additional Contributors & Acknowledgements Hirutso Enni, Kurt Jensen, Dan Falck, Dan Heeks, Brad Collette, Michael Haberler, dhull, jonathanwin, Leemon Baird, Devon (Gorialis) R, Steven Fortune, Bill Simons, Carson Farmer, domlysz ## 🤕 DISCLAIMER From 1514fab215abdf3cc27356326851e6329ff776c5 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:35:08 -0400 Subject: [PATCH 043/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aa51dce77..903857e71 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ It offers an open source solution for artistic, personal, commercial or industri It has been used for many milling projects since its creation in 2012, and is actively developed. > [!NOTE] -> _If you are a developer who would like to help, check out the [Contributing](#-contributing) section._ +> _If you are a developer who would like to help, check out the section on [Contributing](#-contributing) ._ ## 👨‍🎓 How to Use (Wiki) From 6962911edc5699bb37242a6b653185ac940c95fc Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:35:21 -0400 Subject: [PATCH 044/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 903857e71..5c062d5fc 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ It offers an open source solution for artistic, personal, commercial or industri It has been used for many milling projects since its creation in 2012, and is actively developed. > [!NOTE] -> _If you are a developer who would like to help, check out the section on [Contributing](#-contributing) ._ +> _If you are a developer who would like to help, check out the section on [Contributing](#-contributing)._ ## 👨‍🎓 How to Use (Wiki) From d40c5cd962481eccf2a4ec4d5939b2b00e30f9b4 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 10:51:10 -0400 Subject: [PATCH 045/100] Update README.md --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 5c062d5fc..7e453f145 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ ## 👁️ About BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). -It offers an open source solution for artistic, personal, commercial or industrial CAM _(Computer Aided Machining)_, a G-Code generation tool. +It offers an open source solution for [CAM _(Computer Aided Machining)_](https://en.wikipedia.org/wiki/Computer-aided_manufacturing) toolpath generation, simulation and [G-code](https://en.wikipedia.org/wiki/G-code) export. -It has been used for many milling projects since its creation in 2012, and is actively developed. +It has been used for many milling projects _(artistic, personal, commercial and industrial)_ since its creation in 2012, and is actively developed. > [!NOTE] > _If you are a developer who would like to help, check out the section on [Contributing](#-contributing)._ @@ -43,7 +43,7 @@ It has been used for many milling projects since its creation in 2012, and is ac ![Linux](https://img.shields.io/badge/Platform-Linux%20|%20Windows-brightgreen.svg) -BlenderCAM works on Windows or Linux. Probably on MacOS also. +BlenderCAM works on Windows or Linux. * [BlenderCAM Installation](documentation/Blendercam%20Installation.md) * [Getting Started](documentation/Getting%20started.md) @@ -51,6 +51,8 @@ BlenderCAM works on Windows or Linux. Probably on MacOS also. * [Tools](documentation/Blendercam-Tools.md) * [Example of using Profile and Pocket operations](documentation/Profile%20and%20Pocket%20operations.md) +> [!NOTE] +> BlenderCAM _should_ work on MacOS, but it has not been tested. ## 👌 Features @@ -119,9 +121,9 @@ BlenderCAM works on Windows or Linux. Probably on MacOS also. ## 🤝 Contributing -BlenderCAM is in active development. +#### BlenderCAM is in active development. -Originally created by Vilem Novak, the addon is currently maintained by Alain Pelletier and a team of contributors. +Originally created by [Vilem Novak](https://github.com/vilemduha), the addon is currently maintained by [Alain Pelletier](https://github.com/pppalain) and a team of contributors. If you are a developer who would like to contribute to the project, please fork and open pull requests. From e6cc2b798d9d3971c4e4a74e995d7d9c7e2b06a9 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 13:49:31 -0400 Subject: [PATCH 046/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e453f145..f6d73cc37 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ## 👁️ About -BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). +BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/) . It offers an open source solution for [CAM _(Computer Aided Machining)_](https://en.wikipedia.org/wiki/Computer-aided_manufacturing) toolpath generation, simulation and [G-code](https://en.wikipedia.org/wiki/G-code) export. From c3dce6e3782e5a50ddf1509c8e847cafa39ea7c3 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 28 Mar 2024 13:52:47 -0400 Subject: [PATCH 047/100] Update README.md [skip actions] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6d73cc37..7e453f145 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ ## 👁️ About -BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/) . +BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). It offers an open source solution for [CAM _(Computer Aided Machining)_](https://en.wikipedia.org/wiki/Computer-aided_manufacturing) toolpath generation, simulation and [G-code](https://en.wikipedia.org/wiki/G-code) export. From 7601cedbf9a519b269b9c61440a2e9384c1e1e7a Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 29 Mar 2024 12:38:27 -0400 Subject: [PATCH 048/100] numba removed, machine and operation bugfixes Commented out `numba` import from the `__init__` for now, until the issues with crashing can be resolved. Moved `_IS_LOADING_DEFAULTS` out of the `updateMachine` function, declared the default value = False, and grabbed it via `utils.IS_LOADING_DEFAULTS`. (Note: It was already declared a `global` variable within the function, and I thought that would have been enough to avoid this error, but it works now) Lastly, altered the cleanup in the `_calc_path` function in `ops.py` - previously it was checking for and deleting objects without issues. In Blender 4.1 deleting an object would still leave behind mesh data that retained the object name, causing the cleanup function to not recognize and update old operations. Simply deleting the mesh data solves the issue, as it will also delete the object with it, even though the reverse is no longer true. --- scripts/addons/cam/__init__.py | 5 +++-- scripts/addons/cam/ops.py | 12 ++++-------- scripts/addons/cam/utils.py | 7 ++++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index d89d9a7b9..e485bea31 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -28,8 +28,9 @@ subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) subprocess.check_call([sys.executable, "-m", "pip", "install", "shapely", "Equation", "opencamlib"]) + # Numba Install temporarily disabled after crash report # install numba if available for this platform, ignore failure - subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) + # subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) from shapely import geometry as sgeometry # noqa @@ -100,7 +101,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,12), + "version": (1, 0, 12), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 8455eeb97..39c3009cc 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -44,7 +44,7 @@ ) from .utils import ( was_hidden_dict, - reload_pathss, + reload_paths, isValid, isChainValid, silhoueteOffset, @@ -193,13 +193,9 @@ async def _calc_path(operator, context): ob = bpy.data.objects[o.object_name] ob.select_set(True) bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)''' - if bpy.context.mode != 'OBJECT': - bpy.ops.object.mode_set(mode='OBJECT') # force object mode - bpy.ops.object.select_all(action='DESELECT') - path = bpy.data.objects.get('cam_path_{}'.format(o.name)) - if path: - path.select_set(state=True) - bpy.ops.object.delete() + mesh = bpy.data.meshes.get(f'cam_path_{o.name}') + if mesh: + bpy.data.meshes.remove(mesh) if not o.valid: operator.report({'ERROR_INVALID_INPUT'}, diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 02ff435a1..66e328ea7 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -1710,7 +1710,7 @@ def rotTo2axes(e, axescombination): return (angle1, angle2) -def reload_pathss(o): +def reload_paths(o): oname = "cam_path_" + o.name s = bpy.context.scene # for o in s.objects: @@ -1769,11 +1769,12 @@ def reload_pathss(o): was_hidden_dict = {} +_IS_LOADING_DEFAULTS = False + def updateMachine(self, context): - global _IS_LOADING_DEFAULTS print('update machine ') - if not _IS_LOADING_DEFAULTS: + if not utils._IS_LOADING_DEFAULTS: addMachineAreaObject() From dd3fae8ecaff9184a5d0e9777e17b818f3f0a816 Mon Sep 17 00:00:00 2001 From: Release robot Date: Sat, 30 Mar 2024 14:30:03 +0000 Subject: [PATCH 049/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index e485bea31..cdfe931c8 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -101,7 +101,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 12), + "version":(1,0,13), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 9490de2df..8295a9318 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,12) \ No newline at end of file +__version__=(1,0,13) \ No newline at end of file From 5afb9d197e15794dde21be55e19b7f0dcc12c480 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 30 Mar 2024 18:22:35 -0400 Subject: [PATCH 050/100] Preset and Preview Fix Cutter and Machine Presets worked without issue. Operation presets would load, but could not be saved - `AttributeError` This was caused by 3 errors in `AddPresetCamOperation` in `init`: - `o.movement.movement.spindle_rotation` changed to `o.movement.spindle_rotation` - `o.optimize` and `o.optimize_threshold` changed to `o.optimisation.optimize` and `o.optimisation.optimize_threshold` Discovered this while trying to add a small usability patch that would add an operation if there wasn't already on in the scene, so that loading an operation preset wouldn't cause an error message. Patch was done by adding a `setup_operation_preset` function in `utils` that would `try: except` to try to adjust an existing operation, but add one if one isn't present. I also patched the existing operation presets, though they aren't copied to disk by default, so I added a `BoolProperty` to the `AddonPreferences` and copy logic to the `check_operations_on_load` handler so that they will only be copied once. The copy operation does not affect any additional presets the user may have, only the default set that come with the addon. Also fixed the 'Material Preview' viewport mode. Previously the object would disappear when switching to that mode. Render Engines have the option to 'borrow' Material Preview mode from EEVEE via a boolean `bl_use_eevee_viewport = True` I set that, and added the panels to the Material tab when in Cam. --- scripts/addons/cam/__init__.py | 178 +++++++++++++++--- .../cam_operations/Fin_Ball_3,0_Block_All.py | 4 +- .../Fin_Ball_3,0_Block_Around.py | 4 +- .../Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py | 4 +- ...in_Ball_3,0_Circles_Around_EXPERIMENTAL.py | 4 +- .../cam_operations/Fin_Ball_3,0_Cross_All.py | 4 +- .../Fin_Ball_3,0_Cross_Around.py | 4 +- .../cam_operations/Fin_Ball_3,0_Cutout.py | 4 +- .../Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py | 4 +- .../Fin_Ball_3,0_Parallel_All.py | 4 +- .../Fin_Ball_3,0_Parallel_Around.py | 4 +- .../Fin_Ball_3,0_Pencil_EXPERIMENTAL.py | 4 +- .../Fin_Ball_3,0_Pocket_EXPERIMENTAL.py | 4 +- .../cam_operations/Fin_Ball_3,0_Spiral_All.py | 4 +- .../Fin_Ball_3,0_Spiral_Around.py | 4 +- .../cam_operations/Finishing_3mm_ballnose.py | 4 +- .../cam_operations/Rou_Ball_3,0_Block_All.py | 4 +- .../Rou_Ball_3,0_Block_Around.py | 4 +- .../Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py | 4 +- ...ou_Ball_3,0_Circles_Around_EXPERIMENTAL.py | 4 +- .../cam_operations/Rou_Ball_3,0_Cross_All.py | 4 +- .../Rou_Ball_3,0_Cross_Around.py | 4 +- .../cam_operations/Rou_Ball_3,0_Cutout.py | 4 +- .../Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py | 4 +- .../Rou_Ball_3,0_Parallel_All.py | 4 +- .../Rou_Ball_3,0_Parallel_Around.py | 4 +- .../Rou_Ball_3,0_Pencil_EXPERIMENTAL.py | 4 +- .../Rou_Ball_3,0_Pocket_EXPERIMENTAL.py | 4 +- .../cam_operations/Rou_Ball_3,0_Spiral_All.py | 4 +- .../Rou_Ball_3,0_Spiral_Around.py | 4 +- .../Fin_Ball_3,0_Block_All.cpython-311.pyc | Bin 0 -> 3041 bytes scripts/addons/cam/utils.py | 18 +- 32 files changed, 256 insertions(+), 56 deletions(-) create mode 100644 scripts/addons/cam/presets/cam_operations/__pycache__/Fin_Ball_3,0_Block_All.cpython-311.pyc diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index cdfe931c8..c4220a5ae 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -65,6 +65,7 @@ getStrategyList, updateBridges, ) +from .ui_panels.movement import CAM_MOVEMENT_Properties import bl_operators import blf import bpy @@ -101,7 +102,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,13), + "version": (1, 0, 13), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", @@ -116,6 +117,11 @@ class CamAddonPreferences(AddonPreferences): # when defining this in a submodule of a python package. bl_idname = __package__ + op_preset_update: BoolProperty( + name="Have the Operation Presets been Updated", + default=False, + ) + experimental: BoolProperty( name="Show experimental features", default=False, @@ -1797,27 +1803,109 @@ class AddPresetCamOperation(bl_operators.presets.AddPresetBase, Operator): preset_menu = "CAM_OPERATION_MT_presets" preset_defines = [ - "o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]"] - - preset_values = ['o.use_layers', 'o.info.duration', 'o.info.chipload', 'o.material.estimate_from_model', 'o.movement.stay_low', 'o.carve_depth', - 'o.dist_along_paths', 'o.source_image_crop_end_x', 'o.source_image_crop_end_y', 'o.material.size', - 'o.material.radius_around_model', 'o.use_limit_curve', 'o.cut_type', 'o.optimisation.use_exact', - 'o.optimisation.exact_subdivide_edges', 'o.minz_from', 'o.movement.free_height', - 'o.source_image_crop_start_x', 'o.movement.insideout', 'o.movement.movement.spindle_rotation', 'o.skin', - 'o.source_image_crop_start_y', 'o.movement.type', 'o.source_image_crop', 'o.limit_curve', - 'o.spindle_rpm', 'o.ambient_behaviour', 'o.cutter_type', 'o.source_image_scale_z', - 'o.cutter_diameter', 'o.source_image_size_x', 'o.curve_object', 'o.curve_object1', - 'o.cutter_flutes', 'o.ambient_radius', 'o.optimisation.simulation_detail', 'o.update_offsetimage_tag', - 'o.dist_between_paths', 'o.max', 'o.min', 'o.optimisation.pixsize', 'o.slice_detail', 'o.movement.parallel_step_back', - 'o.drill_type', 'o.source_image_name', 'o.dont_merge', 'o.update_silhouete_tag', - 'o.material.origin', 'o.inverse', 'o.waterline_fill', 'o.source_image_offset', 'o.optimisation.circle_detail', - 'o.strategy', 'o.update_zbufferimage_tag', 'o.stepdown', 'o.feedrate', 'o.cutter_tip_angle', - 'o.cutter_id', 'o.path_object_name', 'o.pencil_threshold', 'o.geometry_source', - 'o.optimize_threshold', 'o.movement.protect_vertical', 'o.plunge_feedrate', 'o.minz', 'o.info.warnings', - 'o.object_name', 'o.optimize', 'o.parallel_angle', 'o.cutter_length', - 'o.output_header', 'o.gcode_header', 'o.output_trailer', 'o.gcode_trailer', 'o.use_modifiers', - 'o.movement.useG64', - 'o.movement.G64', 'o.enable_A', 'o.enable_B', 'o.A_along_x', 'o.rotation_A', 'o.rotation_B', 'o.straight'] + 'import cam', + 'o = cam.utils.setup_operation_preset()', + ] + + preset_values = [ + 'o.info.duration', + 'o.info.chipload', + 'o.info.warnings', + + 'o.material.estimate_from_model', + 'o.material.size', + 'o.material.radius_around_model', + 'o.material.origin', + + 'o.movement.stay_low', + 'o.movement.free_height', + 'o.movement.insideout', + 'o.movement.spindle_rotation', + 'o.movement.type', + 'o.movement.useG64', + 'o.movement.G64', + 'o.movement.parallel_step_back', + 'o.movement.protect_vertical', + + 'o.source_image_name', + 'o.source_image_offset', + 'o.source_image_size_x', + 'o.source_image_crop', + 'o.source_image_crop_start_x', + 'o.source_image_crop_start_y', + 'o.source_image_crop_end_x', + 'o.source_image_crop_end_y', + 'o.source_image_scale_z', + + 'o.optimisation.optimize', + 'o.optimisation.optimize_threshold', + 'o.optimisation.use_exact', + 'o.optimisation.exact_subdivide_edges', + 'o.optimisation.simulation_detail', + 'o.optimisation.pixsize', + 'o.optimisation.circle_detail', + + 'o.cut_type', + 'o.cutter_tip_angle', + 'o.cutter_id', + 'o.cutter_diameter', + 'o.cutter_type', + 'o.cutter_flutes', + 'o.cutter_length', + + 'o.ambient_behaviour', + 'o.ambient_radius', + + 'o.curve_object', + 'o.curve_object1', + 'o.limit_curve', + 'o.use_limit_curve', + + 'o.feedrate', + 'o.plunge_feedrate', + + 'o.dist_along_paths', + 'o.dist_between_paths', + + 'o.max', + 'o.min', + 'o.minz_from', + 'o.minz', + + 'o.skin', + 'o.spindle_rpm', + 'o.use_layers', + 'o.carve_depth', + + 'o.update_offsetimage_tag', + 'o.slice_detail', + 'o.drill_type', + 'o.dont_merge', + 'o.update_silhouete_tag', + 'o.inverse', + 'o.waterline_fill', + 'o.strategy', + 'o.update_zbufferimage_tag', + 'o.stepdown', + 'o.path_object_name', + 'o.pencil_threshold', + 'o.geometry_source', + 'o.object_name', + 'o.parallel_angle', + + 'o.output_header', + 'o.gcode_header', + 'o.output_trailer', + 'o.gcode_trailer', + 'o.use_modifiers', + + 'o.enable_A', + 'o.enable_B', + 'o.A_along_x', + 'o.rotation_A', + 'o.rotation_B', + 'o.straight' + ] preset_subdir = "cam_operations" @@ -1861,6 +1949,7 @@ class AddPresetCamMachine(bl_operators.presets.AddPresetBase, Operator): class BLENDERCAM_ENGINE(bpy.types.RenderEngine): bl_idname = 'BLENDERCAM_RENDER' bl_label = "Cam" + bl_use_eevee_viewport = True _IS_LOADING_DEFAULTS = False @@ -1902,6 +1991,13 @@ def copy_if_not_exists(src, dst): bpy.context.preferences.addons['cam'].preferences.just_updated = False bpy.ops.wm.save_userpref() + if not bpy.context.preferences.addons['cam'].preferences.op_preset_update: + # Update the Operation presets + op_presets_source = os.path.join(preset_source_path, 'cam_operations') + op_presets_target = os.path.join(preset_target_path, 'cam_operations') + shutil.copytree(preset_source_path, preset_target_path, dirs_exist_ok=True) + bpy.context.preferences.addons['cam'].preferences.op_preset_update = True + def get_panels(): # convenience function for bot register and unregister functions # types = bpy.types @@ -2134,7 +2230,7 @@ def compatible_panels(): ui.CAM_OPTIMISATION_Properties, ui.CAM_AREA_Panel, ui.CAM_MOVEMENT_Panel, - ui.CAM_MOVEMENT_Properties, + CAM_MOVEMENT_Properties, ui.CAM_FEEDRATE_Panel, ui.CAM_CUTTER_Panel, ui.CAM_GCODE_Panel, @@ -2214,7 +2310,7 @@ def compatible_panels(): ] -def register(): +def register() -> None: for p in classes: bpy.utils.register_class(p) @@ -2264,8 +2360,25 @@ def register(): basrelief.register() + from bl_ui.properties_material import ( + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_surface, + EEVEE_MATERIAL_PT_settings, + ) + + panels = [ + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_surface, + EEVEE_MATERIAL_PT_settings, + ] + + for panel in panels: + bpy.utils.unregister_class(panel) + panel.COMPAT_ENGINES.add('BLENDERCAM_RENDER') + bpy.utils.register_class(panel) -def unregister(): + +def unregister() -> None: for p in classes: bpy.utils.unregister_class(p) s = bpy.types.Scene @@ -2281,3 +2394,18 @@ def unregister(): del s.cam_pack del s.cam_slice basrelief.unregister() + + from bl_ui.properties_material import ( + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_surface, + EEVEE_MATERIAL_PT_settings, + ) + + panels = [ + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_surface, + EEVEE_MATERIAL_PT_settings, + ] + + for panel in panels: + panel.COMPAT_ENGINES.remove('BLENDERCAM_RENDER') diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py index aaeb535ee..38aa6b1f7 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py index 74b9f7c93..cf6d990d5 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py index d7808f0ab..ba371116a 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py index 1bfadb23b..143899f29 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py index 452eb2939..13c92baf9 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py index d6ce21721..6eb0b19ce 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py index 39141c634..b924eee1c 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py index 57a715955..8d12df58d 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py index 956ef5148..e2120df05 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py index b3458a84e..a9f322937 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py index d51592bfc..a838e3e5d 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py index 80d9c813d..fcc4423fd 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py index c1f8c03cb..d8a0afe3f 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py index 080f8385e..e80c3acd4 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py b/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py index 004016cd9..b148d9960 100644 --- a/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py +++ b/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py index 6c84a8222..3307dab7d 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py index b0e8d225d..d72034238 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py index 69b3cea18..6fb00c9a4 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py index ae05f07a6..cde6b0a08 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py index 4ba06ed94..388b5252b 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py index 3733e7f6a..b96afee52 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py index d4fd2d3cf..27e28df1b 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py index 9579c802c..1dc946596 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py index 85d04d336..8cf7fb169 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py index 858b33c78..40b316006 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py index 30f14c5e2..5bf667eb1 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py index eff95d252..696f213f1 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py index e1309d625..2c437b48e 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py index 4e0b0aaf4..a19068d84 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py @@ -1,5 +1,7 @@ import bpy -o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] +import cam + +o = cam.utils.setup_operation_preset() o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 diff --git a/scripts/addons/cam/presets/cam_operations/__pycache__/Fin_Ball_3,0_Block_All.cpython-311.pyc b/scripts/addons/cam/presets/cam_operations/__pycache__/Fin_Ball_3,0_Block_All.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63f5aa890d736806738b6eb5f1922e81b3731209 GIT binary patch literal 3041 zcmZXWOKclO7=XvI9j_g~?byzvk2FnNw@K*(T3SjSCrwKsO@OwDMv+!)@7UgSy}R07 z=Y@oP$PrE)P$dqz0IHCQ`3h@13+*?Qstf6)P zEwtv7`*6SLtdBX!exL!*RSp0}IR$}Yoce*{oI*eeP6AMpQy3`4DFT${Gys(06a~t1 ziUAFBiUZ|1C4lmrSe*qmtN(>`tlm5{Xl0pjRBqIbO7jePAvO5PV7XU z=QIxV1}6k`ffL)YH#xB=7df%HQV&h{P?;0+o8jcUj*15#GhCql^89=qM$><8Zj~-S zW=&xD@LJ{F(&K3uQA9(FH*Z|OdTFly?BkUm?9Un{W&zK+>F!`*nhG-zXkqdCTzzVG z^vR<$M@x0-)Xft|?~Xgx+o!W1de*}9X8N+Ci8GR_il-+}h%>5QyAE!RxWUXtZxK5sll_P| zInn$d(=<3Uzc~9&ahCck#s&>lbHVs-;tYI3jN=;EWuqoM!u30q8SgP8}Dt@k} z$(J-fokp%|GG3oEP2Hq^-LPn?CzdG5GEGU%ih?y;tl);Us^~3~#yyoO$x6$jVX0;7 zqJ}M-4pekg#^#zL+YLHUl}so?#s)yFs+d)kku51IjaFN1POq%ss!fGz%NFeo1JeYI zY;20MA~i7#z|U2vSgS*rWEXA45GAdyV#wkeIPOVVv20ONb*(NMlHIUq+R;?7y@s*Y z)kS1zq1eP`9aBMWxt*gz4PzO`psOj64ho>znu)R4)K_tn^`L>3dc&rPI@X~&b3?SC zA5~04infYP3yM*drecfE;%KG`!AwO`#hR%%A&QJun&3*^RO*UGN4dgnrzn|vOOw0i zF+OW4o0$5W(mK4rEdZ}g8f9-cyk4O(UZ~TIfU|^tugAJ!Lvau!?TtjcVPdPHt1^un zk_ndvtKF`K3}xNPm@w3qR>vZ5PSP-S8#YaZ728TxQfb7*aE?r9yGjGrZ742ksY;da zb;|9Ms5B)A9PcQCbihi<`99l+`4z(~xB-nhd)yShlnws`?rmVMEr} zG&W+}Sckh}!=Y5PI?c5V8C0U1$t}c|>NMNcTZ-DyTX5h+rp0eejo&iIAxj<2!pRuv7JrKsv%^b~F}dbU1$e)*;a9?pCD5>%+_l58zk;EX}r z;L1&Dt!71!xe-}y$nXn2J96ERnHx@mS6t86U(Z4+}L;~ zT!16jQt?Ie11N-jU|Ey!akb<3`FsRL+Rooj1cjrY40ME{c95iUozz4-PBMi~X6gY- z?(_wGMIz)Lwci8dzXryC2~2bX6AuCtJznkd zP7r){cXxLLlpk&TIw(UjhnY;0c&6>!8X(Cuh!{yuI3k?|ksv8H7bmeChzQ9JF^?2U zGfa>{2h$`u3>YJWBWyNJ@_U)gk^~D8C9yq@Z!GDGF-Hu6vV(FY>(r7W(G;k|UJV6D zJ4mwQZoaWYOztJ=_rNMagttHhJrM@ut$vak1Q91`C<08vENMR(NPrMXj75)-{G?l0 z2Fhm(kFr|ABwKK^c*{w!HAhJ}3IgInZit(6j54I)G;NR!IVI*u;TUrsBvIxR_Ab6iscaN3x{vUHbc>f3E C5HJn^ literal 0 HcmV?d00001 diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 66e328ea7..d2f700da0 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -22,6 +22,7 @@ # here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. +from typing import Any import bpy import time import mathutils @@ -61,6 +62,7 @@ # Import OpencamLib # Return available OpenCamLib version on success, None otherwise + def opencamlib_version(): try: import ocl @@ -68,8 +70,8 @@ def opencamlib_version(): try: import opencamlib as ocl except ImportError as e: - return - return(ocl.version()) + return 'Error' + return ocl.version() def positionObject(operation): @@ -1764,6 +1766,18 @@ def reload_paths(o): bpy.data.meshes.remove(old_pathmesh) +def setup_operation_preset(): + scene = bpy.context.scene + cam_operations = scene.cam_operations + active_operation = scene.cam_active_operation + try: + o = cam_operations[active_operation] + except IndexError: + bpy.ops.scene.cam_operation_add() + o = cam_operations[active_operation] + return o + + # Moved from init - the following code was moved here to permit the import fix USE_PROFILER = False From 00eb5b4b4bd6686c51dc91043e7482ee2c303140 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 30 Mar 2024 20:14:12 -0400 Subject: [PATCH 051/100] Update utils.py Removed typing import --- scripts/addons/cam/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index d2f700da0..9cf9a7d5c 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -22,7 +22,6 @@ # here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. -from typing import Any import bpy import time import mathutils From a6d8c34b8034fb9fb19d431a73d7dc0432450b6d Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 30 Mar 2024 20:15:37 -0400 Subject: [PATCH 052/100] Update utils.py --- scripts/addons/cam/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 9cf9a7d5c..e2ed68344 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -61,7 +61,6 @@ # Import OpencamLib # Return available OpenCamLib version on success, None otherwise - def opencamlib_version(): try: import ocl @@ -69,7 +68,7 @@ def opencamlib_version(): try: import opencamlib as ocl except ImportError as e: - return 'Error' + return return ocl.version() From b3af65ef00d94a6332fc7e05bce81a2a5b1be98c Mon Sep 17 00:00:00 2001 From: Release robot Date: Sun, 31 Mar 2024 14:54:08 +0000 Subject: [PATCH 053/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index c4220a5ae..a201c69ad 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -102,7 +102,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 13), + "version":(1,0,14), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 8295a9318..4edf85a92 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,13) \ No newline at end of file +__version__=(1,0,14) \ No newline at end of file From d64589818671bf660ab92d853bf14765d9dd93aa Mon Sep 17 00:00:00 2001 From: Alain Pelletier <33393909+pppalain@users.noreply.github.com> Date: Sun, 31 Mar 2024 11:56:42 -0300 Subject: [PATCH 054/100] Update build_and_test.yaml --- .github/workflows/build_and_test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 95bc65688..4ec6afdc7 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -8,7 +8,7 @@ jobs: fail-fast: false matrix: os: ['ubuntu-latest','windows-latest'] - blender_version: ['3.6.7','4.0.2'] + blender_version: ['3.6.7','4.1'] include: - os: 'macos-latest' blender_version: 'ignored' From 3a2c237be4c7fd2d6d3a2338e13e993f899c5f45 Mon Sep 17 00:00:00 2001 From: Alain Pelletier <33393909+pppalain@users.noreply.github.com> Date: Sun, 31 Mar 2024 11:57:31 -0300 Subject: [PATCH 055/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7e453f145..a0870ef5f 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ BlenderCAM works on Windows or Linux. ## 👌 Features -| | Blender from 2.80 to 4.0.0 +| | Blender from 2.80 to 4.1 | -------------------------- | :----------------: | | Several Milling Strategies for 2D and 3D | ✔️ | | Cutter Types: Ball, Ballcone, Endmill Flat, V-Carve _(various angles)_, User Defined | ✔️ | From 7325fb116404515345164c3e98959955a01dd2aa Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 11:07:14 -0400 Subject: [PATCH 056/100] Import and init cleanup All imports made explicit All redundant and unused imports removed All circular import errors resolved Imports alphabetized and sorted by source, in the following order: - Python Standard Library - pip Packages - Blender Libraries - cam Modules (from .) init split into multiple files: New: - cam_operation - chain - engine - machine_settings - preset_managers Existing: - pack - slice - ui - utils Rewrote get_panels to remove legacy panels and updated to follow Blender's guide: https://docs.blender.org/api/current/bpy.types.RenderEngine.html --- scripts/addons/cam/__init__.py | 2600 ++--------------- scripts/addons/cam/async_op.py | 4 +- scripts/addons/cam/autoupdate.py | 15 +- scripts/addons/cam/basrelief.py | 13 +- scripts/addons/cam/bridges.py | 35 +- scripts/addons/cam/cam_chunk.py | 1271 ++++++++ scripts/addons/cam/cam_operation.py | 1192 ++++++++ scripts/addons/cam/chain.py | 52 + scripts/addons/cam/collision.py | 57 +- scripts/addons/cam/constants.py | 6 + scripts/addons/cam/curvecamcreate.py | 80 +- scripts/addons/cam/curvecamequation.py | 23 +- scripts/addons/cam/curvecamtools.py | 84 +- scripts/addons/cam/engine.py | 74 + scripts/addons/cam/gcodeimportparser.py | 6 +- scripts/addons/cam/gcodepath.py | 125 +- scripts/addons/cam/image_utils.py | 209 +- scripts/addons/cam/involute_gear.py | 41 +- scripts/addons/cam/joinery.py | 54 +- scripts/addons/cam/machine_settings.py | 228 ++ scripts/addons/cam/ops.py | 129 +- scripts/addons/cam/pack.py | 103 +- scripts/addons/cam/parametric.py | 18 +- scripts/addons/cam/pattern.py | 47 +- scripts/addons/cam/polygon_utils_cam.py | 13 +- scripts/addons/cam/preferences.py | 109 + scripts/addons/cam/preset_managers.py | 210 ++ scripts/addons/cam/puzzle_joinery.py | 101 +- scripts/addons/cam/simple.py | 30 +- scripts/addons/cam/simulation.py | 124 +- scripts/addons/cam/slice.py | 40 +- scripts/addons/cam/strategy.py | 149 +- scripts/addons/cam/testing.py | 8 +- scripts/addons/cam/ui.py | 71 +- scripts/addons/cam/ui_panels/area.py | 9 +- scripts/addons/cam/ui_panels/buttons_panel.py | 4 +- scripts/addons/cam/ui_panels/chains.py | 7 +- scripts/addons/cam/ui_panels/cutter.py | 5 +- scripts/addons/cam/ui_panels/feedrate.py | 4 +- scripts/addons/cam/ui_panels/gcode.py | 5 +- scripts/addons/cam/ui_panels/info.py | 12 +- scripts/addons/cam/ui_panels/interface.py | 9 +- scripts/addons/cam/ui_panels/machine.py | 5 +- scripts/addons/cam/ui_panels/material.py | 13 +- scripts/addons/cam/ui_panels/movement.py | 25 +- scripts/addons/cam/ui_panels/op_properties.py | 5 +- scripts/addons/cam/ui_panels/operations.py | 5 +- scripts/addons/cam/ui_panels/optimisation.py | 14 +- scripts/addons/cam/ui_panels/pack.py | 5 +- scripts/addons/cam/ui_panels/slice.py | 5 +- scripts/addons/cam/utils.py | 175 +- scripts/addons/cam/voronoi.py | 1 - 52 files changed, 4527 insertions(+), 3102 deletions(-) create mode 100644 scripts/addons/cam/cam_chunk.py create mode 100644 scripts/addons/cam/cam_operation.py create mode 100644 scripts/addons/cam/chain.py create mode 100644 scripts/addons/cam/engine.py create mode 100644 scripts/addons/cam/machine_settings.py create mode 100644 scripts/addons/cam/preferences.py create mode 100644 scripts/addons/cam/preset_managers.py diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index a201c69ad..f5c3cfe88 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -18,2394 +18,374 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK **** + +# Python Standard Library import subprocess import sys + +# pip Packages try: import shapely except ModuleNotFoundError: # pip install required python stuff subprocess.check_call([sys.executable, "-m", "ensurepip"]) subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", " pip"]) - subprocess.check_call([sys.executable, "-m", "pip", "install", - "shapely", "Equation", "opencamlib"]) + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "shapely", "Equation", "opencamlib"] + ) + # Numba Install temporarily disabled after crash report # install numba if available for this platform, ignore failure # subprocess.run([sys.executable, "-m", "pip", "install", "numba"]) -from shapely import geometry as sgeometry # noqa - -from .ui import * -from .version import __version__ -from . import ( - ui, - ops, - constants, - curvecamtools, - curvecamequation, - curvecamcreate, - utils, - simple, - polygon_utils_cam, - autoupdate, - basrelief, -) # , post_processors -from .utils import ( - updateMachine, - updateRest, - updateOperation, - updateOperationValid, - operationValid, - updateZbufferImage, - updateOffsetImage, - updateStrategy, - updateCutout, - updateChipload, - updateRotation, - update_operation, - getStrategyList, - updateBridges, -) -from .ui_panels.movement import CAM_MOVEMENT_Properties -import bl_operators -import blf +# Blender Library import bpy -from bpy.app.handlers import persistent from bpy.props import ( - BoolProperty, - EnumProperty, - FloatProperty, - FloatVectorProperty, + CollectionProperty, IntProperty, PointerProperty, StringProperty, - CollectionProperty, -) -from bpy.types import ( - Menu, - Operator, - UIList, - AddonPreferences, ) from bpy_extras.object_utils import object_data_add -import bpy.ops -import math -from mathutils import * -import numpy -import os -import pickle -import shutil -import threading -import time -from pathlib import Path +# Relative Imports - from 'cam' module +from . import basrelief +from .autoupdate import ( + UpdateChecker, + Updater, + UpdateSourceOperator, +) +from .cam_operation import camOperation +from .chain import ( + camChain, + opReference, +) +from .curvecamcreate import ( + CamCurveDrawer, + CamCurveFlatCone, + CamCurveGear, + CamCurveHatch, + CamCurveInterlock, + CamCurveMortise, + CamCurvePlate, + CamCurvePuzzle, +) +from .curvecamequation import ( + CamCustomCurve, + CamHypotrochoidCurve, + CamLissajousCurve, + CamSineCurve, +) +from .curvecamtools import ( + CamCurveBoolean, + CamCurveConvexHull, + CamCurveIntarsion, + CamCurveOvercuts, + CamCurveOvercutsB, + CamCurveRemoveDoubles, + CamMeshGetPockets, + CamOffsetSilhouete, + CamObjectSilhouete, +) +from .engine import ( + BLENDERCAM_ENGINE, + get_panels, +) +from .machine_settings import machineSettings +from .ops import ( + CalculatePath, + # bridges related + CamBridgesAdd, + CamChainAdd, + CamChainRemove, + CamChainOperationAdd, + CamChainOperationRemove, + CamChainOperationUp, + CamChainOperationDown, + CamOperationAdd, + CamOperationCopy, + CamOperationRemove, + CamOperationMove, + # 5 axis ops + CamOrientationAdd, + # shape packing + CamPackObjects, + CamSliceObjects, + CAMSimulate, + CAMSimulateChain, + KillPathsBackground, + PathsAll, + PathsBackground, + PathsChain, + PathExport, + PathExportChain, + timer_update, +) +from .pack import PackObjectsSettings +from .preferences import CamAddonPreferences +from .preset_managers import ( + AddPresetCamCutter, + AddPresetCamMachine, + AddPresetCamOperation, + CAM_CUTTER_MT_presets, + CAM_MACHINE_MT_presets, + CAM_OPERATION_MT_presets, +) +from .slice import SliceObjectsSettings +from .ui import ( + CustomPanel, + import_settings, + VIEW3D_PT_tools_curvetools, + VIEW3D_PT_tools_create, + WM_OT_gcode_import, +) +from .ui_panels.area import CAM_AREA_Panel +from .ui_panels.chains import ( + CAM_CHAINS_Panel, + CAM_UL_chains, + CAM_UL_operations, +) +from .ui_panels.cutter import CAM_CUTTER_Panel +from .ui_panels.feedrate import CAM_FEEDRATE_Panel +from .ui_panels.gcode import CAM_GCODE_Panel +from .ui_panels.info import ( + CAM_INFO_Panel, + CAM_INFO_Properties, +) +from .ui_panels.interface import ( + CAM_INTERFACE_Panel, + CAM_INTERFACE_Properties, +) +from .ui_panels.machine import CAM_MACHINE_Panel +from .ui_panels.material import ( + CAM_MATERIAL_Panel, + CAM_MATERIAL_PositionObject, + CAM_MATERIAL_Properties, +) +from .ui_panels.movement import ( + CAM_MOVEMENT_Panel, + CAM_MOVEMENT_Properties, +) +from .ui_panels.op_properties import CAM_OPERATION_PROPERTIES_Panel +from .ui_panels.operations import CAM_OPERATIONS_Panel +from .ui_panels.optimisation import ( + CAM_OPTIMISATION_Panel, + CAM_OPTIMISATION_Properties, +) +from .ui_panels.pack import CAM_PACK_Panel +from .ui_panels.slice import CAM_SLICE_Panel +from .utils import ( + check_operations_on_load, + updateOperation, +) + bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,14), + "version": (1, 0, 14), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", "warning": "", "doc_url": "https://blendercam.com/", "tracker_url": "", - "category": "Scene"} - - -class CamAddonPreferences(AddonPreferences): - # this must match the addon name, use '__package__' - # when defining this in a submodule of a python package. - bl_idname = __package__ - - op_preset_update: BoolProperty( - name="Have the Operation Presets been Updated", - default=False, - ) - - experimental: BoolProperty( - name="Show experimental features", - default=False, - ) - - update_source: StringProperty( - name="Source of updates for the addon", - description="This can be either a github repo link in which case " - "it will download the latest release on there, " - "or an api link like " - "https://api.github.com/repos//blendercam/commits" - " to get from a github repository", - default="https://github.com/pppalain/blendercam", - ) - - last_update_check: IntProperty( - name="Last update time", - default=0, - ) - - last_commit_hash: StringProperty( - name="Hash of last commit from updater", - default="", - ) - - just_updated: BoolProperty( - name="Set to true on update or initial install", - default=True, - ) - - new_version_available: StringProperty( - name="Set to new version name if one is found", - default="", - ) - - default_interface_level: EnumProperty( - name="Interface level in new file", - description="Choose visible options", - items=[ - ('0', "Basic", "Only show essential options"), - ('1', "Advanced", "Show advanced options"), - ('2', "Complete", "Show all options"), - ('3', "Experimental", "Show experimental options") - ], - default='3', - ) - - default_machine_preset: StringProperty( - name="Machine preset in new file", - description="So that machine preset choice persists between files", - default='', - ) - - def draw(self, context): - layout = self.layout - layout.label( - text="Use experimental features when you want to help development of Blender CAM:") - layout.prop(self, "experimental") - layout.prop(self, "update_source") - layout.label(text="Choose a preset update source") - - UPDATE_SOURCES = [("https://github.com/vilemduha/blendercam", "Stable", "Stable releases (github.com/vilemduja/blendercam)"), - ("https://github.com/pppalain/blendercam", "Unstable", - "Unstable releases (github.com/pppalain/blendercam)"), - # comments for searching in github actions release script to automatically set this repo - # if required - # REPO ON NEXT LINE - ("https://api.github.com/repos/pppalain/blendercam/commits", - "Direct from git (may not work)", "Get from git commits directly"), - # REPO ON PREV LINE - ("", "None", "Don't do auto update"), - ] - grid = layout.grid_flow(align=True) - for (url, short, long) in UPDATE_SOURCES: - op = grid.operator("render.cam_set_update_source", text=short) - op.new_source = url - - -class machineSettings(bpy.types.PropertyGroup): - """stores all data for machines""" - # name = StringProperty(name="Machine Name", default="Machine") - post_processor: EnumProperty( - name='Post processor', - items=( - ('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), - ('MACH3', 'Mach3', 'default mach3'), - ('EMC', 'LinuxCNC - EMC2', - 'Linux based CNC control software - formally EMC2'), - ('FADAL', 'Fadal', 'Fadal VMC'), - ('GRBL', 'grbl', - 'optimized gcode for grbl firmware on Arduino with cnc shield'), - ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), - ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), - ('TNC151', 'Heidenhain TNC151', - 'Post Processor for the Heidenhain TNC151 machine'), - ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), - ('HM50', 'Hafco HM-50', 'Hafco HM-50'), - ('CENTROID', 'Centroid M40', 'Centroid M40'), - ('ANILAM', 'Anilam Crusader M', 'Anilam Crusader M'), - ('GRAVOS', 'Gravos', 'Gravos'), - ('WIN-PC', 'WinPC-NC', 'German CNC by Burkhard Lewetz'), - ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), - ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o') - ), - description='Post processor', - default='MACH3', - ) - # units = EnumProperty(name='Units', items = (('IMPERIAL', '')) - # position definitions: - use_position_definitions: BoolProperty( - name="Use position definitions", - description="Define own positions for op start, " - "toolchange, ending position", - default=False, - ) - starting_position: FloatVectorProperty( - name='Start position', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - update=updateMachine, - ) - mtc_position: FloatVectorProperty( - name='MTC position', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - update=updateMachine, - ) - ending_position: FloatVectorProperty( - name='End position', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - update=updateMachine, - ) - - working_area: FloatVectorProperty( - name='Work Area', - default=(0.500, 0.500, 0.100), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - update=updateMachine, - ) - feedrate_min: FloatProperty( - name="Feedrate minimum /min", - default=0.0, - min=0.00001, - max=320000, - precision=constants.PRECISION, - unit='LENGTH', - ) - feedrate_max: FloatProperty( - name="Feedrate maximum /min", - default=2, - min=0.00001, - max=320000, - precision=constants.PRECISION, - unit='LENGTH', - ) - feedrate_default: FloatProperty( - name="Feedrate default /min", - default=1.5, - min=0.00001, - max=320000, - precision=constants.PRECISION, - unit='LENGTH', - ) - hourly_rate: FloatProperty( - name="Price per hour", - default=100, - min=0.005, - precision=2, - ) - - # UNSUPPORTED: - - spindle_min: FloatProperty( - name="Spindle speed minimum RPM", - default=5000, - min=0.00001, - max=320000, - precision=1, - ) - spindle_max: FloatProperty( - name="Spindle speed maximum RPM", - default=30000, - min=0.00001, - max=320000, - precision=1, - ) - spindle_default: FloatProperty( - name="Spindle speed default RPM", - default=15000, - min=0.00001, - max=320000, - precision=1, - ) - spindle_start_time: FloatProperty( - name="Spindle start delay seconds", - description='Wait for the spindle to start spinning before starting ' - 'the feeds , in seconds', - default=0, - min=0.0000, - max=320000, - precision=1, - ) - - axis4: BoolProperty( - name="#4th axis", - description="Machine has 4th axis", - default=0, - ) - axis5: BoolProperty( - name="#5th axis", - description="Machine has 5th axis", - default=0, - ) - - eval_splitting: BoolProperty( - name="Split files", - description="split gcode file with large number of operations", - default=True, - ) # split large files - split_limit: IntProperty( - name="Operations per file", - description="Split files with larger number of operations than this", - min=1000, - max=20000000, - default=800000, - ) - - # rotary_axis1 = EnumProperty(name='Axis 1', - # items=( - # ('X', 'X', 'x'), - # ('Y', 'Y', 'y'), - # ('Z', 'Z', 'z')), - # description='Number 1 rotational axis', - # default='X', update = updateOffsetImage) - - collet_size: FloatProperty( - name="#Collet size", - description="Collet size for collision detection", - default=33, - min=0.00001, - max=320000, - precision=constants.PRECISION, - unit="LENGTH", - ) - # exporter_start = StringProperty(name="exporter start", default="%") - - # post processor options - - output_block_numbers: BoolProperty( - name="output block numbers", - description="output block numbers ie N10 at start of line", - default=False, - ) - - start_block_number: IntProperty( - name="start block number", - description="the starting block number ie 10", - default=10, - ) - - block_number_increment: IntProperty( - name="block number increment", - description="how much the block number should " - "increment for the next line", - default=10, - ) - - output_tool_definitions: BoolProperty( - name="output tool definitions", - description="output tool definitions", - default=True, - ) - - output_tool_change: BoolProperty( - name="output tool change commands", - description="output tool change commands ie: Tn M06", - default=True, - ) - - output_g43_on_tool_change: BoolProperty( - name="output G43 on tool change", - description="output G43 on tool change line", - default=False, - ) - - -class PackObjectsSettings(bpy.types.PropertyGroup): - """stores all data for machines""" - sheet_fill_direction: EnumProperty( - name='Fill direction', - items=( - ('X', 'X', 'Fills sheet in X axis direction'), - ('Y', 'Y', 'Fills sheet in Y axis direction') - ), - description='Fill direction of the packer algorithm', - default='Y', - ) - sheet_x: FloatProperty( - name="X size", - description="Sheet size", - min=0.001, - max=10, - default=0.5, - precision=constants.PRECISION, - unit="LENGTH", - ) - sheet_y: FloatProperty( - name="Y size", - description="Sheet size", - min=0.001, - max=10, - default=0.5, - precision=constants.PRECISION, - unit="LENGTH", - ) - distance: FloatProperty( - name="Minimum distance", - description="minimum distance between objects(should be " - "at least cutter diameter!)", - min=0.001, - max=10, - default=0.01, - precision=constants.PRECISION, - unit="LENGTH", - ) - tolerance: FloatProperty( - name="Placement Tolerance", - description="Tolerance for placement: smaller value slower placemant", - min=0.001, - max=0.02, - default=0.005, - precision=constants.PRECISION, - unit="LENGTH", - ) - rotate: BoolProperty( - name="enable rotation", - description="Enable rotation of elements", - default=True, - ) - rotate_angle: FloatProperty( - name="Placement Angle rotation step", - description="bigger rotation angle,faster placemant", - default=0.19635 * 4, - min=math.pi/180, - max=math.pi, - precision=5, - subtype="ANGLE", - unit="ROTATION", - ) - - -class SliceObjectsSettings(bpy.types.PropertyGroup): - """stores all data for machines""" - slice_distance: FloatProperty( - name="Slicing distance", - description="slices distance in z, should be most often " - "thickness of plywood sheet.", - min=0.001, - max=10, - default=0.005, - precision=constants.PRECISION, - unit="LENGTH", - ) - slice_above0: BoolProperty( - name="Slice above 0", - description="only slice model above 0", - default=False, - ) - slice_3d: BoolProperty( - name="3d slice", - description="for 3d carving", - default=False, - ) - indexes: BoolProperty( - name="add indexes", - description="adds index text of layer + index", - default=True, - ) - - -class import_settings(bpy.types.PropertyGroup): - split_layers: BoolProperty( - name="Split Layers", - description="Save every layer as single Objects in Collection", - default=False, - ) - subdivide: BoolProperty( - name="Subdivide", - description="Only Subdivide gcode segments that are " - "bigger than 'Segment length' ", - default=False, - ) - output: EnumProperty( - name="output type", - items=( - ('mesh', 'Mesh', 'Make a mesh output'), - ('curve', 'Curve', 'Make curve output') - ), - default='curve', - ) - max_segment_size: FloatProperty( - name="", - description="Only Segments bigger then this value get subdivided", - default=0.001, - min=0.0001, - max=1.0, - unit="LENGTH", - ) - - -class camOperation(bpy.types.PropertyGroup): - - material: PointerProperty( - type=CAM_MATERIAL_Properties - ) - info: PointerProperty( - type=CAM_INFO_Properties - ) - optimisation: PointerProperty( - type=CAM_OPTIMISATION_Properties - ) - movement: PointerProperty( - type=CAM_MOVEMENT_Properties - ) - - name: StringProperty( - name="Operation Name", - default="Operation", - update=updateRest, - ) - filename: StringProperty( - name="File name", - default="Operation", - update=updateRest, - ) - auto_export: BoolProperty( - name="Auto export", - description="export files immediately after path calculation", - default=True, - ) - remove_redundant_points: BoolProperty( - name="Symplify Gcode", - description="Remove redundant points sharing the same angle" - " as the start vector", - default=False, - ) - simplify_tol: IntProperty( - name="Tolerance", - description='lower number means more precise', - default=50, - min=1, - max=1000, - ) - hide_all_others: BoolProperty( - name="Hide all others", - description="Hide all other tool pathes except toolpath" - " assotiated with selected CAM operation", - default=False, - ) - parent_path_to_object: BoolProperty( - name="Parent path to object", - description="Parent generated CAM path to source object", - default=False, - ) - object_name: StringProperty( - name='Object', - description='object handled by this operation', - update=updateOperationValid, - ) - collection_name: StringProperty( - name='Collection', - description='Object collection handled by this operation', - update=updateOperationValid, - ) - curve_object: StringProperty( - name='Curve source', - description='curve which will be sampled along the 3d object', - update=operationValid, - ) - curve_object1: StringProperty( - name='Curve target', - description='curve which will serve as attractor for the ' - 'cutter when the cutter follows the curve', - update=operationValid, - ) - source_image_name: StringProperty( - name='image_source', - description='image source', - update=operationValid, - ) - geometry_source: EnumProperty( - name='Source of data', - items=( - ('OBJECT', 'object', 'a'), - ('COLLECTION', 'Collection of objects', 'a'), - ('IMAGE', 'Image', 'a') - ), - description='Geometry source', - default='OBJECT', - update=updateOperationValid, - ) - cutter_type: EnumProperty( - name='Cutter', - items=( - ('END', 'End', 'end - flat cutter'), - ('BALLNOSE', 'Ballnose', 'ballnose cutter'), - ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), - ('VCARVE', 'V-carve', 'v carve cutter'), - ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), - ('CYLCONE', 'Cylinder cone', - 'Cylinder end with a Cone for Parallel - X'), - ('LASER', 'Laser', 'Laser cutter'), - ('PLASMA', 'Plasma', 'Plasma cutter'), - ('CUSTOM', 'Custom-EXPERIMENTAL', - 'modelled cutter - not well tested yet.') - ), - description='Type of cutter used', - default='END', - update=updateZbufferImage, - ) - cutter_object_name: StringProperty( - name='Cutter object', - description='object used as custom cutter for this operation', - update=updateZbufferImage, - ) - - machine_axes: EnumProperty( - name='Number of axes', - items=( - ('3', '3 axis', 'a'), - ('4', '#4 axis - EXPERIMENTAL', 'a'), - ('5', '#5 axis - EXPERIMENTAL', 'a') - ), - description='How many axes will be used for the operation', - default='3', - update=updateStrategy, - ) - strategy: EnumProperty( - name='Strategy', - items=getStrategyList, - description='Strategy', - update=updateStrategy, - ) - - strategy4axis: EnumProperty( - name='4 axis Strategy', - items=( - ('PARALLELR', 'Parallel around 1st rotary axis', - 'Parallel lines around first rotary axis'), - ('PARALLEL', 'Parallel along 1st rotary axis', - 'Parallel lines along first rotary axis'), - ('HELIX', 'Helix around 1st rotary axis', - 'Helix around rotary axis'), - ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just applied to the 4th axis'), - ('CROSS', 'Cross', 'Cross paths') - ), - description='#Strategy', - default='PARALLEL', - update=updateStrategy, - ) - strategy5axis: EnumProperty( - name='Strategy', - items=( - ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just rotated by 4+5th axes'), - ), - description='5 axis Strategy', - default='INDEXED', - update=updateStrategy, - ) - - rotary_axis_1: EnumProperty( - name='Rotary axis', - items=( - ('X', 'X', ''), - ('Y', 'Y', ''), - ('Z', 'Z', ''), - ), - description='Around which axis rotates the first rotary axis', - default='X', - update=updateStrategy, - ) - rotary_axis_2: EnumProperty( - name='Rotary axis 2', - items=( - ('X', 'X', ''), - ('Y', 'Y', ''), - ('Z', 'Z', ''), - ), - description='Around which axis rotates the second rotary axis', - default='Z', - update=updateStrategy, - ) - - skin: FloatProperty( - name="Skin", - description="Material to leave when roughing ", - min=0.0, - max=1.0, - default=0.0, - precision=constants.PRECISION, - unit="LENGTH", - update=updateOffsetImage, - ) - inverse: BoolProperty( - name="Inverse milling", - description="Male to female model conversion", - default=False, - update=updateOffsetImage, - ) - array: BoolProperty( - name="Use array", - description="Create a repetitive array for producing the " - "same thing many times", - default=False, - update=updateRest, - ) - array_x_count: IntProperty( - name="X count", - description="X count", - default=1, - min=1, - max=32000, - update=updateRest, - ) - array_y_count: IntProperty( - name="Y count", - description="Y count", - default=1, - min=1, - max=32000, - update=updateRest, - ) - array_x_distance: FloatProperty( - name="X distance", - description="distance between operation origins", - min=0.00001, - max=1.0, - default=0.01, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - array_y_distance: FloatProperty( - name="Y distance", - description="distance between operation origins", - min=0.00001, - max=1.0, - default=0.01, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - - # pocket options - pocket_option: EnumProperty( - name='Start Position', - items=( - ('INSIDE', 'Inside', 'a'), - ('OUTSIDE', 'Outside', 'a') - ), - description='Pocket starting position', - default='INSIDE', - update=updateRest, - ) - pocketToCurve: BoolProperty( - name="Pocket to curve", - description="generates a curve instead of a path", - default=False, - update=updateRest, - ) - # Cutout - cut_type: EnumProperty( - name='Cut', - items=( - ('OUTSIDE', 'Outside', 'a'), - ('INSIDE', 'Inside', 'a'), - ('ONLINE', 'On line', 'a') - ), - description='Type of cutter used', - default='OUTSIDE', - update=updateRest, - ) - outlines_count: IntProperty( - name="Outlines count", - description="Outlines count", - default=1, - min=1, - max=32, - update=updateCutout, - ) - straight: BoolProperty( - name="Overshoot Style", - description="Use overshoot cutout instead of conventional rounded", - default=False, - update=updateRest, - ) - # cutter - cutter_id: IntProperty( - name="Tool number", - description="For machines which support tool change based on tool id", - min=0, - max=10000, - default=1, - update=updateRest, - ) - cutter_diameter: FloatProperty( - name="Cutter diameter", - description="Cutter diameter = 2x cutter radius", - min=0.000001, - max=10, - default=0.003, - precision=constants.PRECISION, - unit="LENGTH", - update=updateOffsetImage, - ) - cylcone_diameter: FloatProperty( - name="Bottom Diameter", - description="Bottom diameter", - min=0.000001, - max=10, - default=0.003, - precision=constants.PRECISION, - unit="LENGTH", - update=updateOffsetImage, - ) - cutter_length: FloatProperty( - name="#Cutter length", - description="#not supported#Cutter length", - min=0.0, - max=100.0, - default=25.0, - precision=constants.PRECISION, - unit="LENGTH", - update=updateOffsetImage, - ) - cutter_flutes: IntProperty( - name="Cutter flutes", - description="Cutter flutes", - min=1, - max=20, - default=2, - update=updateChipload, - ) - cutter_tip_angle: FloatProperty( - name="Cutter v-carve angle", - description="Cutter v-carve angle", - min=0.0, - max=180.0, - default=60.0, - precision=constants.PRECISION, - update=updateOffsetImage, - ) - ball_radius: FloatProperty( - name="Ball radius", - description="Radius of", - min=0.0, - max=0.035, - default=0.001, - unit="LENGTH", - precision=constants.PRECISION, - update=updateOffsetImage, - ) - # ball_cone_flute: FloatProperty(name="BallCone Flute Length", description="length of flute", min=0.0, - # max=0.1, default=0.017, unit="LENGTH", precision=constants.PRECISION, update=updateOffsetImage) - bull_corner_radius: FloatProperty( - name="Bull Corner Radius", - description="Radius tool bit corner", - min=0.0, - max=0.035, - default=0.005, - unit="LENGTH", - precision=constants.PRECISION, - update=updateOffsetImage, - ) - - cutter_description: StringProperty( - name="Tool Description", - default="", - update=updateOffsetImage, - ) - - Laser_on: StringProperty( - name="Laser ON string", - default="M68 E0 Q100", - ) - Laser_off: StringProperty( - name="Laser OFF string", - default="M68 E0 Q0", - ) - Laser_cmd: StringProperty( - name="Laser command", - default="M68 E0 Q", - ) - Laser_delay: FloatProperty( - name="Laser ON Delay", - description="time after fast move to turn on laser and " - "let machine stabilize", - default=0.2, - ) - Plasma_on: StringProperty( - name="Plasma ON string", - default="M03", - ) - Plasma_off: StringProperty( - name="Plasma OFF string", - default="M05", - ) - Plasma_delay: FloatProperty( - name="Plasma ON Delay", - description="time after fast move to turn on Plasma and " - "let machine stabilize", - default=0.1, - ) - Plasma_dwell: FloatProperty( - name="Plasma dwell time", - description="Time to dwell and warm up the torch", - default=0.0, - ) - - # steps - dist_between_paths: FloatProperty( - name="Distance between toolpaths", - default=0.001, - min=0.00001, - max=32, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - dist_along_paths: FloatProperty( - name="Distance along toolpaths", - default=0.0002, - min=0.00001, - max=32, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - parallel_angle: FloatProperty( - name="Angle of paths", - default=0, - min=-360, - max=360, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRest, - ) - - old_rotation_A: FloatProperty( - name="A axis angle", - description="old value of Rotate A axis\nto specified angle", - default=0, - min=-360, - max=360, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRest, - ) - - old_rotation_B: FloatProperty( - name="A axis angle", - description="old value of Rotate A axis\nto specified angle", - default=0, - min=-360, - max=360, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRest, - ) - - rotation_A: FloatProperty( - name="A axis angle", - description="Rotate A axis\nto specified angle", - default=0, - min=-360, - max=360, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRotation, - ) - enable_A: BoolProperty( - name="Enable A axis", - description="Rotate A axis", - default=False, - update=updateRotation, - ) - A_along_x: BoolProperty( - name="A Along X ", - description="A Parallel to X", - default=True, - update=updateRest, - ) - - rotation_B: FloatProperty( - name="B axis angle", - description="Rotate B axis\nto specified angle", - default=0, - min=-360, - max=360, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRotation, - ) - enable_B: BoolProperty( - name="Enable B axis", - description="Rotate B axis", - default=False, - update=updateRotation, - ) - - # carve only - carve_depth: FloatProperty( - name="Carve depth", - default=0.001, - min=-.100, - max=32, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - - # drill only - drill_type: EnumProperty( - name='Holes on', - items=( - ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), - ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), - ('ALL_POINTS', 'All points in curve', 'a') - ), - description='Strategy to detect holes to drill', - default='MIDDLE_SYMETRIC', - update=updateRest, - ) - # waterline only - slice_detail: FloatProperty( - name="Distance betwen slices", - default=0.001, - min=0.00001, - max=32, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - waterline_fill: BoolProperty( - name="Fill areas between slices", - description="Fill areas between slices in waterline mode", - default=True, - update=updateRest, - ) - waterline_project: BoolProperty( - name="Project paths - not recomended", - description="Project paths in areas between slices", - default=True, - update=updateRest, - ) - - # movement and ramps - use_layers: BoolProperty( - name="Use Layers", - description="Use layers for roughing", - default=True, - update=updateRest, - ) - stepdown: FloatProperty( - name="", - description="Layer height", - default=0.01, - min=0.00001, - max=32, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - lead_in: FloatProperty( - name="Lead in radius", - description="Lead out radius for torch or laser to turn off", - min=0.00, - max=1, - default=0.0, - precision=constants.PRECISION, - unit="LENGTH", - ) - lead_out: FloatProperty( - name="Lead out radius", - description="Lead out radius for torch or laser to turn off", - min=0.00, - max=1, - default=0.0, - precision=constants.PRECISION, - unit="LENGTH", - ) - profile_start: IntProperty( - name="Start point", - description="Start point offset", - min=0, - default=0, - update=updateRest, - ) - - # helix_angle: FloatProperty(name="Helix ramp angle", default=3*math.pi/180, min=0.00001, max=math.pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) - - minz: FloatProperty( - name="Operation depth end", - default=-0.01, - min=-3, - max=3, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - - minz_from: EnumProperty( - name='Set max depth from', - description='Set maximum operation depth', - items=( - ('OBJECT', 'Object', 'Set max operation depth from Object'), - ('MATERIAL', 'Material', 'Set max operation depth from Material'), - ('CUSTOM', 'Custom', 'Custom max depth'), - ), - default='OBJECT', - update=updateRest, - ) - - start_type: EnumProperty( - name='Start type', - items=( - ('ZLEVEL', 'Z level', 'Starts on a given Z level'), - ('OPERATIONRESULT', 'Rest milling', - 'For rest milling, operations have to be ' - 'put in chain for this to work well.'), - ), - description='Starting depth', - default='ZLEVEL', - update=updateStrategy, - ) - - maxz: FloatProperty( - name="Operation depth start", - description='operation starting depth', - default=0, - min=-3, - max=10, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) # EXPERIMENTAL - - first_down: BoolProperty( - name="First down", - description="First go down on a contour, then go to the next one", - default=False, - update=update_operation, - ) - - ####################################################### - # Image related - #################################################### - - source_image_scale_z: FloatProperty( - name="Image source depth scale", - default=0.01, - min=-1, - max=1, - precision=constants.PRECISION, - unit="LENGTH", - update=updateZbufferImage, - ) - source_image_size_x: FloatProperty( - name="Image source x size", - default=0.1, - min=-10, - max=10, - precision=constants.PRECISION, - unit="LENGTH", - update=updateZbufferImage, - ) - source_image_offset: FloatVectorProperty( - name='Image offset', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - update=updateZbufferImage, - ) - - source_image_crop: BoolProperty( - name="Crop source image", - description="Crop source image - the position of the sub-rectangle " - "is relative to the whole image, so it can be used for e.g. " - "finishing just a part of an image", - default=False, - update=updateZbufferImage, - ) - source_image_crop_start_x: FloatProperty( - name='crop start x', - default=0, - min=0, - max=100, - precision=constants.PRECISION, - subtype='PERCENTAGE', - update=updateZbufferImage, - ) - source_image_crop_start_y: FloatProperty( - name='crop start y', - default=0, - min=0, - max=100, - precision=constants.PRECISION, - subtype='PERCENTAGE', - update=updateZbufferImage, - ) - source_image_crop_end_x: FloatProperty( - name='crop end x', - default=100, - min=0, - max=100, - precision=constants.PRECISION, - subtype='PERCENTAGE', - update=updateZbufferImage, - ) - source_image_crop_end_y: FloatProperty( - name='crop end y', - default=100, - min=0, - max=100, - precision=constants.PRECISION, - subtype='PERCENTAGE', - update=updateZbufferImage, - ) - - ######################################################### - # Toolpath and area related - ##################################################### - - ambient_behaviour: EnumProperty( - name='Ambient', - items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), - description='handling ambient surfaces', - default='ALL', - update=updateZbufferImage, - ) - - ambient_radius: FloatProperty( - name="Ambient radius", - description="Radius around the part which will be milled if " - "ambient is set to Around", - min=0.0, - max=100.0, - default=0.01, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - # ambient_cutter = EnumProperty(name='Borders',items=(('EXTRAFORCUTTER', 'Extra for cutter', "Extra space for cutter is cut around the segment"),('ONBORDER', "Cutter on edge", "Cutter goes exactly on edge of ambient with it's middle") ,('INSIDE', "Inside segment", 'Cutter stays within segment') ),description='handling of ambient and cutter size',default='INSIDE') - use_limit_curve: BoolProperty( - name="Use limit curve", - description="A curve limits the operation area", - default=False, - update=updateRest, - ) - ambient_cutter_restrict: BoolProperty( - name="Cutter stays in ambient limits", - description="Cutter doesn't get out from ambient limits otherwise " - "goes on the border exactly", - default=True, - update=updateRest, - ) # restricts cutter inside ambient only - limit_curve: StringProperty( - name='Limit curve', - description='curve used to limit the area of the operation', - update=updateRest, - ) - - # feeds - feedrate: FloatProperty( - name="Feedrate", - description="Feedrate in units per minute", - min=0.00005, - max=50.0, - default=1.0, - precision=constants.PRECISION, - unit="LENGTH", - update=updateChipload, - ) - plunge_feedrate: FloatProperty( - name="Plunge speed ", - description="% of feedrate", - min=0.1, - max=100.0, - default=50.0, - precision=1, - subtype='PERCENTAGE', - update=updateRest, - ) - plunge_angle: FloatProperty( - name="Plunge angle", - description="What angle is allready considered to plunge", - default=math.pi / 6, - min=0, - max=math.pi * 0.5, - precision=0, - subtype="ANGLE", - unit="ROTATION", - update=updateRest, - ) - spindle_rpm: FloatProperty( - name="Spindle rpm", - description="Spindle speed ", - min=0, - max=60000, - default=12000, - update=updateChipload, - ) - - # optimization and performance - - do_simulation_feedrate: BoolProperty( - name="Adjust feedrates with simulation EXPERIMENTAL", - description="Adjust feedrates with simulation", - default=False, - update=updateRest, - ) - - dont_merge: BoolProperty( - name="Dont merge outlines when cutting", - description="this is usefull when you want to cut around everything", - default=False, - update=updateRest, - ) - - pencil_threshold: FloatProperty( - name="Pencil threshold", - default=0.00002, - min=0.00000001, - max=1, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - - crazy_threshold1: FloatProperty( - name="min engagement", - default=0.02, - min=0.00000001, - max=100, - precision=constants.PRECISION, - update=updateRest, - ) - crazy_threshold5: FloatProperty( - name="optimal engagement", - default=0.3, - min=0.00000001, - max=100, - precision=constants.PRECISION, - update=updateRest, - ) - crazy_threshold2: FloatProperty( - name="max engagement", - default=0.5, - min=0.00000001, - max=100, - precision=constants.PRECISION, - update=updateRest, - ) - crazy_threshold3: FloatProperty( - name="max angle", - default=2, - min=0.00000001, - max=100, - precision=constants.PRECISION, - update=updateRest, - ) - crazy_threshold4: FloatProperty( - name="test angle step", - default=0.05, - min=0.00000001, - max=100, - precision=constants.PRECISION, - update=updateRest, - ) - # Add pocket operation to medial axis - add_pocket_for_medial: BoolProperty( - name="Add pocket operation", - description="clean unremoved material after medial axis", - default=True, - update=updateRest, - ) - - add_mesh_for_medial: BoolProperty( - name="Add Medial mesh", - description="Medial operation returns mesh for editing and " - "further processing", - default=False, - update=updateRest, - ) - #### - medial_axis_threshold: FloatProperty( - name="Long vector threshold", - default=0.001, - min=0.00000001, - max=100, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - medial_axis_subdivision: FloatProperty( - name="Fine subdivision", - default=0.0002, - min=0.00000001, - max=100, - precision=constants.PRECISION, - unit="LENGTH", - update=updateRest, - ) - # calculations - - # bridges - use_bridges: BoolProperty( - name="Use bridges", - description="use bridges in cutout", - default=False, - update=updateBridges, - ) - bridges_width: FloatProperty( - name='width of bridges', - default=0.002, - unit='LENGTH', - precision=constants.PRECISION, - update=updateBridges, - ) - bridges_height: FloatProperty( - name='height of bridges', - description="Height from the bottom of the cutting operation", - default=0.0005, - unit='LENGTH', - precision=constants.PRECISION, - update=updateBridges, - ) - bridges_collection_name: StringProperty( - name='Bridges Collection', - description='Collection of curves used as bridges', - update=operationValid, - ) - use_bridge_modifiers: BoolProperty( - name="use bridge modifiers", - description="include bridge curve modifiers using render level when " - "calculating operation, does not effect original bridge data", - default=True, - update=updateBridges, - ) - - # commented this - auto bridges will be generated, but not as a setting of the operation - # bridges_placement = EnumProperty(name='Bridge placement', - # items=( - # ('AUTO','Automatic', 'Automatic bridges with a set distance'), - # ('MANUAL','Manual', 'Manual placement of bridges'), - # ), - # description='Bridge placement', - # default='AUTO', - # update = updateStrategy) - # - # bridges_per_curve = IntProperty(name="minimum bridges per curve", description="", default=4, min=1, max=512, update = updateBridges) - # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=constants.PRECISION, update = updateBridges) - - use_modifiers: BoolProperty( - name="use mesh modifiers", - description="include mesh modifiers using render level when " - "calculating operation, does not effect original mesh", - default=True, - update=operationValid, - ) - # optimisation panel - - # material settings - - -############################################################################## - # MATERIAL SETTINGS - - min: FloatVectorProperty( - name='Operation minimum', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - ) - max: FloatVectorProperty( - name='Operation maximum', - default=(0, 0, 0), - unit='LENGTH', - precision=constants.PRECISION, - subtype="XYZ", - ) - - # g-code options for operation - output_header: BoolProperty( - name="output g-code header", - description="output user defined g-code command header" - " at start of operation", - default=False, - ) - - gcode_header: StringProperty( - name="g-code header", - description="g-code commands at start of operation." - " Use ; for line breaks", - default="G53 G0", - ) - - enable_dust: BoolProperty( - name="Dust collector", - description="output user defined g-code command header" - " at start of operation", - default=False, - ) - - gcode_start_dust_cmd: StringProperty( - name="Start dust collector", - description="commands to start dust collection. Use ; for line breaks", - default="M100", - ) - - gcode_stop_dust_cmd: StringProperty( - name="Stop dust collector", - description="command to stop dust collection. Use ; for line breaks", - default="M101", - ) - - enable_hold: BoolProperty( - name="Hold down", - description="output hold down command at start of operation", - default=False, - ) - - gcode_start_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." - " Use ; for line breaks", - default="M102", - ) - - gcode_stop_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", - default="M103", - ) - - enable_mist: BoolProperty( - name="Mist", - description="Mist command at start of operation", - default=False, - ) - - gcode_start_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." - " Use ; for line breaks", - default="M104", - ) - - gcode_stop_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", - default="M105", - ) - - output_trailer: BoolProperty( - name="output g-code trailer", - description="output user defined g-code command trailer" - " at end of operation", - default=False, - ) - - gcode_trailer: StringProperty( - name="g-code trailer", - description="g-code commands at end of operation." - " Use ; for line breaks", - default="M02", - ) - - # internal properties - ########################################### - - # testing = IntProperty(name="developer testing ", description="This is just for script authors for help in coding, keep 0", default=0, min=0, max=512) - offset_image = numpy.array([], dtype=float) - zbuffer_image = numpy.array([], dtype=float) - - silhouete = sgeometry.Polygon() - ambient = sgeometry.Polygon() - operation_limit = sgeometry.Polygon() - borderwidth = 50 - object = None - path_object_name: StringProperty( - name='Path object', - description='actual cnc path' - ) - - # update and tags and related - - changed: BoolProperty( - name="True if any of the operation settings has changed", - description="mark for update", - default=False, - ) - update_zbufferimage_tag: BoolProperty( - name="mark zbuffer image for update", - description="mark for update", - default=True, - ) - update_offsetimage_tag: BoolProperty( - name="mark offset image for update", - description="mark for update", - default=True, - ) - update_silhouete_tag: BoolProperty( - name="mark silhouete image for update", - description="mark for update", - default=True, - ) - update_ambient_tag: BoolProperty( - name="mark ambient polygon for update", - description="mark for update", - default=True, - ) - update_bullet_collision_tag: BoolProperty( - name="mark bullet collisionworld for update", - description="mark for update", - default=True, - ) - - valid: BoolProperty( - name="Valid", - description="True if operation is ok for calculation", - default=True, - ) - changedata: StringProperty( - name='changedata', - description='change data for checking if stuff changed.', - ) - - # process related data - - computing: BoolProperty( - name="Computing right now", - description="", - default=False, - ) - pid: IntProperty( - name="process id", - description="Background process id", - default=-1, - ) - outtext: StringProperty( - name='outtext', - description='outtext', - default='', - ) - - -# this type is defined just to hold reference to operations for chains -class opReference(bpy.types.PropertyGroup): - name: StringProperty( - name="Operation name", - default="Operation", - ) - computing = False # for UiList display - - -# chain is just a set of operations which get connected on export into 1 file. -class camChain(bpy.types.PropertyGroup): - index: IntProperty( - name="index", - description="index in the hard-defined camChains", - default=-1, - ) - active_operation: IntProperty( - name="active operation", - description="active operation in chain", - default=-1, - ) - name: StringProperty( - name="Chain Name", - default="Chain", - ) - filename: StringProperty( - name="File name", - default="Chain", - ) # filename of - valid: BoolProperty( - name="Valid", - description="True if whole chain is ok for calculation", - default=True, - ) - computing: BoolProperty( - name="Computing right now", - description="", - default=False, - ) - # this is to hold just operation names. - operations: CollectionProperty( - type=opReference, - ) - + "category": "Scene", +} -class CAM_CUTTER_MT_presets(Menu): - bl_label = "Cutter presets" - preset_subdir = "cam_cutters" - preset_operator = "script.execute_preset" - draw = Menu.draw_preset +classes = [ + # CamBackgroundMonitor -class CAM_MACHINE_MT_presets(Menu): - bl_label = "Machine presets" - preset_subdir = "cam_machines" - preset_operator = "script.execute_preset" - draw = Menu.draw_preset - - @classmethod - def post_cb(cls, context): - name = cls.bl_label - filepath = bpy.utils.preset_find(name, - cls.preset_subdir, - display_name=True, - ext=".py") - context.preferences.addons['cam'].preferences.default_machine_preset = filepath - bpy.ops.wm.save_userpref() - - -class AddPresetCamCutter(bl_operators.presets.AddPresetBase, Operator): - """Add a Cutter Preset""" - bl_idname = "render.cam_preset_cutter_add" - bl_label = "Add Cutter Preset" - preset_menu = "CAM_CUTTER_MT_presets" - - preset_defines = [ - "d = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]" - ] - - preset_values = [ - "d.cutter_id", - "d.cutter_type", - "d.cutter_diameter", - "d.cutter_length", - "d.cutter_flutes", - "d.cutter_tip_angle", - "d.cutter_description", - ] - - preset_subdir = "cam_cutters" - - -class CAM_OPERATION_MT_presets(Menu): - bl_label = "Operation presets" - preset_subdir = "cam_operations" - preset_operator = "script.execute_preset" - draw = Menu.draw_preset - - -class AddPresetCamOperation(bl_operators.presets.AddPresetBase, Operator): - """Add an Operation Preset""" - bl_idname = "render.cam_preset_operation_add" - bl_label = "Add Operation Preset" - preset_menu = "CAM_OPERATION_MT_presets" - - preset_defines = [ - 'import cam', - 'o = cam.utils.setup_operation_preset()', - ] - - preset_values = [ - 'o.info.duration', - 'o.info.chipload', - 'o.info.warnings', - - 'o.material.estimate_from_model', - 'o.material.size', - 'o.material.radius_around_model', - 'o.material.origin', - - 'o.movement.stay_low', - 'o.movement.free_height', - 'o.movement.insideout', - 'o.movement.spindle_rotation', - 'o.movement.type', - 'o.movement.useG64', - 'o.movement.G64', - 'o.movement.parallel_step_back', - 'o.movement.protect_vertical', - - 'o.source_image_name', - 'o.source_image_offset', - 'o.source_image_size_x', - 'o.source_image_crop', - 'o.source_image_crop_start_x', - 'o.source_image_crop_start_y', - 'o.source_image_crop_end_x', - 'o.source_image_crop_end_y', - 'o.source_image_scale_z', - - 'o.optimisation.optimize', - 'o.optimisation.optimize_threshold', - 'o.optimisation.use_exact', - 'o.optimisation.exact_subdivide_edges', - 'o.optimisation.simulation_detail', - 'o.optimisation.pixsize', - 'o.optimisation.circle_detail', - - 'o.cut_type', - 'o.cutter_tip_angle', - 'o.cutter_id', - 'o.cutter_diameter', - 'o.cutter_type', - 'o.cutter_flutes', - 'o.cutter_length', - - 'o.ambient_behaviour', - 'o.ambient_radius', - - 'o.curve_object', - 'o.curve_object1', - 'o.limit_curve', - 'o.use_limit_curve', - - 'o.feedrate', - 'o.plunge_feedrate', - - 'o.dist_along_paths', - 'o.dist_between_paths', - - 'o.max', - 'o.min', - 'o.minz_from', - 'o.minz', - - 'o.skin', - 'o.spindle_rpm', - 'o.use_layers', - 'o.carve_depth', - - 'o.update_offsetimage_tag', - 'o.slice_detail', - 'o.drill_type', - 'o.dont_merge', - 'o.update_silhouete_tag', - 'o.inverse', - 'o.waterline_fill', - 'o.strategy', - 'o.update_zbufferimage_tag', - 'o.stepdown', - 'o.path_object_name', - 'o.pencil_threshold', - 'o.geometry_source', - 'o.object_name', - 'o.parallel_angle', - - 'o.output_header', - 'o.gcode_header', - 'o.output_trailer', - 'o.gcode_trailer', - 'o.use_modifiers', - - 'o.enable_A', - 'o.enable_B', - 'o.A_along_x', - 'o.rotation_A', - 'o.rotation_B', - 'o.straight' - ] - - preset_subdir = "cam_operations" - - -class AddPresetCamMachine(bl_operators.presets.AddPresetBase, Operator): - """Add a Cam Machine Preset""" - bl_idname = "render.cam_preset_machine_add" - bl_label = "Add Machine Preset" - preset_menu = "CAM_MACHINE_MT_presets" - - preset_defines = [ - "d = bpy.context.scene.cam_machine", - "s = bpy.context.scene.unit_settings" - ] - preset_values = [ - "d.post_processor", - "s.system", - "d.use_position_definitions", - "d.starting_position", - "d.mtc_position", - "d.ending_position", - "d.working_area", - "d.feedrate_min", - "d.feedrate_max", - "d.feedrate_default", - "d.spindle_min", - "d.spindle_max", - "d.spindle_default", - "d.axis4", - "d.axis5", - "d.collet_size", - "d.output_tool_change", - "d.output_block_numbers", - "d.output_tool_definitions", - "d.output_g43_on_tool_change", - ] - - preset_subdir = "cam_machines" - - -class BLENDERCAM_ENGINE(bpy.types.RenderEngine): - bl_idname = 'BLENDERCAM_RENDER' - bl_label = "Cam" - bl_use_eevee_viewport = True - - -_IS_LOADING_DEFAULTS = False - - -@bpy.app.handlers.persistent -def check_operations_on_load(context): - global _IS_LOADING_DEFAULTS - """checks any broken computations on load and reset them.""" - s = bpy.context.scene - for o in s.cam_operations: - if o.computing: - o.computing = False - # set interface level to previously used level for a new file - if not bpy.data.filepath: - _IS_LOADING_DEFAULTS = True - s.interface.level = bpy.context.preferences.addons['cam'].preferences.default_interface_level - machine_preset = bpy.context.preferences.addons[ - 'cam'].preferences.machine_preset = bpy.context.preferences.addons['cam'].preferences.default_machine_preset - if len(machine_preset) > 0: - print("Loading preset:", machine_preset) - # load last used machine preset - bpy.ops.script.execute_preset(filepath=machine_preset, - menu_idname="CAM_MACHINE_MT_presets") - _IS_LOADING_DEFAULTS = False - # check for updated version of the plugin - bpy.ops.render.cam_check_updates() - # copy presets if not there yet - if bpy.context.preferences.addons['cam'].preferences.just_updated: - preset_source_path = Path(__file__).parent / 'presets' - preset_target_path = Path(bpy.utils.script_path_user()) / 'presets' - - def copy_if_not_exists(src, dst): - if Path(dst).exists() == False: - shutil.copy2(src, dst) - shutil.copytree(preset_source_path, preset_target_path, - copy_function=copy_if_not_exists, dirs_exist_ok=True) - - bpy.context.preferences.addons['cam'].preferences.just_updated = False - bpy.ops.wm.save_userpref() - - if not bpy.context.preferences.addons['cam'].preferences.op_preset_update: - # Update the Operation presets - op_presets_source = os.path.join(preset_source_path, 'cam_operations') - op_presets_target = os.path.join(preset_target_path, 'cam_operations') - shutil.copytree(preset_source_path, preset_target_path, dirs_exist_ok=True) - bpy.context.preferences.addons['cam'].preferences.op_preset_update = True - - -def get_panels(): # convenience function for bot register and unregister functions - # types = bpy.types - return ( - ui.CAM_UL_operations, - # ui.CAM_UL_orientations, - ui.CAM_UL_chains, - opReference, - camChain, - machineSettings, - CamAddonPreferences, - - ui.CAM_INTERFACE_Panel, - ui.CAM_CHAINS_Panel, - ui.CAM_OPERATIONS_Panel, - ui.CAM_INFO_Panel, - ui.CAM_MATERIAL_Panel, - ui.CAM_OPERATION_PROPERTIES_Panel, - ui.CAM_OPTIMISATION_Panel, - ui.CAM_AREA_Panel, - ui.CAM_MOVEMENT_Panel, - ui.CAM_FEEDRATE_Panel, - ui.CAM_CUTTER_Panel, - ui.CAM_GCODE_Panel, - ui.CAM_MACHINE_Panel, - ui.CAM_PACK_Panel, - ui.CAM_SLICE_Panel, - ui.VIEW3D_PT_tools_curvetools, - ui.CustomPanel, - - ops.PathsBackground, - ops.KillPathsBackground, - ops.CalculatePath, - ops.PathsChain, - ops.PathExportChain, - ops.PathsAll, - ops.PathExport, - ops.CAMPositionObject, - ops.CAMSimulate, - ops.CAMSimulateChain, - ops.CamChainAdd, - ops.CamChainRemove, - ops.CamChainOperationAdd, - ops.CamChainOperationRemove, - ops.CamChainOperationUp, - ops.CamChainOperationDown, - - ops.CamOperationAdd, - ops.CamOperationCopy, - ops.CamOperationRemove, - ops.CamOperationMove, - # bridges related - ops.CamBridgesAdd, - # 5 axis ops - ops.CamOrientationAdd, - # shape packing - ops.CamPackObjects, - ops.CamSliceObjects, - # other tools - curvecamtools.CamCurveBoolean, - curvecamtools.CamCurveConvexHull, - curvecamtools.CamCurveHatch, - curvecamtools.CamCurvePlate, - curvecamtools.CamCurveDrawer, - curvecamcreate.CamCurveFlatCone, - curvecamtools.CamCurveMortise, - curvecamtools.CamOffsetSilhouete, - curvecamtools.CamObjectSilhouete, - curvecamtools.CamCurveIntarsion, - curvecamtools.CamCurveOvercuts, - curvecamtools.CamCurveOvercutsB, - curvecamtools.CamCurveRemoveDoubles, - curvecamtools.CamMeshGetPockets, - curvecamequation.CamSineCurve, - curvecamequation.CamLissajousCurve, - curvecamequation.CamHypotrochoidCurve, - curvecamequation.CamCustomCurve, - - CAM_CUTTER_MT_presets, - CAM_OPERATION_MT_presets, - CAM_MACHINE_MT_presets, - AddPresetCamCutter, - AddPresetCamOperation, - AddPresetCamMachine, - BLENDERCAM_ENGINE, - # CamBackgroundMonitor - # pack module: - PackObjectsSettings, - SliceObjectsSettings, - camOperation, - - ) - - -def compatible_panels(): - """gets panels that are for blender internal, but are compatible with blender CAM""" - t = bpy.types - return ( - # textures - t.TEXTURE_PT_context_texture, - t.TEXTURE_PT_preview, - t.TEXTURE_PT_colors, - t.TEXTURE_PT_clouds, - t.TEXTURE_PT_wood, - t.TEXTURE_PT_marble, - t.TEXTURE_PT_magic, - t.TEXTURE_PT_blend, - t.TEXTURE_PT_stucci, - t.TEXTURE_PT_image, - t.TEXTURE_PT_image_sampling, - t.TEXTURE_PT_image_mapping, - t.TEXTURE_PT_envmap, - t.TEXTURE_PT_envmap_sampling, - t.TEXTURE_PT_musgrave, - t.TEXTURE_PT_voronoi, - t.TEXTURE_PT_distortednoise, - t.TEXTURE_PT_voxeldata, - t.TEXTURE_PT_pointdensity, - t.TEXTURE_PT_pointdensity_turbulence, - t.TEXTURE_PT_ocean, - t.TEXTURE_PT_mapping, - t.TEXTURE_PT_influence, - t.TEXTURE_PT_custom_props, - - # meshes - t.DATA_PT_context_mesh, - t.DATA_PT_normals, - t.DATA_PT_texture_space, - t.DATA_PT_shape_keys, - t.DATA_PT_uv_texture, - t.DATA_PT_vertex_colors, - t.DATA_PT_vertex_groups, - t.DATA_PT_customdata, - t.DATA_PT_custom_props_mesh, - - # materials - t.MATERIAL_PT_context_material, - t.MATERIAL_PT_preview, - t.MATERIAL_PT_pipeline, - t.MATERIAL_PT_diffuse, - t.MATERIAL_PT_specular, - t.MATERIAL_PT_shading, - t.MATERIAL_PT_transp, - t.MATERIAL_PT_mirror, - t.MATERIAL_PT_sss, - t.MATERIAL_PT_halo, - t.MATERIAL_PT_flare, - t.MATERIAL_PT_game_settings, - t.MATERIAL_PT_physics, - t.MATERIAL_PT_strand, - t.MATERIAL_PT_options, - t.MATERIAL_PT_shadow, - - t.MATERIAL_PT_transp_game, - t.MATERIAL_PT_volume_density, - t.MATERIAL_PT_volume_shading, - t.MATERIAL_PT_volume_lighting, - t.MATERIAL_PT_volume_transp, - t.MATERIAL_PT_volume_integration, - t.MATERIAL_PT_volume_options, - t.MATERIAL_PT_custom_props, - - # particles - t.PARTICLE_PT_context_particles, - t.PARTICLE_PT_emission, - t.PARTICLE_PT_hair_dynamics, - t.PARTICLE_PT_cache, - t.PARTICLE_PT_velocity, - t.PARTICLE_PT_rotation, - t.PARTICLE_PT_physics, - t.PARTICLE_PT_boidbrain, - t.PARTICLE_PT_render, - t.PARTICLE_PT_draw, - t.PARTICLE_PT_children, - t.PARTICLE_PT_field_weights, - t.PARTICLE_PT_force_fields, - t.PARTICLE_PT_vertexgroups, - - # scene - t.SCENE_PT_scene, - t.SCENE_PT_unit, - t.SCENE_PT_keying_sets, - t.SCENE_PT_keying_set_paths, - t.SCENE_PT_color_management, - - t.SCENE_PT_audio, - t.SCENE_PT_physics, - t.SCENE_PT_rigid_body_world, - t.SCENE_PT_rigid_body_cache, - t.SCENE_PT_rigid_body_field_weights, - t.SCENE_PT_simplify, - t.SCENE_PT_custom_props, - - # world - t.WORLD_PT_context_world, - t.WORLD_PT_preview, - t.WORLD_PT_world, - t.WORLD_PT_ambient_occlusion, - t.WORLD_PT_environment_lighting, - t.WORLD_PT_indirect_lighting, - t.WORLD_PT_gather, - t.WORLD_PT_mist, - t.WORLD_PT_custom_props - - ) - + # .autoupdate + UpdateSourceOperator, + Updater, + UpdateChecker, -classes = [ - autoupdate.UpdateSourceOperator, - autoupdate.Updater, - autoupdate.UpdateChecker, - ui.CAM_UL_operations, - ui.CAM_UL_chains, + # .chain opReference, camChain, - machineSettings, - CamAddonPreferences, - import_settings, - ui.CAM_INTERFACE_Panel, - ui.CAM_INTERFACE_Properties, - ui.CAM_CHAINS_Panel, - ui.CAM_OPERATIONS_Panel, - ui.CAM_INFO_Properties, - ui.CAM_INFO_Panel, - ui.CAM_MATERIAL_Panel, - ui.CAM_MATERIAL_Properties, - ui.CAM_MATERIAL_PositionObject, - ui.CAM_OPERATION_PROPERTIES_Panel, - ui.CAM_OPTIMISATION_Panel, - ui.CAM_OPTIMISATION_Properties, - ui.CAM_AREA_Panel, - ui.CAM_MOVEMENT_Panel, - CAM_MOVEMENT_Properties, - ui.CAM_FEEDRATE_Panel, - ui.CAM_CUTTER_Panel, - ui.CAM_GCODE_Panel, - ui.CAM_MACHINE_Panel, - ui.CAM_PACK_Panel, - ui.CAM_SLICE_Panel, - ui.VIEW3D_PT_tools_curvetools, - ui.VIEW3D_PT_tools_create, - ui.CustomPanel, - ui.WM_OT_gcode_import, - ops.PathsBackground, - ops.KillPathsBackground, - ops.CalculatePath, - ops.PathsChain, - ops.PathExportChain, - ops.PathsAll, - ops.PathExport, - ops.CAMSimulate, - ops.CAMSimulateChain, - ops.CamChainAdd, - ops.CamChainRemove, - ops.CamChainOperationAdd, - ops.CamChainOperationRemove, - ops.CamChainOperationUp, - ops.CamChainOperationDown, + # .curvecamcreate + CamCurveDrawer, + CamCurveFlatCone, + CamCurveGear, + CamCurveHatch, + CamCurveInterlock, + CamCurveMortise, + CamCurvePlate, + CamCurvePuzzle, + + # .curvecamequation + CamCustomCurve, + CamHypotrochoidCurve, + CamLissajousCurve, + CamSineCurve, + + # .curvecamtools + CamCurveBoolean, + CamCurveConvexHull, + CamCurveIntarsion, + CamCurveOvercuts, + CamCurveOvercutsB, + CamCurveRemoveDoubles, + CamMeshGetPockets, + CamOffsetSilhouete, + CamObjectSilhouete, + + # .engine + BLENDERCAM_ENGINE, - ops.CamOperationAdd, - ops.CamOperationCopy, - ops.CamOperationRemove, - ops.CamOperationMove, + # .machine_settings + machineSettings, + + # .ops + CalculatePath, # bridges related - ops.CamBridgesAdd, + CamBridgesAdd, + CamChainAdd, + CamChainRemove, + CamChainOperationAdd, + CamChainOperationRemove, + CamChainOperationUp, + CamChainOperationDown, + CamOperationAdd, + CamOperationCopy, + CamOperationRemove, + CamOperationMove, # 5 axis ops - ops.CamOrientationAdd, + CamOrientationAdd, # shape packing - ops.CamPackObjects, - ops.CamSliceObjects, - # other tools - curvecamtools.CamCurveBoolean, - curvecamtools.CamCurveConvexHull, - curvecamtools.CamOffsetSilhouete, - curvecamtools.CamObjectSilhouete, - curvecamtools.CamCurveIntarsion, - curvecamtools.CamCurveOvercuts, - curvecamtools.CamCurveOvercutsB, - curvecamtools.CamCurveRemoveDoubles, - curvecamtools.CamMeshGetPockets, - - curvecamequation.CamSineCurve, - curvecamequation.CamLissajousCurve, - curvecamequation.CamHypotrochoidCurve, - curvecamequation.CamCustomCurve, + CamPackObjects, + CamSliceObjects, + CAMSimulate, + CAMSimulateChain, + KillPathsBackground, + PathsAll, + PathsBackground, + PathsChain, + PathExport, + PathExportChain, + + # .pack + PackObjectsSettings, - curvecamcreate.CamCurveHatch, - curvecamcreate.CamCurvePlate, - curvecamcreate.CamCurveDrawer, - curvecamcreate.CamCurveGear, - curvecamcreate.CamCurveFlatCone, - curvecamcreate.CamCurveMortise, - curvecamcreate.CamCurveInterlock, - curvecamcreate.CamCurvePuzzle, + # .preferences + CamAddonPreferences, + # .preset_managers CAM_CUTTER_MT_presets, CAM_OPERATION_MT_presets, CAM_MACHINE_MT_presets, AddPresetCamCutter, AddPresetCamOperation, AddPresetCamMachine, - BLENDERCAM_ENGINE, - # CamBackgroundMonitor - # pack module: - PackObjectsSettings, + + # .slice SliceObjectsSettings, - camOperation, + # .ui and .ui_panels - the order will affect the layout + import_settings, + CAM_UL_operations, + CAM_UL_chains, + CAM_INTERFACE_Panel, + CAM_INTERFACE_Properties, + CAM_CHAINS_Panel, + CAM_OPERATIONS_Panel, + CAM_INFO_Properties, + CAM_INFO_Panel, + CAM_MATERIAL_Panel, + CAM_MATERIAL_Properties, + CAM_MATERIAL_PositionObject, + CAM_OPERATION_PROPERTIES_Panel, + CAM_OPTIMISATION_Panel, + CAM_OPTIMISATION_Properties, + CAM_AREA_Panel, + CAM_MOVEMENT_Panel, + CAM_MOVEMENT_Properties, + CAM_FEEDRATE_Panel, + CAM_CUTTER_Panel, + CAM_GCODE_Panel, + CAM_MACHINE_Panel, + CAM_PACK_Panel, + CAM_SLICE_Panel, + VIEW3D_PT_tools_curvetools, + VIEW3D_PT_tools_create, + CustomPanel, + WM_OT_gcode_import, + + # .cam_operation - last to allow dependencies to register before it + camOperation, ] def register() -> None: - for p in classes: - bpy.utils.register_class(p) + for cls in classes: + bpy.utils.register_class(cls) - s = bpy.types.Scene + basrelief.register() - s.cam_chains = CollectionProperty( - type=camChain, - ) - s.cam_active_chain = IntProperty( + bpy.app.handlers.frame_change_pre.append(timer_update) + bpy.app.handlers.load_post.append(check_operations_on_load) + # bpy.types.INFO_HT_header.append(header_info) + + scene = bpy.types.Scene + + scene.cam_active_chain = IntProperty( name="CAM Active Chain", description="The selected chain", ) - - s.cam_operations = CollectionProperty( - type=camOperation, - ) - - s.cam_active_operation = IntProperty( + scene.cam_active_operation = IntProperty( name="CAM Active Operation", description="The selected operation", update=updateOperation, ) - s.cam_machine = PointerProperty( - type=machineSettings, + scene.cam_chains = CollectionProperty( + type=camChain, ) - - s.cam_import_gcode = PointerProperty( + scene.cam_import_gcode = PointerProperty( type=import_settings, ) - - s.cam_text = StringProperty() - bpy.app.handlers.frame_change_pre.append(ops.timer_update) - bpy.app.handlers.load_post.append(check_operations_on_load) - # bpy.types.INFO_HT_header.append(header_info) - - s.cam_pack = PointerProperty( + scene.cam_machine = PointerProperty( + type=machineSettings, + ) + scene.cam_operations = CollectionProperty( + type=camOperation, + ) + scene.cam_pack = PointerProperty( type=PackObjectsSettings, ) - - s.cam_slice = PointerProperty( + scene.cam_slice = PointerProperty( type=SliceObjectsSettings, ) - - bpy.types.Scene.interface = PointerProperty( + scene.cam_text = StringProperty() + scene.interface = PointerProperty( type=CAM_INTERFACE_Properties, ) - basrelief.register() - - from bl_ui.properties_material import ( - EEVEE_MATERIAL_PT_context_material, - EEVEE_MATERIAL_PT_surface, - EEVEE_MATERIAL_PT_settings, - ) - - panels = [ - EEVEE_MATERIAL_PT_context_material, - EEVEE_MATERIAL_PT_surface, - EEVEE_MATERIAL_PT_settings, - ] - - for panel in panels: - bpy.utils.unregister_class(panel) - panel.COMPAT_ENGINES.add('BLENDERCAM_RENDER') - bpy.utils.register_class(panel) + for panel in get_panels(): + panel.COMPAT_ENGINES.add("BLENDERCAM_RENDER") def unregister() -> None: - for p in classes: - bpy.utils.unregister_class(p) - s = bpy.types.Scene + for cls in classes: + bpy.utils.unregister_class(cls) - # cam chains are defined hardly now. - del s.cam_chains - del s.cam_active_chain - del s.cam_operations - del s.cam_active_operation - del s.cam_machine - del s.cam_import_gcode - del s.cam_text - del s.cam_pack - del s.cam_slice basrelief.unregister() - from bl_ui.properties_material import ( - EEVEE_MATERIAL_PT_context_material, - EEVEE_MATERIAL_PT_surface, - EEVEE_MATERIAL_PT_settings, - ) - - panels = [ - EEVEE_MATERIAL_PT_context_material, - EEVEE_MATERIAL_PT_surface, - EEVEE_MATERIAL_PT_settings, - ] + scene = bpy.types.Scene - for panel in panels: - panel.COMPAT_ENGINES.remove('BLENDERCAM_RENDER') + # cam chains are defined hardly now. + del scene.cam_chains + del scene.cam_active_chain + del scene.cam_operations + del scene.cam_active_operation + del scene.cam_machine + del scene.cam_import_gcode + del scene.cam_text + del scene.cam_pack + del scene.cam_slice + + for panel in get_panels(): + if 'BLENDERCAM_RENDER' in panel.COMPAT_ENGINES: + panel.COMPAT_ENGINES.remove('BLENDERCAM_RENDER') diff --git a/scripts/addons/cam/async_op.py b/scripts/addons/cam/async_op.py index 6e212fa51..bb298a626 100644 --- a/scripts/addons/cam/async_op.py +++ b/scripts/addons/cam/async_op.py @@ -1,8 +1,8 @@ -import bpy import sys - import types +import bpy + @types.coroutine def progress_async(text, n=None, value_type='%'): diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index cd522e430..70856e9c4 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -1,17 +1,18 @@ +import calendar from datetime import date -from .version import __version__ as current_version -from urllib.request import urlopen +import io import json +import os import pathlib +import re +import sys +from urllib.request import urlopen import zipfile + import bpy from bpy.props import StringProperty -import re -import io -import os -import sys -import calendar +from .version import __version__ as current_version class UpdateChecker(bpy.types.Operator): diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index 86035f4ca..f1c4ad195 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -1,9 +1,14 @@ -import bpy +from math import ( + ceil, + floor, + sqrt +) +import re import time + import numpy -import math -import re -from math import * + +import bpy from bpy.props import ( BoolProperty, EnumProperty, diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index 224d7ff38..f45f729bc 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -19,21 +19,22 @@ # # ***** END GPL LICENCE BLOCK ***** # here is the bridges functionality of Blender CAM. The functions here are called with operators defined from ops.py. +from math import ( + hypot, + pi, +) + +from shapely import ops as sops +from shapely import geometry as sgeometry +from shapely import prepared import bpy -from bpy_extras.object_utils import AddObjectHelper, object_data_add +from bpy_extras.object_utils import object_data_add +from mathutils import Vector from . import utils from . import simple -import mathutils -import math - - -from shapely import ops as sops -from shapely import geometry as sgeometry -from shapely import affinity, prepared - def addBridge(x, y, rot, sizex, sizey): bpy.ops.mesh.primitive_plane_add(size=sizey*2, calc_uvs=True, enter_editmode=False, align='WORLD', @@ -77,12 +78,12 @@ def addAutoBridges(o): minx, miny, maxx, maxy = c.bounds d1 = c.project(sgeometry.Point(maxx + 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, -math.pi / 2, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, -pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point(minx - 1000, (maxy + miny) / 2.0)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi / 2, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, pi / 2, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, maxy + 1000)) @@ -92,7 +93,7 @@ def addAutoBridges(o): bpy.context.collection.objects.unlink(bo) d1 = c.project(sgeometry.Point((minx + maxx) / 2.0, miny - 1000)) p = c.interpolate(d1) - bo = addBridge(p.x, p.y, math.pi, o.bridges_width, o.cutter_diameter * 1) + bo = addBridge(p.x, p.y, pi, o.bridges_width, o.cutter_diameter * 1) g.objects.link(bo) bpy.context.collection.objects.unlink(bo) @@ -153,8 +154,8 @@ def useBridges(ch, o): if vi + 1 < len(ch_points): i2 = vi + 1 chp2 = ch_points[vi + 1] # Vector(ch_points[vi+1]) - v1 = mathutils.Vector(chp1) - v2 = mathutils.Vector(chp2) + v1 = Vector(chp1) + v2 = Vector(chp2) if v1.z < bridgeheight or v2.z < bridgeheight: v = v2 - v1 p2 = sgeometry.Point(chp2) @@ -181,13 +182,13 @@ def useBridges(ch, o): newpoints.append((chp1[0], chp1[1], max(chp1[2], bridgeheight))) cpoints = [] if itpoint: - pt = mathutils.Vector((intersections.x, intersections.y, intersections.z)) + pt = Vector((intersections.x, intersections.y, intersections.z)) cpoints = [pt] elif itmpoint: cpoints = [] for p in intersections.geoms: - pt = mathutils.Vector((p.x, p.y, p.z)) + pt = Vector((p.x, p.y, p.z)) cpoints.append(pt) # ####sort collisions here :( ncpoints = [] @@ -243,7 +244,7 @@ def useBridges(ch, o): if z == bridgeheight: # find all points with z = bridge height count += 1 if isedge == 1: # This is to subdivide edges which are longer than the width of the bridge - edgelength = math.hypot(x - x2, y - y2) + edgelength = hypot(x - x2, y - y2) if edgelength > o.bridges_width: # make new vertex verts.append(((x + x2)/2, (y + y2)/2, o.minz)) diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py new file mode 100644 index 000000000..cd5baedb0 --- /dev/null +++ b/scripts/addons/cam/cam_chunk.py @@ -0,0 +1,1271 @@ +# blender CAM chunk.py (c) 2012 Vilem Novak +# +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ***** END GPL LICENCE BLOCK ***** + +from math import ( + ceil, + cos, + hypot, + pi, + sin, + sqrt, + tan, +) + +import numpy as np +from shapely.geometry import polygon as spolygon +from shapely import geometry as sgeometry + +import bpy +from mathutils import Vector + +from . import polygon_utils_cam +from .simple import ( + activate, + dist2d, + progress, +) +from .exception import CamException +from .numba_wrapper import jit + + +def Rotate_pbyp(originp, p, ang): # rotate point around another point with angle + ox, oy, oz = originp + px, py, oz = p + + if ang == abs(pi / 2): + d = ang / abs(ang) + qx = ox + d * (oy - py) + qy = oy + d * (px - ox) + else: + qx = ox + cos(ang) * (px - ox) - sin(ang) * (py - oy) + qy = oy + sin(ang) * (px - ox) + cos(ang) * (py - oy) + rot_p = [qx, qy, oz] + return rot_p + + +@jit(nopython=True, parallel=True, fastmath=True, cache=True) +def _internalXyDistanceTo(ourpoints, theirpoints, cutoff): + v1 = ourpoints[0] + v2 = theirpoints[0] + minDistSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 + cutoffSq = cutoff**2 + for v1 in ourpoints: + for v2 in theirpoints: + distSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 + if distSq < cutoffSq: + return sqrt(distSq) + minDistSq = min(distSq, minDistSq) + return sqrt(minDistSq) + + +# for building points - stores points as lists for easy insert /append behaviour +class camPathChunkBuilder: + def __init__(self, inpoints=None, startpoints=None, endpoints=None, rotations=None): + if inpoints is None: + inpoints = [] + self.points = inpoints + self.startpoints = startpoints or [] + self.endpoints = endpoints or [] + self.rotations = rotations or [] + self.depth = None + + def to_chunk(self): + chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) + if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): + chunk.closed = True + if self.depth is not None: + chunk.depth = self.depth + + return chunk + +# an actual chunk - stores points as numpy arrays + + +class camPathChunk: + # parents=[] + # children=[] + # sorted=False + + # progressIndex=-1# for e.g. parallel strategy, when trying to save time.. + def __init__(self, inpoints, startpoints=None, endpoints=None, rotations=None): + # name this as _points so nothing external accesses it directly + # for 3 axes, this is only storage of points. For N axes, here go the sampled points + if len(inpoints) == 0: + self.points = np.empty(shape=(0, 3)) + else: + self.points = np.array(inpoints) + self.poly = None # get polygon just in time + self.simppoly = None + if startpoints: + # from where the sweep test begins, but also retract point for given path + self.startpoints = startpoints + else: + self.startpoints = [] + if endpoints: + self.endpoints = endpoints + else: + self.endpoints = [] # where sweep test ends + if rotations: + self.rotations = rotations + else: + self.rotations = [] # rotation of the machine axes + self.closed = False + self.children = [] + self.parents = [] + # self.unsortedchildren=False + self.sorted = False # if the chunk has allready been milled in the simulation + self.length = 0 # this is total length of this chunk. + self.zstart = 0 # this is stored for ramps mainly, + # because they are added afterwards, but have to use layer info + self.zend = 0 # + + def update_poly(self): + if len(self.points) > 2: + self.poly = sgeometry.Polygon(self.points[:, 0:2]) + else: + self.poly = sgeometry.Polygon() + + def get_point(self, n): + return self.points[n].tolist() + + def get_points(self): + return self.points.tolist() + + def get_points_np(self): + return self.points + + def set_points(self, points): + self.points = np.array(points) + + def count(self): + return len(self.points) + + def copy(self): + nchunk = camPathChunk(inpoints=self.points.copy(), startpoints=self.startpoints, + endpoints=self.endpoints, rotations=self.rotations) + nchunk.closed = self.closed + nchunk.children = self.children + nchunk.parents = self.parents + nchunk.sorted = self.sorted + nchunk.length = self.length + return nchunk + + def shift(self, x, y, z): + self.points = self.points + np.array([x, y, z]) + for i, p in enumerate(self.startpoints): + self.startpoints[i] = (p[0] + x, p[1] + y, p[2] + z) + for i, p in enumerate(self.endpoints): + self.endpoints[i] = (p[0] + x, p[1] + y, p[2] + z) + + def setZ(self, z, if_bigger=False): + if if_bigger: + self.points[:, 2] = z if z > self.points[:, 2] else self.points[:, 2] + else: + self.points[:, 2] = z + + def offsetZ(self, z): + self.points[:, 2] += z + + def flipX(self, x_centre): + self.points[:, 0] = x_centre - self.points[:, 0] + + def isbelowZ(self, z): + return np.any(self.points[:, 2] < z) + + def clampZ(self, z): + np.clip(self.points[:, 2], z, None, self.points[:, 2]) + + def clampmaxZ(self, z): + np.clip(self.points[:, 2], None, z, self.points[:, 2]) + + def dist(self, pos, o): + if self.closed: + dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 + return sqrt(np.min(dist_sq)) + else: + if o.movement.type == 'MEANDER': + d1 = dist2d(pos, self.points[0]) + d2 = dist2d(pos, self.points[-1]) + # if d22 points + # simplify them if they aren't already, to speed up distance finding + if self.simppoly is None: + self.simppoly = self.poly.simplify(0.0003).boundary + if other.simppoly is None: + other.simppoly = other.poly.simplify(0.0003).boundary + return self.simppoly.distance(other.simppoly) + else: # this is the old method, preferably should be replaced in most cases except parallel + # where this method works probably faster. + # print('warning, sorting will be slow due to bad parenting in parentChildDist') + return _internalXyDistanceTo(self.points, other.points, cutoff) + + def adaptdist(self, pos, o): + # reorders chunk so that it starts at the closest point to pos. + if self.closed: + dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 + point_idx = np.argmin(dist_sq) + new_points = np.concatenate((self.points[point_idx:], self.points[:point_idx+1])) + self.points = new_points + else: + if o.movement.type == 'MEANDER': + d1 = dist2d(pos, self.points[0]) + d2 = dist2d(pos, self.points[-1]) + if d2 < d1: + self.points = np.flip(self.points, axis=0) + + def getNextClosest(self, o, pos): + # finds closest chunk that can be milled, when inside sorting hierarchy. + mind = 100000000000 + + self.cango = False + closest = None + testlist = [] + testlist.extend(self.children) + tested = [] + tested.extend(self.children) + ch = None + while len(testlist) > 0: + chtest = testlist.pop() + if not chtest.sorted: + self.cango = False + cango = True + + for child in chtest.children: + if not child.sorted: + if child not in tested: + testlist.append(child) + tested.append(child) + cango = False + + if cango: + d = chtest.dist(pos, o) + if d < mind: + ch = chtest + mind = d + if ch is not None: + # print('found some') + return ch + # print('returning none') + return None + + def getLength(self): + # computes length of the chunk - in 3d + + point_differences = self.points[0:-1, :] - self.points[1:, :] + distances = np.linalg.norm(point_differences, axis=1) + self.length = np.sum(distances) + + def reverse(self): + self.points = np.flip(self.points, axis=0) + self.startpoints.reverse() + self.endpoints.reverse() + self.rotations.reverse() + + def pop(self, index): + print("WARNING: Popping from chunk is slow", self, index) + self.points = np.concatenate((self.points[0:index], self.points[index+1:]), axis=0) + if len(self.startpoints) > 0: + self.startpoints.pop(index) + self.endpoints.pop(index) + self.rotations.pop(index) + + def dedupePoints(self): + if len(self.points) > 1: + keep_points = np.empty(self.points.shape[0], dtype=bool) + keep_points[0] = True + diff_points = np.sum((self.points[1:]-self.points[:1])**2, axis=1) + keep_points[1:] = diff_points > 0.000000001 + self.points = self.points[keep_points, :] + + def insert(self, at_index, point, startpoint=None, endpoint=None, rotation=None): + self.append(point, startpoint=startpoint, endpoint=endpoint, + rotation=rotation, at_index=at_index) + + def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index=None): + if at_index is None: + self.points = np.concatenate((self.points, np.array([point]))) + if startpoint is not None: + self.startpoints.append(startpoint) + if endpoint is not None: + self.endpoints.append(endpoint) + if rotation is not None: + self.rotations.append(rotation) + else: + self.points = np.concatenate( + (self.points[0:at_index], np.array([point]), self.points[at_index:])) + if startpoint is not None: + self.startpoints[at_index:at_index] = [startpoint] + if endpoint is not None: + self.endpoints[at_index:at_index] = [endpoint] + if rotation is not None: + self.rotations[at_index:at_index] = [rotation] + + def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_index=None): + if len(points) == 0: + return + if at_index is None: + self.points = np.concatenate((self.points, np.array(points))) + if startpoints is not None: + self.startpoints.extend(startpoints) + if endpoints is not None: + self.endpoints.extend(endpoints) + if rotations is not None: + self.rotations.extend(rotations) + else: + self.points = np.concatenate( + (self.points[0:at_index], np.array(points), self.points[at_index:])) + if startpoints is not None: + self.startpoints[at_index:at_index] = startpoints + if endpoints is not None: + self.endpoints[at_index:at_index] = endpoints + if rotations is not None: + self.rotations[at_index:at_index] = rotations + + def clip_points(self, minx, maxx, miny, maxy): + """ remove any points outside this range """ + included_values = (self.points[:, 0] >= minx) and ((self.points[:, 0] <= maxx) + and (self.points[:, 1] >= maxy) and (self.points[:, 1] <= maxy)) + self.points = self.points[included_values] + + def rampContour(self, zstart, zend, o): + + stepdown = zstart - zend + chunk_points = [] + estlength = (zstart - zend) / tan(o.movement.ramp_in_angle) + self.getLength() + ramplength = estlength # min(ch.length,estlength) + ltraveled = 0 + endpoint = None + i = 0 + # z=zstart + znew = 10 + rounds = 0 # for counting if ramping makes more layers + while endpoint is None and not (znew == zend and i == 0): # + # for i,s in enumerate(ch.points): + # print(i, znew, zend, len(ch.points)) + s = self.points[i] + + if i > 0: + s2 = self.points[i - 1] + ltraveled += dist2d(s, s2) + ratio = ltraveled / ramplength + elif rounds > 0 and i == 0: + s2 = self.points[-1] + ltraveled += dist2d(s, s2) + ratio = ltraveled / ramplength + else: + ratio = 0 + znew = zstart - stepdown * ratio + if znew <= zend: + + ratio = ((z - zend) / (z - znew)) + v1 = Vector(chunk_points[-1]) + v2 = Vector((s[0], s[1], znew)) + v = v1 + ratio * (v2 - v1) + chunk_points.append((v.x, v.y, max(s[2], v.z))) + + if zend == o.min.z and endpoint is None and self.closed: + endpoint = i + 1 + if endpoint == len(self.points): + endpoint = 0 + # print(endpoint,len(ch.points)) + # else: + znew = max(znew, zend, s[2]) + chunk_points.append((s[0], s[1], znew)) + z = znew + if endpoint is not None: + break + i += 1 + if i >= len(self.points): + i = 0 + rounds += 1 + # if not o.use_layers: + # endpoint=0 + if endpoint is not None: # append final contour on the bottom z level + i = endpoint + started = False + # print('finaliz') + if i == len(self.points): + i = 0 + while i != endpoint or not started: + started = True + s = self.points[i] + chunk_points.append((s[0], s[1], s[2])) + # print(i,endpoint) + i += 1 + if i == len(self.points): + i = 0 + # ramp out + if o.movement.ramp_out and (not o.use_layers or not o.first_down or (o.first_down and endpoint is not None)): + z = zend + # i=endpoint + + while z < o.maxz: + if i == len(self.points): + i = 0 + s1 = self.points[i] + i2 = i - 1 + if i2 < 0: + i2 = len(self.points) - 1 + s2 = self.points[i2] + l = dist2d(s1, s2) + znew = z + tan(o.movement.ramp_out_angle) * l + if znew > o.maxz: + ratio = ((z - o.maxz) / (z - znew)) + v1 = Vector(chunk_points[-1]) + v2 = Vector((s1[0], s1[1], znew)) + v = v1 + ratio * (v2 - v1) + chunk_points.append((v.x, v.y, v.z)) + + else: + chunk_points.append((s1[0], s1[1], znew)) + z = znew + i += 1 + + # TODO: convert to numpy properly + self.points = np.array(chunk_points) + + def rampZigZag(self, zstart, zend, o): + # TODO: convert to numpy properly + if zend == None: + zend = self.points[0][2] + chunk_points = [] + # print(zstart,zend) + if zend < zstart: # this check here is only for stupid setup, + # when the chunks lie actually above operation start z. + + stepdown = zstart - zend + + estlength = (zstart - zend) / tan(o.movement.ramp_in_angle) + self.getLength() + if self.length > 0: # for single point chunks.. + ramplength = estlength + zigzaglength = ramplength / 2.000 + turns = 1 + print('turns %i' % turns) + if zigzaglength > self.length: + turns = ceil(zigzaglength / self.length) + ramplength = turns * self.length * 2.0 + zigzaglength = self.length + ramppoints = self.points.tolist() + + else: + zigzagtraveled = 0.0 + haspoints = False + ramppoints = [(self.points[0][0], self.points[0][1], self.points[0][2])] + i = 1 + while not haspoints: + # print(i,zigzaglength,zigzagtraveled) + p1 = ramppoints[-1] + p2 = self.points[i] + d = dist2d(p1, p2) + zigzagtraveled += d + if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): + ratio = 1 - (zigzagtraveled - zigzaglength) / d + if (i + 1 == len( + self.points)): # this condition is for a rare case of combined layers+bridges+ramps.. + ratio = 1 + v = p1 + ratio * (p2 - p1) + ramppoints.append(v.tolist()) + haspoints = True + else: + ramppoints.append(p2) + i += 1 + negramppoints = ramppoints.copy() + negramppoints.reverse() + ramppoints.extend(negramppoints[1:]) + + traveled = 0.0 + chunk_points.append( + (self.points[0][0], self.points[0][1], max(self.points[0][2], zstart))) + for r in range(turns): + for p in range(0, len(ramppoints)): + p1 = chunk_points[-1] + p2 = ramppoints[p] + d = dist2d(p1, p2) + traveled += d + ratio = traveled / ramplength + znew = zstart - stepdown * ratio + # max value here is so that it doesn't go + chunk_points.append((p2[0], p2[1], max(p2[2], znew))) + # below surface in the case of 3d paths + + # chunks = setChunksZ([ch],zend) + chunk_points.extend(self.points.tolist()) + + ###################################### + # ramp out - this is the same thing, just on the other side.. + if o.movement.ramp_out: + zstart = o.maxz + zend = self.points[-1][2] + # again, sometimes a chunk could theoretically end above the starting level. + if zend < zstart: + stepdown = zstart - zend + + estlength = (zstart - zend) / tan(o.movement.ramp_out_angle) + self.getLength() + if self.length > 0: + ramplength = estlength + zigzaglength = ramplength / 2.000 + turns = 1 + print('turns %i' % turns) + if zigzaglength > self.length: + turns = ceil(zigzaglength / self.length) + ramplength = turns * self.length * 2.0 + zigzaglength = self.length + ramppoints = self.points.tolist() + # revert points here, we go the other way. + ramppoints.reverse() + + else: + zigzagtraveled = 0.0 + haspoints = False + ramppoints = [(self.points[-1][0], self.points[-1] + [1], self.points[-1][2])] + i = len(self.points) - 2 + while not haspoints: + # print(i,zigzaglength,zigzagtraveled) + p1 = ramppoints[-1] + p2 = self.points[i] + d = dist2d(p1, p2) + zigzagtraveled += d + if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): + ratio = 1 - (zigzagtraveled - zigzaglength) / d + if (i + 1 == len( + self.points)): # this condition is for a rare case of + # combined layers+bridges+ramps... + ratio = 1 + # print((ratio,zigzaglength)) + v = p1 + ratio * (p2 - p1) + ramppoints.append(v.tolist()) + haspoints = True + # elif : + + else: + ramppoints.append(p2) + i -= 1 + negramppoints = ramppoints.copy() + negramppoints.reverse() + ramppoints.extend(negramppoints[1:]) + + traveled = 0.0 + for r in range(turns): + for p in range(0, len(ramppoints)): + p1 = chunk_points[-1] + p2 = ramppoints[p] + d = dist2d(p1, p2) + traveled += d + ratio = 1 - (traveled / ramplength) + znew = zstart - stepdown * ratio + chunk_points.append((p2[0], p2[1], max(p2[2], znew))) + # max value here is so that it doesn't go below surface in the case of 3d paths + self.points = np.array(chunk_points) + + # modify existing path start point + def changePathStart(self, o): + if o.profile_start > 0: + newstart = o.profile_start + chunkamt = len(self.points) + newstart = newstart % chunkamt + self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) + + def breakPathForLeadinLeadout(self, o): + iradius = o.lead_in + oradius = o.lead_out + if iradius + oradius > 0: + chunkamt = len(self.points) + + for i in range(chunkamt - 1): + apoint = self.points[i] + bpoint = self.points[i + 1] + bmax = bpoint[0] - apoint[0] + bmay = bpoint[1] - apoint[1] + segmentLength = hypot(bmax, bmay) # find segment length + + if segmentLength > 2 * max(iradius, + oradius): # Be certain there is enough room for the leadin and leadiout + # add point on the line here + # average of the two x points to find center + newpointx = (bpoint[0] + apoint[0]) / 2 + # average of the two y points to find center + newpointy = (bpoint[1] + apoint[1]) / 2 + self.points = np.concatenate( + (self.points[:i+1], np.array([[newpointx, newpointy, apoint[2]]]), self.points[i+1:])) + + def leadContour(self, o): + perimeterDirection = 1 # 1 is clockwise, 0 is CCW + if o.movement.spindle_rotation == 'CW': + if o.movement.type == 'CONVENTIONAL': + perimeterDirection = 0 + + if self.parents: # if it is inside another parent + perimeterDirection ^= 1 # toggle with a bitwise XOR + print("has parent") + + if perimeterDirection == 1: + print("path direction is Clockwise") + else: + print("path direction is counterclockwise") + iradius = o.lead_in + oradius = o.lead_out + start = self.points[0] + nextp = self.points[1] + rpoint = Rotate_pbyp(start, nextp, pi / 2) + dx = rpoint[0] - start[0] + dy = rpoint[1] - start[1] + la = hypot(dx, dy) + pvx = (iradius * dx) / la + start[0] # arc center(x) + pvy = (iradius * dy) / la + start[1] # arc center(y) + arc_c = [pvx, pvy, start[2]] + + # TODO: this could easily be numpy + chunk_points = [] # create a new cutting path + + # add lead in arc in the begining + if round(o.lead_in, 6) > 0.0: + for i in range(15): + iangle = -i * (pi / 2) / 15 + arc_p = Rotate_pbyp(arc_c, start, iangle) + chunk_points.insert(0, arc_p) + + # glue rest of the path to the arc + chunk_points.extend(self.points.tolist()) + # for i in range(len(self.points)): + # chunk_points.append(self.points[i]) + + # add lead out arc to the end + if round(o.lead_in, 6) > 0.0: + for i in range(15): + iangle = i * (pi / 2) / 15 + arc_p = Rotate_pbyp(arc_c, start, iangle) + chunk_points.append(arc_p) + + self.points = np.array(chunk_points) + + +def chunksCoherency(chunks): + # checks chunks for their stability, for pencil path. + # it checks if the vectors direction doesn't jump too much too quickly, + # if this happens it splits the chunk on such places, + # too much jumps = deletion of the chunk. this is because otherwise the router has to slow down too often, + # but also means that some parts detected by cavity algorithm won't be milled + nchunks = [] + for chunk in chunks: + if len(chunk.points) > 2: + nchunk = camPathChunkBuilder() + + # doesn't check for 1 point chunks here, they shouldn't get here at all. + lastvec = Vector(chunk.points[1]) - Vector(chunk.points[0]) + for i in range(0, len(chunk.points) - 1): + nchunk.points.append(chunk.points[i]) + vec = Vector(chunk.points[i + 1]) - Vector(chunk.points[i]) + angle = vec.angle(lastvec, vec) + # print(angle,i) + if angle > 1.07: # 60 degrees is maximum toleration for pencil paths. + if len(nchunk.points) > 4: # this is a testing threshold + nchunks.append(nchunk.to_chunk()) + nchunk = camPathChunkBuilder() + lastvec = vec + if len(nchunk.points) > 4: # this is a testing threshold + nchunk.points = np.array(nchunk.points) + nchunks.append(nchunk) + return nchunks + + +def setChunksZ(chunks, z): + newchunks = [] + for ch in chunks: + chunk = ch.copy() + chunk.setZ(z) + newchunks.append(chunk) + return newchunks + +# don't make this @jit parallel, because it sometimes gets called with small N +# and the overhead of threading is too much. + + +@jit(nopython=True, fastmath=True, cache=True) +def _optimize_internal(points, keep_points, e, protect_vertical, protect_vertical_limit): + # inlined so that numba can optimize it nicely + def _mag_sq(v1): + return v1[0]**2 + v1[1]**2 + v1[2]**2 + + def _dot_pr(v1, v2): + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] + + def _applyVerticalLimit(v1, v2, cos_limit): + """test path segment on verticality threshold, for protect_vertical option""" + z = abs(v1[2] - v2[2]) + if z > 0: + # don't use this vector because dot product of 0,0,1 is trivially just v2[2] + # vec_up = np.array([0, 0, 1]) + vec_diff = v1-v2 + vec_diff2 = v2-v1 + vec_diff_mag = np.sqrt(_mag_sq(vec_diff)) + # dot product = cos(angle) * mag1 * mag2 + cos1_times_mag = vec_diff[2] + cos2_times_mag = vec_diff2[2] + if cos1_times_mag > cos_limit*vec_diff_mag: + # vertical, moving down + v1[0] = v2[0] + v1[1] = v2[1] + elif cos2_times_mag > cos_limit*vec_diff_mag: + # vertical, moving up + v2[0] = v1[0] + v2[1] = v1[1] + + cos_limit = cos(protect_vertical_limit) + prev_i = 0 + for i in range(1, points.shape[0]-1): + v1 = points[prev_i] + v2 = points[i+1] + vmiddle = points[i] + + line_direction = v2-v1 + line_length = sqrt(_mag_sq(line_direction)) + if line_length == 0: + # don't keep duplicate points + keep_points[i] = False + continue + # normalize line direction + line_direction *= (1.0/line_length) # N in formula below + # X = A + tN (line formula) Distance to point P + # A = v1, N = line_direction, P = vmiddle + # distance = || (P - A) - ((P-A).N)N || + point_offset = vmiddle - v1 + distance_sq = _mag_sq(point_offset - (line_direction * + _dot_pr(point_offset, line_direction))) + # compare on squared distance to save a sqrt + if distance_sq < e*e: + keep_points[i] = False + else: + keep_points[i] = True + if protect_vertical: + _applyVerticalLimit(points[prev_i], points[i], cos_limit) + prev_i = i + + +def optimizeChunk(chunk, operation): + if len(chunk.points) > 2: + points = chunk.points + naxispoints = False + if len(chunk.startpoints) > 0: + startpoints = chunk.startpoints + endpoints = chunk.endpoints + naxispoints = True + + protect_vertical = operation.movement.protect_vertical and operation.machine_axes == '3' + keep_points = np.full(points.shape[0], True) + # shape points need to be on line, + # but we need to protect vertical - which + # means changing point values + # bits of this are moved from simple.py so that + # numba can optimize as a whole + _optimize_internal(points, keep_points, operation.optimisation.optimize_threshold * + 0.000001, protect_vertical, operation.movement.protect_vertical_limit) + + # now do numpy select by boolean array + chunk.points = points[keep_points] + if naxispoints: + # list comprehension so we don't have to do tons of appends + chunk.startpoints = [chunk.startpoints[i] + for i, b in enumerate(keep_points) if b == True] + chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] + chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] + return chunk + + +def limitChunks(chunks, o, + force=False): # TODO: this should at least add point on area border... + # but shouldn't be needed at all at the first place... + if o.use_limit_curve or force: + nchunks = [] + for ch in chunks: + prevsampled = True + nch = camPathChunkBuilder() + nch1 = None + closed = True + for s in ch.points: + sampled = o.ambient.contains(sgeometry.Point(s[0], s[1])) + if not sampled and len(nch.points) > 0: + nch.closed = False + closed = False + nchunks.append(nch.to_chunk()) + if nch1 is None: + nch1 = nchunks[-1] + nch = camPathChunkBuilder() + elif sampled: + nch.points.append(s) + prevsampled = sampled + if len(nch.points) > 2 and closed and ch.closed and np.array_equal(ch.points[0], ch.points[-1]): + nch.closed = True + elif ch.closed and nch1 is not None and len(nch.points) > 1 and np.array_equal(nch.points[-1], nch1.points[0]): + # here adds beginning of closed chunk to the end, if the chunks were split during limiting + nch.points.extend(nch1.points.tolist()) + nchunks.remove(nch1) + print('joining stuff') + if len(nch.points) > 0: + nchunks.append(nch.to_chunk()) + return nchunks + else: + return chunks + + +def parentChildPoly(parents, children, o): + # hierarchy based on polygons - a polygon inside another is his child. + # hierarchy works like this: - children get milled first. + + for parent in parents: + if parent.poly is None: + parent.update_poly() + for child in children: + if child.poly is None: + child.update_poly() + if child != parent: # and len(child.poly)>0 + if parent.poly.contains(sgeometry.Point(child.poly.boundary.coords[0])): + parent.children.append(child) + child.parents.append(parent) + + +def parentChildDist(parents, children, o, distance=None): + # parenting based on x,y distance between chunks + # hierarchy works like this: - children get milled first. + + if distance is None: + dlim = o.dist_between_paths * 2 + if (o.strategy == 'PARALLEL' or o.strategy == 'CROSS') and o.movement.parallel_step_back: + dlim = dlim * 2 + else: + dlim = distance + + for child in children: + for parent in parents: + isrelation = False + if parent != child: + if parent.xyDistanceWithin(child, cutoff=dlim): + parent.children.append(child) + child.parents.append(parent) + + +def parentChild(parents, children, o): + # connect all children to all parents. Useful for any type of defining hierarchy. + # hierarchy works like this: - children get milled first. + + for child in children: + for parent in parents: + if parent != child: + parent.children.append(child) + child.parents.append(parent) + + +# this does more cleve chunks to Poly with hierarchies... ;) +def chunksToShapely(chunks): + # print ('analyzing paths') + for ch in chunks: # first convert chunk to poly + if len(ch.points) > 2: + # pchunk=[] + ch.poly = sgeometry.Polygon(ch.points[:, 0:2]) + if not ch.poly.is_valid: + ch.poly = sgeometry.Polygon() + else: + ch.poly = sgeometry.Polygon() + + for ppart in chunks: # then add hierarchy relations + for ptest in chunks: + if ppart != ptest: + if not ppart.poly.is_empty and not ptest.poly.is_empty: + if ptest.poly.contains(ppart.poly): + # hierarchy works like this: - children get milled first. + ppart.parents.append(ptest) + + for ch in chunks: # now make only simple polygons with holes, not more polys inside others + found = False + if len(ch.parents) % 2 == 1: + + for parent in ch.parents: + if len(parent.parents) + 1 == len(ch.parents): + # nparents serves as temporary storage for parents, + ch.nparents = [parent] + # not to get mixed with the first parenting during the check + found = True + break + + if not found: + ch.nparents = [] + + for ch in chunks: # then subtract the 1st level holes + ch.parents = ch.nparents + ch.nparents = None + if len(ch.parents) > 0: + + try: + ch.parents[0].poly = ch.parents[0].poly.difference( + ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) + except: + + print('chunksToShapely oops!') + + lastPt = None + tolerance = 0.0000003 + newPoints = [] + + for pt in ch.points: + toleranceXok = True + toleranceYok = True + if lastPt is not None: + if abs(pt[0] - lastPt[0]) < tolerance: + toleranceXok = False + if abs(pt[1] - lastPt[1]) < tolerance: + toleranceYok = False + + if toleranceXok or toleranceYok: + newPoints.append(pt) + lastPt = pt + else: + newPoints.append(pt) + lastPt = pt + + toleranceXok = True + toleranceYok = True + if abs(newPoints[0][0] - lastPt[0]) < tolerance: + toleranceXok = False + if abs(newPoints[0][1] - lastPt[1]) < tolerance: + toleranceYok = False + + if not toleranceXok and not toleranceYok: + newPoints.pop() + + ch.points = np.array(newPoints) + ch.poly = sgeometry.Polygon(ch.points) + + try: + ch.parents[0].poly = ch.parents[0].poly.difference(ch.poly) + except: + + # print('chunksToShapely double oops!') + + lastPt = None + tolerance = 0.0000003 + newPoints = [] + + for pt in ch.parents[0].points: + toleranceXok = True + toleranceYok = True + # print( '{0:.9f}, {0:.9f}, {0:.9f}'.format(pt[0], pt[1], pt[2]) ) + # print(pt) + if lastPt is not None: + if abs(pt[0] - lastPt[0]) < tolerance: + toleranceXok = False + if abs(pt[1] - lastPt[1]) < tolerance: + toleranceYok = False + + if toleranceXok or toleranceYok: + newPoints.append(pt) + lastPt = pt + else: + newPoints.append(pt) + lastPt = pt + + toleranceXok = True + toleranceYok = True + if abs(newPoints[0][0] - lastPt[0]) < tolerance: + toleranceXok = False + if abs(newPoints[0][1] - lastPt[1]) < tolerance: + toleranceYok = False + + if not toleranceXok and not toleranceYok: + newPoints.pop() + # print('starting and ending points too close, removing ending point') + + ch.parents[0].points = np.array(newPoints) + ch.parents[0].poly = sgeometry.Polygon(ch.parents[0].points) + + ch.parents[0].poly = ch.parents[0].poly.difference( + ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) + + returnpolys = [] + + for polyi in range(0, len(chunks)): # export only the booleaned polygons + ch = chunks[polyi] + if not ch.poly.is_empty: + if len(ch.parents) == 0: + returnpolys.append(ch.poly) + from shapely.geometry import MultiPolygon + polys = MultiPolygon(returnpolys) + return polys + + +def meshFromCurveToChunk(object): + mesh = object.data + # print('detecting contours from curve') + chunks = [] + chunk = camPathChunkBuilder() + ek = mesh.edge_keys + d = {} + for e in ek: + d[e] = 1 # + dk = d.keys() + x = object.location.x + y = object.location.y + z = object.location.z + lastvi = 0 + vtotal = len(mesh.vertices) + perc = 0 + progress('processing curve - START - Vertices: ' + str(vtotal)) + for vi in range(0, len(mesh.vertices) - 1): + co = (mesh.vertices[vi].co + object.location).to_tuple() + if not dk.isdisjoint([(vi, vi + 1)]) and d[(vi, vi + 1)] == 1: + chunk.points.append(co) + else: + chunk.points.append(co) + if len(chunk.points) > 2 and (not (dk.isdisjoint([(vi, lastvi)])) or not ( + dk.isdisjoint([(lastvi, vi)]))): # this was looping chunks of length of only 2 points... + # print('itis') + + chunk.closed = True + chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) + # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z + lastvi = vi + 1 + chunk = chunk.to_chunk() + chunk.dedupePoints() + if chunk.count() >= 1: + # dump single point chunks + chunks.append(chunk) + chunk = camPathChunkBuilder() + + progress('processing curve - FINISHED') + + vi = len(mesh.vertices) - 1 + chunk.points.append((mesh.vertices[vi].co.x + x, + mesh.vertices[vi].co.y + y, mesh.vertices[vi].co.z + z)) + if not (dk.isdisjoint([(vi, lastvi)])) or not (dk.isdisjoint([(lastvi, vi)])): + chunk.closed = True + chunk.points.append( + (mesh.vertices[lastvi].co.x + x, mesh.vertices[lastvi].co.y + y, mesh.vertices[lastvi].co.z + z)) + chunk = chunk.to_chunk() + chunk.dedupePoints() + if chunk.count() >= 1: + # dump single point chunks + chunks.append(chunk) + return chunks + + +def makeVisible(o): + storage = [True, []] + + if not o.visible_get(): + storage[0] = False + + cam_collection = bpy.data.collections.new("cam") + bpy.context.scene.collection.children.link(cam_collection) + cam_collection.objects.link(bpy.context.object) + + for i in range(0, 20): + storage[1].append(o.layers[i]) + + o.layers[i] = bpy.context.scene.layers[i] + + return storage + + +def restoreVisibility(o, storage): + o.hide_viewport = storage[0] + # print(storage) + for i in range(0, 20): + o.layers[i] = storage[1][i] + + +def meshFromCurve(o, use_modifiers=False): + activate(o) + bpy.ops.object.duplicate() + + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + + co = bpy.context.active_object + + # support for text objects is only and only here, just convert them to curves. + if co.type == 'FONT': + bpy.ops.object.convert(target='CURVE', keep_original=False) + elif co.type != 'CURVE': # curve must be a curve... + bpy.ops.object.delete() # delete temporary object + raise CamException("Source curve object must be of type CURVE") + co.data.dimensions = '3D' + co.data.bevel_depth = 0 + co.data.extrude = 0 + + # first, convert to mesh to avoid parenting issues with hooks, then apply locrotscale. + bpy.ops.object.convert(target='MESH', keep_original=False) + + if use_modifiers: + eval_object = co.evaluated_get(bpy.context.evaluated_depsgraph_get()) + newmesh = bpy.data.meshes.new_from_object(eval_object) + oldmesh = co.data + co.modifiers.clear() + co.data = newmesh + bpy.data.meshes.remove(oldmesh) + + try: + bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) + bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) + + except: + pass + + return bpy.context.active_object + + +def curveToChunks(o, use_modifiers=False): + co = meshFromCurve(o, use_modifiers) + chunks = meshFromCurveToChunk(co) + + co = bpy.context.active_object + + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects[co.name].select_set(True) + bpy.ops.object.delete() + + return chunks + + +def shapelyToChunks(p, zlevel): # + chunk_builders = [] + # p=sortContours(p) + seq = polygon_utils_cam.shapelyToCoords(p) + i = 0 + for s in seq: + # progress(p[i]) + if len(s) > 1: + chunk = camPathChunkBuilder([]) + for v in s: + if p.has_z: + chunk.points.append((v[0], v[1], v[2])) + else: + chunk.points.append((v[0], v[1], zlevel)) + + chunk_builders.append(chunk) + i += 1 + chunk_builders.reverse() # this is for smaller shapes first. + return [c.to_chunk() for c in chunk_builders] + + +def chunkToShapely(chunk): + p = spolygon.Polygon(chunk.points) + return p + + +def chunksRefine(chunks, o): + """add extra points in between for chunks""" + for ch in chunks: + # print('before',len(ch)) + newchunk = [] + v2 = Vector(ch.points[0]) + # print(ch.points) + for s in ch.points: + + v1 = Vector(s) + v = v1 - v2 + + if v.length > o.dist_along_paths: + d = v.length + v.normalize() + i = 0 + vref = Vector((0, 0, 0)) + + while vref.length < d: + i += 1 + vref = v * o.dist_along_paths * i + if vref.length < d: + p = v2 + vref + + newchunk.append((p.x, p.y, p.z)) + + newchunk.append(s) + v2 = v1 + ch.points = np.array(newchunk) + + return chunks + + +def chunksRefineThreshold(chunks, distance, limitdistance): + """add extra points in between for chunks. For medial axis strategy only !""" + for ch in chunks: + newchunk = [] + v2 = Vector(ch.points[0]) + + for s in ch.points: + + v1 = Vector(s) + v = v1 - v2 + + if v.length > limitdistance: + d = v.length + v.normalize() + i = 1 + vref = Vector((0, 0, 0)) + while vref.length < d / 2: + + vref = v * distance * i + if vref.length < d: + p = v2 + vref + + newchunk.append((p.x, p.y, p.z)) + i += 1 + # because of the condition, so it doesn't run again. + vref = v * distance * i + while i > 0: + vref = v * distance * i + if vref.length < d: + p = v1 - vref + + newchunk.append((p.x, p.y, p.z)) + i -= 1 + + newchunk.append(s) + v2 = v1 + ch.points = np.array(newchunk) + + return chunks diff --git a/scripts/addons/cam/cam_operation.py b/scripts/addons/cam/cam_operation.py new file mode 100644 index 000000000..60dcfbde9 --- /dev/null +++ b/scripts/addons/cam/cam_operation.py @@ -0,0 +1,1192 @@ +from math import pi + +import numpy +from shapely import geometry as sgeometry + +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, + PointerProperty, + StringProperty, +) +from bpy.types import ( + PropertyGroup, +) +from . import constants +from .utils import ( + getStrategyList, + operationValid, + update_operation, + updateBridges, + updateChipload, + updateCutout, + updateOffsetImage, + updateOperationValid, + updateRest, + updateRotation, + updateStrategy, + updateZbufferImage, +) +from .ui_panels.info import CAM_INFO_Properties +from .ui_panels.material import CAM_MATERIAL_Properties +from .ui_panels.movement import CAM_MOVEMENT_Properties +from .ui_panels.optimisation import CAM_OPTIMISATION_Properties + + +class camOperation(PropertyGroup): + + material: PointerProperty( + type=CAM_MATERIAL_Properties + ) + info: PointerProperty( + type=CAM_INFO_Properties + ) + optimisation: PointerProperty( + type=CAM_OPTIMISATION_Properties + ) + movement: PointerProperty( + type=CAM_MOVEMENT_Properties + ) + + name: StringProperty( + name="Operation Name", + default="Operation", + update=updateRest, + ) + filename: StringProperty( + name="File name", + default="Operation", + update=updateRest, + ) + auto_export: BoolProperty( + name="Auto export", + description="export files immediately after path calculation", + default=True, + ) + remove_redundant_points: BoolProperty( + name="Symplify Gcode", + description="Remove redundant points sharing the same angle" + " as the start vector", + default=False, + ) + simplify_tol: IntProperty( + name="Tolerance", + description='lower number means more precise', + default=50, + min=1, + max=1000, + ) + hide_all_others: BoolProperty( + name="Hide all others", + description="Hide all other tool pathes except toolpath" + " assotiated with selected CAM operation", + default=False, + ) + parent_path_to_object: BoolProperty( + name="Parent path to object", + description="Parent generated CAM path to source object", + default=False, + ) + object_name: StringProperty( + name='Object', + description='object handled by this operation', + update=updateOperationValid, + ) + collection_name: StringProperty( + name='Collection', + description='Object collection handled by this operation', + update=updateOperationValid, + ) + curve_object: StringProperty( + name='Curve source', + description='curve which will be sampled along the 3d object', + update=operationValid, + ) + curve_object1: StringProperty( + name='Curve target', + description='curve which will serve as attractor for the ' + 'cutter when the cutter follows the curve', + update=operationValid, + ) + source_image_name: StringProperty( + name='image_source', + description='image source', + update=operationValid, + ) + geometry_source: EnumProperty( + name='Source of data', + items=( + ('OBJECT', 'object', 'a'), + ('COLLECTION', 'Collection of objects', 'a'), + ('IMAGE', 'Image', 'a') + ), + description='Geometry source', + default='OBJECT', + update=updateOperationValid, + ) + cutter_type: EnumProperty( + name='Cutter', + items=( + ('END', 'End', 'end - flat cutter'), + ('BALLNOSE', 'Ballnose', 'ballnose cutter'), + ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), + ('VCARVE', 'V-carve', 'v carve cutter'), + ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), + ('CYLCONE', 'Cylinder cone', + 'Cylinder end with a Cone for Parallel - X'), + ('LASER', 'Laser', 'Laser cutter'), + ('PLASMA', 'Plasma', 'Plasma cutter'), + ('CUSTOM', 'Custom-EXPERIMENTAL', + 'modelled cutter - not well tested yet.') + ), + description='Type of cutter used', + default='END', + update=updateZbufferImage, + ) + cutter_object_name: StringProperty( + name='Cutter object', + description='object used as custom cutter for this operation', + update=updateZbufferImage, + ) + + machine_axes: EnumProperty( + name='Number of axes', + items=( + ('3', '3 axis', 'a'), + ('4', '#4 axis - EXPERIMENTAL', 'a'), + ('5', '#5 axis - EXPERIMENTAL', 'a') + ), + description='How many axes will be used for the operation', + default='3', + update=updateStrategy, + ) + strategy: EnumProperty( + name='Strategy', + items=getStrategyList, + description='Strategy', + update=updateStrategy, + ) + + strategy4axis: EnumProperty( + name='4 axis Strategy', + items=( + ('PARALLELR', 'Parallel around 1st rotary axis', + 'Parallel lines around first rotary axis'), + ('PARALLEL', 'Parallel along 1st rotary axis', + 'Parallel lines along first rotary axis'), + ('HELIX', 'Helix around 1st rotary axis', + 'Helix around rotary axis'), + ('INDEXED', 'Indexed 3-axis', + 'all 3 axis strategies, just applied to the 4th axis'), + ('CROSS', 'Cross', 'Cross paths') + ), + description='#Strategy', + default='PARALLEL', + update=updateStrategy, + ) + strategy5axis: EnumProperty( + name='Strategy', + items=( + ('INDEXED', 'Indexed 3-axis', + 'all 3 axis strategies, just rotated by 4+5th axes'), + ), + description='5 axis Strategy', + default='INDEXED', + update=updateStrategy, + ) + + rotary_axis_1: EnumProperty( + name='Rotary axis', + items=( + ('X', 'X', ''), + ('Y', 'Y', ''), + ('Z', 'Z', ''), + ), + description='Around which axis rotates the first rotary axis', + default='X', + update=updateStrategy, + ) + rotary_axis_2: EnumProperty( + name='Rotary axis 2', + items=( + ('X', 'X', ''), + ('Y', 'Y', ''), + ('Z', 'Z', ''), + ), + description='Around which axis rotates the second rotary axis', + default='Z', + update=updateStrategy, + ) + + skin: FloatProperty( + name="Skin", + description="Material to leave when roughing ", + min=0.0, + max=1.0, + default=0.0, + precision=constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + inverse: BoolProperty( + name="Inverse milling", + description="Male to female model conversion", + default=False, + update=updateOffsetImage, + ) + array: BoolProperty( + name="Use array", + description="Create a repetitive array for producing the " + "same thing many times", + default=False, + update=updateRest, + ) + array_x_count: IntProperty( + name="X count", + description="X count", + default=1, + min=1, + max=32000, + update=updateRest, + ) + array_y_count: IntProperty( + name="Y count", + description="Y count", + default=1, + min=1, + max=32000, + update=updateRest, + ) + array_x_distance: FloatProperty( + name="X distance", + description="distance between operation origins", + min=0.00001, + max=1.0, + default=0.01, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + array_y_distance: FloatProperty( + name="Y distance", + description="distance between operation origins", + min=0.00001, + max=1.0, + default=0.01, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + # pocket options + pocket_option: EnumProperty( + name='Start Position', + items=( + ('INSIDE', 'Inside', 'a'), + ('OUTSIDE', 'Outside', 'a') + ), + description='Pocket starting position', + default='INSIDE', + update=updateRest, + ) + pocketToCurve: BoolProperty( + name="Pocket to curve", + description="generates a curve instead of a path", + default=False, + update=updateRest, + ) + # Cutout + cut_type: EnumProperty( + name='Cut', + items=( + ('OUTSIDE', 'Outside', 'a'), + ('INSIDE', 'Inside', 'a'), + ('ONLINE', 'On line', 'a') + ), + description='Type of cutter used', + default='OUTSIDE', + update=updateRest, + ) + outlines_count: IntProperty( + name="Outlines count", + description="Outlines count", + default=1, + min=1, + max=32, + update=updateCutout, + ) + straight: BoolProperty( + name="Overshoot Style", + description="Use overshoot cutout instead of conventional rounded", + default=False, + update=updateRest, + ) + # cutter + cutter_id: IntProperty( + name="Tool number", + description="For machines which support tool change based on tool id", + min=0, + max=10000, + default=1, + update=updateRest, + ) + cutter_diameter: FloatProperty( + name="Cutter diameter", + description="Cutter diameter = 2x cutter radius", + min=0.000001, + max=10, + default=0.003, + precision=constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cylcone_diameter: FloatProperty( + name="Bottom Diameter", + description="Bottom diameter", + min=0.000001, + max=10, + default=0.003, + precision=constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cutter_length: FloatProperty( + name="#Cutter length", + description="#not supported#Cutter length", + min=0.0, + max=100.0, + default=25.0, + precision=constants.PRECISION, + unit="LENGTH", + update=updateOffsetImage, + ) + cutter_flutes: IntProperty( + name="Cutter flutes", + description="Cutter flutes", + min=1, + max=20, + default=2, + update=updateChipload, + ) + cutter_tip_angle: FloatProperty( + name="Cutter v-carve angle", + description="Cutter v-carve angle", + min=0.0, + max=180.0, + default=60.0, + precision=constants.PRECISION, + update=updateOffsetImage, + ) + ball_radius: FloatProperty( + name="Ball radius", + description="Radius of", + min=0.0, + max=0.035, + default=0.001, + unit="LENGTH", + precision=constants.PRECISION, + update=updateOffsetImage, + ) + # ball_cone_flute: FloatProperty(name="BallCone Flute Length", description="length of flute", min=0.0, + # max=0.1, default=0.017, unit="LENGTH", precision=constants.PRECISION, update=updateOffsetImage) + bull_corner_radius: FloatProperty( + name="Bull Corner Radius", + description="Radius tool bit corner", + min=0.0, + max=0.035, + default=0.005, + unit="LENGTH", + precision=constants.PRECISION, + update=updateOffsetImage, + ) + + cutter_description: StringProperty( + name="Tool Description", + default="", + update=updateOffsetImage, + ) + + Laser_on: StringProperty( + name="Laser ON string", + default="M68 E0 Q100", + ) + Laser_off: StringProperty( + name="Laser OFF string", + default="M68 E0 Q0", + ) + Laser_cmd: StringProperty( + name="Laser command", + default="M68 E0 Q", + ) + Laser_delay: FloatProperty( + name="Laser ON Delay", + description="time after fast move to turn on laser and " + "let machine stabilize", + default=0.2, + ) + Plasma_on: StringProperty( + name="Plasma ON string", + default="M03", + ) + Plasma_off: StringProperty( + name="Plasma OFF string", + default="M05", + ) + Plasma_delay: FloatProperty( + name="Plasma ON Delay", + description="time after fast move to turn on Plasma and " + "let machine stabilize", + default=0.1, + ) + Plasma_dwell: FloatProperty( + name="Plasma dwell time", + description="Time to dwell and warm up the torch", + default=0.0, + ) + + # steps + dist_between_paths: FloatProperty( + name="Distance between toolpaths", + default=0.001, + min=0.00001, + max=32, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + dist_along_paths: FloatProperty( + name="Distance along toolpaths", + default=0.0002, + min=0.00001, + max=32, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + parallel_angle: FloatProperty( + name="Angle of paths", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + old_rotation_A: FloatProperty( + name="A axis angle", + description="old value of Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + old_rotation_B: FloatProperty( + name="A axis angle", + description="old value of Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + + rotation_A: FloatProperty( + name="A axis angle", + description="Rotate A axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRotation, + ) + enable_A: BoolProperty( + name="Enable A axis", + description="Rotate A axis", + default=False, + update=updateRotation, + ) + A_along_x: BoolProperty( + name="A Along X ", + description="A Parallel to X", + default=True, + update=updateRest, + ) + + rotation_B: FloatProperty( + name="B axis angle", + description="Rotate B axis\nto specified angle", + default=0, + min=-360, + max=360, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRotation, + ) + enable_B: BoolProperty( + name="Enable B axis", + description="Rotate B axis", + default=False, + update=updateRotation, + ) + + # carve only + carve_depth: FloatProperty( + name="Carve depth", + default=0.001, + min=-.100, + max=32, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + # drill only + drill_type: EnumProperty( + name='Holes on', + items=( + ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), + ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), + ('ALL_POINTS', 'All points in curve', 'a') + ), + description='Strategy to detect holes to drill', + default='MIDDLE_SYMETRIC', + update=updateRest, + ) + # waterline only + slice_detail: FloatProperty( + name="Distance betwen slices", + default=0.001, + min=0.00001, + max=32, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + waterline_fill: BoolProperty( + name="Fill areas between slices", + description="Fill areas between slices in waterline mode", + default=True, + update=updateRest, + ) + waterline_project: BoolProperty( + name="Project paths - not recomended", + description="Project paths in areas between slices", + default=True, + update=updateRest, + ) + + # movement and ramps + use_layers: BoolProperty( + name="Use Layers", + description="Use layers for roughing", + default=True, + update=updateRest, + ) + stepdown: FloatProperty( + name="", + description="Layer height", + default=0.01, + min=0.00001, + max=32, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + lead_in: FloatProperty( + name="Lead in radius", + description="Lead out radius for torch or laser to turn off", + min=0.00, + max=1, + default=0.0, + precision=constants.PRECISION, + unit="LENGTH", + ) + lead_out: FloatProperty( + name="Lead out radius", + description="Lead out radius for torch or laser to turn off", + min=0.00, + max=1, + default=0.0, + precision=constants.PRECISION, + unit="LENGTH", + ) + profile_start: IntProperty( + name="Start point", + description="Start point offset", + min=0, + default=0, + update=updateRest, + ) + + # helix_angle: FloatProperty(name="Helix ramp angle", default=3*pi/180, min=0.00001, max=pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) + + minz: FloatProperty( + name="Operation depth end", + default=-0.01, + min=-3, + max=3, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + minz_from: EnumProperty( + name='Set max depth from', + description='Set maximum operation depth', + items=( + ('OBJECT', 'Object', 'Set max operation depth from Object'), + ('MATERIAL', 'Material', 'Set max operation depth from Material'), + ('CUSTOM', 'Custom', 'Custom max depth'), + ), + default='OBJECT', + update=updateRest, + ) + + start_type: EnumProperty( + name='Start type', + items=( + ('ZLEVEL', 'Z level', 'Starts on a given Z level'), + ('OPERATIONRESULT', 'Rest milling', + 'For rest milling, operations have to be ' + 'put in chain for this to work well.'), + ), + description='Starting depth', + default='ZLEVEL', + update=updateStrategy, + ) + + maxz: FloatProperty( + name="Operation depth start", + description='operation starting depth', + default=0, + min=-3, + max=10, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) # EXPERIMENTAL + + first_down: BoolProperty( + name="First down", + description="First go down on a contour, then go to the next one", + default=False, + update=update_operation, + ) + + ####################################################### + # Image related + #################################################### + + source_image_scale_z: FloatProperty( + name="Image source depth scale", + default=0.01, + min=-1, + max=1, + precision=constants.PRECISION, + unit="LENGTH", + update=updateZbufferImage, + ) + source_image_size_x: FloatProperty( + name="Image source x size", + default=0.1, + min=-10, + max=10, + precision=constants.PRECISION, + unit="LENGTH", + update=updateZbufferImage, + ) + source_image_offset: FloatVectorProperty( + name='Image offset', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + update=updateZbufferImage, + ) + + source_image_crop: BoolProperty( + name="Crop source image", + description="Crop source image - the position of the sub-rectangle " + "is relative to the whole image, so it can be used for e.g. " + "finishing just a part of an image", + default=False, + update=updateZbufferImage, + ) + source_image_crop_start_x: FloatProperty( + name='crop start x', + default=0, + min=0, + max=100, + precision=constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_start_y: FloatProperty( + name='crop start y', + default=0, + min=0, + max=100, + precision=constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_end_x: FloatProperty( + name='crop end x', + default=100, + min=0, + max=100, + precision=constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + source_image_crop_end_y: FloatProperty( + name='crop end y', + default=100, + min=0, + max=100, + precision=constants.PRECISION, + subtype='PERCENTAGE', + update=updateZbufferImage, + ) + + ######################################################### + # Toolpath and area related + ##################################################### + + ambient_behaviour: EnumProperty( + name='Ambient', + items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), + description='handling ambient surfaces', + default='ALL', + update=updateZbufferImage, + ) + + ambient_radius: FloatProperty( + name="Ambient radius", + description="Radius around the part which will be milled if " + "ambient is set to Around", + min=0.0, + max=100.0, + default=0.01, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + # ambient_cutter = EnumProperty(name='Borders',items=(('EXTRAFORCUTTER', 'Extra for cutter', "Extra space for cutter is cut around the segment"),('ONBORDER', "Cutter on edge", "Cutter goes exactly on edge of ambient with it's middle") ,('INSIDE', "Inside segment", 'Cutter stays within segment') ),description='handling of ambient and cutter size',default='INSIDE') + use_limit_curve: BoolProperty( + name="Use limit curve", + description="A curve limits the operation area", + default=False, + update=updateRest, + ) + ambient_cutter_restrict: BoolProperty( + name="Cutter stays in ambient limits", + description="Cutter doesn't get out from ambient limits otherwise " + "goes on the border exactly", + default=True, + update=updateRest, + ) # restricts cutter inside ambient only + limit_curve: StringProperty( + name='Limit curve', + description='curve used to limit the area of the operation', + update=updateRest, + ) + + # feeds + feedrate: FloatProperty( + name="Feedrate", + description="Feedrate in units per minute", + min=0.00005, + max=50.0, + default=1.0, + precision=constants.PRECISION, + unit="LENGTH", + update=updateChipload, + ) + plunge_feedrate: FloatProperty( + name="Plunge speed ", + description="% of feedrate", + min=0.1, + max=100.0, + default=50.0, + precision=1, + subtype='PERCENTAGE', + update=updateRest, + ) + plunge_angle: FloatProperty( + name="Plunge angle", + description="What angle is allready considered to plunge", + default=pi / 6, + min=0, + max=pi * 0.5, + precision=0, + subtype="ANGLE", + unit="ROTATION", + update=updateRest, + ) + spindle_rpm: FloatProperty( + name="Spindle rpm", + description="Spindle speed ", + min=0, + max=60000, + default=12000, + update=updateChipload, + ) + + # optimization and performance + + do_simulation_feedrate: BoolProperty( + name="Adjust feedrates with simulation EXPERIMENTAL", + description="Adjust feedrates with simulation", + default=False, + update=updateRest, + ) + + dont_merge: BoolProperty( + name="Dont merge outlines when cutting", + description="this is usefull when you want to cut around everything", + default=False, + update=updateRest, + ) + + pencil_threshold: FloatProperty( + name="Pencil threshold", + default=0.00002, + min=0.00000001, + max=1, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + + crazy_threshold1: FloatProperty( + name="min engagement", + default=0.02, + min=0.00000001, + max=100, + precision=constants.PRECISION, + update=updateRest, + ) + crazy_threshold5: FloatProperty( + name="optimal engagement", + default=0.3, + min=0.00000001, + max=100, + precision=constants.PRECISION, + update=updateRest, + ) + crazy_threshold2: FloatProperty( + name="max engagement", + default=0.5, + min=0.00000001, + max=100, + precision=constants.PRECISION, + update=updateRest, + ) + crazy_threshold3: FloatProperty( + name="max angle", + default=2, + min=0.00000001, + max=100, + precision=constants.PRECISION, + update=updateRest, + ) + crazy_threshold4: FloatProperty( + name="test angle step", + default=0.05, + min=0.00000001, + max=100, + precision=constants.PRECISION, + update=updateRest, + ) + # Add pocket operation to medial axis + add_pocket_for_medial: BoolProperty( + name="Add pocket operation", + description="clean unremoved material after medial axis", + default=True, + update=updateRest, + ) + + add_mesh_for_medial: BoolProperty( + name="Add Medial mesh", + description="Medial operation returns mesh for editing and " + "further processing", + default=False, + update=updateRest, + ) + #### + medial_axis_threshold: FloatProperty( + name="Long vector threshold", + default=0.001, + min=0.00000001, + max=100, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + medial_axis_subdivision: FloatProperty( + name="Fine subdivision", + default=0.0002, + min=0.00000001, + max=100, + precision=constants.PRECISION, + unit="LENGTH", + update=updateRest, + ) + # calculations + + # bridges + use_bridges: BoolProperty( + name="Use bridges", + description="use bridges in cutout", + default=False, + update=updateBridges, + ) + bridges_width: FloatProperty( + name='width of bridges', + default=0.002, + unit='LENGTH', + precision=constants.PRECISION, + update=updateBridges, + ) + bridges_height: FloatProperty( + name='height of bridges', + description="Height from the bottom of the cutting operation", + default=0.0005, + unit='LENGTH', + precision=constants.PRECISION, + update=updateBridges, + ) + bridges_collection_name: StringProperty( + name='Bridges Collection', + description='Collection of curves used as bridges', + update=operationValid, + ) + use_bridge_modifiers: BoolProperty( + name="use bridge modifiers", + description="include bridge curve modifiers using render level when " + "calculating operation, does not effect original bridge data", + default=True, + update=updateBridges, + ) + + # commented this - auto bridges will be generated, but not as a setting of the operation + # bridges_placement = EnumProperty(name='Bridge placement', + # items=( + # ('AUTO','Automatic', 'Automatic bridges with a set distance'), + # ('MANUAL','Manual', 'Manual placement of bridges'), + # ), + # description='Bridge placement', + # default='AUTO', + # update = updateStrategy) + # + # bridges_per_curve = IntProperty(name="minimum bridges per curve", description="", default=4, min=1, max=512, update = updateBridges) + # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=constants.PRECISION, update = updateBridges) + + use_modifiers: BoolProperty( + name="use mesh modifiers", + description="include mesh modifiers using render level when " + "calculating operation, does not effect original mesh", + default=True, + update=operationValid, + ) + # optimisation panel + + # material settings + + +############################################################################## + # MATERIAL SETTINGS + + min: FloatVectorProperty( + name='Operation minimum', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + ) + max: FloatVectorProperty( + name='Operation maximum', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + ) + + # g-code options for operation + output_header: BoolProperty( + name="output g-code header", + description="output user defined g-code command header" + " at start of operation", + default=False, + ) + + gcode_header: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="G53 G0", + ) + + enable_dust: BoolProperty( + name="Dust collector", + description="output user defined g-code command header" + " at start of operation", + default=False, + ) + + gcode_start_dust_cmd: StringProperty( + name="Start dust collector", + description="commands to start dust collection. Use ; for line breaks", + default="M100", + ) + + gcode_stop_dust_cmd: StringProperty( + name="Stop dust collector", + description="command to stop dust collection. Use ; for line breaks", + default="M101", + ) + + enable_hold: BoolProperty( + name="Hold down", + description="output hold down command at start of operation", + default=False, + ) + + gcode_start_hold_cmd: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="M102", + ) + + gcode_stop_hold_cmd: StringProperty( + name="g-code header", + description="g-code commands at end operation. Use ; for line breaks", + default="M103", + ) + + enable_mist: BoolProperty( + name="Mist", + description="Mist command at start of operation", + default=False, + ) + + gcode_start_mist_cmd: StringProperty( + name="g-code header", + description="g-code commands at start of operation." + " Use ; for line breaks", + default="M104", + ) + + gcode_stop_mist_cmd: StringProperty( + name="g-code header", + description="g-code commands at end operation. Use ; for line breaks", + default="M105", + ) + + output_trailer: BoolProperty( + name="output g-code trailer", + description="output user defined g-code command trailer" + " at end of operation", + default=False, + ) + + gcode_trailer: StringProperty( + name="g-code trailer", + description="g-code commands at end of operation." + " Use ; for line breaks", + default="M02", + ) + + # internal properties + ########################################### + + # testing = IntProperty(name="developer testing ", description="This is just for script authors for help in coding, keep 0", default=0, min=0, max=512) + offset_image = numpy.array([], dtype=float) + zbuffer_image = numpy.array([], dtype=float) + + silhouete = sgeometry.Polygon() + ambient = sgeometry.Polygon() + operation_limit = sgeometry.Polygon() + borderwidth = 50 + object = None + path_object_name: StringProperty( + name='Path object', + description='actual cnc path' + ) + + # update and tags and related + + changed: BoolProperty( + name="True if any of the operation settings has changed", + description="mark for update", + default=False, + ) + update_zbufferimage_tag: BoolProperty( + name="mark zbuffer image for update", + description="mark for update", + default=True, + ) + update_offsetimage_tag: BoolProperty( + name="mark offset image for update", + description="mark for update", + default=True, + ) + update_silhouete_tag: BoolProperty( + name="mark silhouete image for update", + description="mark for update", + default=True, + ) + update_ambient_tag: BoolProperty( + name="mark ambient polygon for update", + description="mark for update", + default=True, + ) + update_bullet_collision_tag: BoolProperty( + name="mark bullet collisionworld for update", + description="mark for update", + default=True, + ) + + valid: BoolProperty( + name="Valid", + description="True if operation is ok for calculation", + default=True, + ) + changedata: StringProperty( + name='changedata', + description='change data for checking if stuff changed.', + ) + + # process related data + + computing: BoolProperty( + name="Computing right now", + description="", + default=False, + ) + pid: IntProperty( + name="process id", + description="Background process id", + default=-1, + ) + outtext: StringProperty( + name='outtext', + description='outtext', + default='', + ) diff --git a/scripts/addons/cam/chain.py b/scripts/addons/cam/chain.py new file mode 100644 index 000000000..44b60b213 --- /dev/null +++ b/scripts/addons/cam/chain.py @@ -0,0 +1,52 @@ +from bpy.props import ( + BoolProperty, + CollectionProperty, + IntProperty, + StringProperty, +) +from bpy.types import PropertyGroup + + +# this type is defined just to hold reference to operations for chains +class opReference(PropertyGroup): + name: StringProperty( + name="Operation name", + default="Operation", + ) + computing = False # for UiList display + + +# chain is just a set of operations which get connected on export into 1 file. +class camChain(PropertyGroup): + index: IntProperty( + name="index", + description="index in the hard-defined camChains", + default=-1, + ) + active_operation: IntProperty( + name="active operation", + description="active operation in chain", + default=-1, + ) + name: StringProperty( + name="Chain Name", + default="Chain", + ) + filename: StringProperty( + name="File name", + default="Chain", + ) # filename of + valid: BoolProperty( + name="Valid", + description="True if whole chain is ok for calculation", + default=True, + ) + computing: BoolProperty( + name="Computing right now", + description="", + default=False, + ) + # this is to hold just operation names. + operations: CollectionProperty( + type=opReference, + ) diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 48a41986a..8c7200fa9 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -18,19 +18,30 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -import bpy +from math import ( + cos, + pi, + radians, + sin, + tan, +) import time -from . import simple -from .simple import * - - -BULLET_SCALE = 10000 -# this is a constant for scaling the rigidbody collision world for higher precision from bullet library -CUTTER_OFFSET = (-5 * BULLET_SCALE, -5 * BULLET_SCALE, -5 * BULLET_SCALE) -# the cutter object has to be present in the scene , so we need to put it aside for sweep collisions, -# otherwise it collides itself. +import bpy +from mathutils import ( + Euler, + Vector, +) + +from .constants import ( + BULLET_SCALE, + CUTTER_OFFSET, +) +from .simple import ( + activate, + delob, + progress, +) def getCutterBullet(o): @@ -75,12 +86,12 @@ def getCutterBullet(o): elif type == 'VCARVE': angle = o.cutter_tip_angle - s = math.tan(math.pi * (90 - angle / 2) / 180) / 2 # angles in degrees + s = tan(pi * (90 - angle / 2) / 180) / 2 # angles in degrees cone_d = o.cutter_diameter * s bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=o.cutter_diameter / 2, radius2=0, depth=cone_d, end_fill_type='NGON', align='WORLD', enter_editmode=False, location=CUTTER_OFFSET, - rotation=(math.pi, 0, 0)) + rotation=(pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) @@ -91,13 +102,13 @@ def getCutterBullet(o): elif type == 'CYLCONE': angle = o.cutter_tip_angle - s = math.tan(math.pi * (90 - angle / 2) / 180) / 2 # angles in degrees + s = tan(pi * (90 - angle / 2) / 180) / 2 # angles in degrees cylcone_d = (o.cutter_diameter - o.cylcone_diameter) * s bpy.ops.mesh.primitive_cone_add(vertices=32, radius1=o.cutter_diameter / 2, radius2=o.cylcone_diameter / 2, depth=cylcone_d, end_fill_type='NGON', align='WORLD', enter_editmode=False, - location=CUTTER_OFFSET, rotation=(math.pi, 0, 0)) + location=CUTTER_OFFSET, rotation=(pi, 0, 0)) cutter = bpy.context.active_object cutter.scale *= BULLET_SCALE bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) @@ -107,29 +118,29 @@ def getCutterBullet(o): cutter.rigid_body.collision_shape = 'CONVEX_HULL' cutter.location = CUTTER_OFFSET elif type == 'BALLCONE': - angle = math.radians(o.cutter_tip_angle)/2 + angle = radians(o.cutter_tip_angle)/2 cutter_R = o.cutter_diameter/2 - Ball_R = o.ball_radius/math.cos(angle) - conedepth = (cutter_R - o.ball_radius)/math.tan(angle) + Ball_R = o.ball_radius/cos(angle) + conedepth = (cutter_R - o.ball_radius)/tan(angle) bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Point', use_cyclic_u=False) oy = Ball_R for i in range(1, 10): - ang = -i * (math.pi/2-angle) / 9 - qx = math.sin(ang) * oy - qy = oy - math.cos(ang) * oy + ang = -i * (pi/2-angle) / 9 + qx = sin(ang) * oy + qy = oy - cos(ang) * oy bpy.ops.curve.vertex_add(location=(qx, qy, 0)) conedepth += qy bpy.ops.curve.vertex_add(location=(-cutter_R, conedepth, 0)) #bpy.ops.curve.vertex_add(location=(0 , conedepth , 0)) bpy.ops.object.editmode_toggle() bpy.ops.object.convert(target='MESH') - bpy.ops.transform.rotate(value=-math.pi / 2, orient_axis='X') + bpy.ops.transform.rotate(value=-pi / 2, orient_axis='X') bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) ob = bpy.context.active_object ob.name = "BallConeTool" ob_scr = ob.modifiers.new(type='SCREW', name='scr') - ob_scr.angle = math.radians(-360) + ob_scr.angle = radians(-360) ob_scr.steps = 32 ob_scr.merge_threshold = 0 ob_scr.use_merge_vertices = True diff --git a/scripts/addons/cam/constants.py b/scripts/addons/cam/constants.py index 595cab16f..34d2f7d4b 100644 --- a/scripts/addons/cam/constants.py +++ b/scripts/addons/cam/constants.py @@ -9,3 +9,9 @@ MAX_OPERATION_TIME = 3200000000 # seconds G64_INCOMPATIBLE_MACHINES = ['GRBL'] + +BULLET_SCALE = 10000 +# this is a constant for scaling the rigidbody collision world for higher precision from bullet library +CUTTER_OFFSET = (-5 * BULLET_SCALE, -5 * BULLET_SCALE, -5 * BULLET_SCALE) +# the cutter object has to be present in the scene , so we need to put it aside for sweep collisions, +# otherwise it collides itself. diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index 1c2f82645..0387caadc 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -18,7 +18,17 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** +from math import ( + degrees, + hypot, + pi, + radians +) +from shapely.geometry import ( + LineString, + MultiLineString, +) import bpy from bpy.props import ( @@ -28,35 +38,17 @@ IntProperty, ) from bpy.types import Operator -from bpy_extras.io_utils import ImportHelper + from . import ( - utils, - pack, - polygon_utils_cam, - simple, - gcodepath, - bridges, - parametric, + involute_gear, joinery, - curvecamtools, puzzle_joinery, - involute_gear -) -import shapely -from shapely.geometry import ( - Point, - LineString, - Polygon, - MultiLineString, - MultiPoint + simple, + utils, ) -import mathutils -import math -from Equation import Expression -import numpy as np -class CamCurveHatch(bpy.types.Operator): +class CamCurveHatch(Operator): """perform hatch operation on single or multiple curves""" # by Alain Pelletier September 2021 bl_idname = "object.curve_hatch" bl_label = "CrossHatch curve" @@ -64,9 +56,9 @@ class CamCurveHatch(bpy.types.Operator): angle: FloatProperty( name="angle", - default=0, min=- - math.pi/2, - max=math.pi/2, + default=0, + min=-pi/2, + max=pi/2, precision=4, subtype="ANGLE", ) @@ -167,7 +159,7 @@ def execute(self, context): height = maxy - miny width = maxx - minx centerx = (minx+maxx) / 2 - diagonal = math.hypot(width, height) + diagonal = hypot(width, height) simple.add_bound_rectangle( minx, miny, maxx, maxy, 'crosshatch_bound') amount = int(2*diagonal/self.distance) + 1 @@ -224,7 +216,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurvePlate(bpy.types.Operator): +class CamCurvePlate(Operator): """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_plate" bl_label = "Sign plate" @@ -499,7 +491,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurveFlatCone(bpy.types.Operator): +class CamCurveFlatCone(Operator): """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_flat_cone" bl_label = "Cone flat calculator" @@ -563,13 +555,13 @@ def execute(self, context): z = self.large_d / 2 x = self.height h = x * y / (z - y) - a = math.hypot(h, y) - ab = math.hypot(x+h, z) + a = hypot(h, y) + ab = hypot(x+h, z) b = ab - a - angle = math.pi * 2 * y / a + angle = pi * 2 * y / a # create base - bpy.ops.curve.simple(Simple_Type='Segment', Simple_a=ab, Simple_b=a, Simple_endangle=math.degrees(angle), + bpy.ops.curve.simple(Simple_Type='Segment', Simple_a=ab, Simple_b=a, Simple_endangle=degrees(angle), use_cyclic_u=True, edit_mode=False) simple.active_name("_segment") @@ -596,7 +588,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurveMortise(bpy.types.Operator): +class CamCurveMortise(Operator): """Generates mortise along a curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_mortise" bl_label = "Mortise" @@ -739,7 +731,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurveInterlock(bpy.types.Operator): +class CamCurveInterlock(Operator): """Generates interlock along a curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_interlock" bl_label = "Interlock" @@ -859,7 +851,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurveDrawer(bpy.types.Operator): +class CamCurveDrawer(Operator): """Generates drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator bl_idname = "object.curve_drawer" bl_label = "Drawer" @@ -1045,7 +1037,7 @@ def execute(self, context): simple.active_name('_bot_fingers') simple.difference('_bot', '_bottom') - simple.rotate(math.pi/2) + simple.rotate(pi/2) joinery.finger_pair("_wfb0", 0, self.width - self.drawer_plate_thickness - self.finger_inset * 2) @@ -1073,7 +1065,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurvePuzzle(bpy.types.Operator): +class CamCurvePuzzle(Operator): """Generates Puzzle joints and interlocks""" # by Alain Pelletier December 2021 bl_idname = "object.curve_puzzle" bl_label = "Puzzle joints" @@ -1126,7 +1118,7 @@ class CamCurvePuzzle(bpy.types.Operator): angle: FloatProperty( name="angle A", - default=math.pi/4, + default=pi/4, min=-10, max=10, subtype="ANGLE", @@ -1134,7 +1126,7 @@ class CamCurvePuzzle(bpy.types.Operator): ) angleb: FloatProperty( name="angle B", - default=math.pi/4, + default=pi/4, min=-10, max=10, subtype="ANGLE", @@ -1420,7 +1412,7 @@ def execute(self, context): which=self.gender) elif self.interlock_type == 'MULTIANGLE': - puzzle_joinery.multiangle(self.radius, self.height, math.pi/3, self.diameter, self.finger_tolerance, + puzzle_joinery.multiangle(self.radius, self.height, pi/3, self.diameter, self.finger_tolerance, self.finger_amount, stem=self.stem_size, twist=self.twist_lock, tneck=self.twist_percent, tthick=self.twist_thick, @@ -1466,7 +1458,7 @@ def execute(self, context): return {'FINISHED'} -class CamCurveGear(bpy.types.Operator): +class CamCurveGear(Operator): """Generates involute Gears // version 1.1 by Leemon Baird, 2011, Leemon@Leemon.com http://www.thingiverse.com/thing:5505""" # ported by Alain Pelletier January 2022 @@ -1520,9 +1512,9 @@ class CamCurveGear(bpy.types.Operator): ) pressure_angle: FloatProperty( name="Pressure Angle", - default=math.radians(20), + default=radians(20), min=0.001, - max=math.pi/2, + max=pi/2, precision=4, subtype="ANGLE", unit="ROTATION", diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index 1819ffbb9..bba928244 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -18,6 +18,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** +from math import pi + +from Equation import Expression +import numpy as np import bpy from bpy.props import ( @@ -27,10 +31,7 @@ StringProperty, ) -from . import utils, parametric -import math -from Equation import Expression -import numpy as np +from . import parametric class CamSineCurve(bpy.types.Operator): @@ -133,9 +134,9 @@ class CamSineCurve(bpy.types.Operator): ) wave_angle_offset: FloatProperty( name="angle offset for multiple waves", - default=math.pi/2, - min=-200*math.pi, - max=200*math.pi, + default=pi/2, + min=-200*pi, + max=200*pi, precision=4, unit="ROTATION", ) @@ -184,7 +185,7 @@ def f(t, offset: float = 0.0, angle_offset: float = 0.0): return c for i in range(self.wave_amount): - angle_off = self.wave_angle_offset*self.period*i/(2*math.pi) + angle_off = self.wave_angle_offset*self.period*i/(2*pi) parametric.create_parametric_curve(f, offset=self.wave_distance*i, min=self.mint, max=self.maxt, use_cubic=True, iterations=self.iteration, angle_offset=angle_off) @@ -381,7 +382,7 @@ def execute(self, context): Rpr = round(R + r, 6) # R +r Rpror = round(Rpr / r, 6) # (R+r)/r Rmror = round(Rmr / r, 6) # (R-r)/r - maxangle = 2 * math.pi * \ + maxangle = 2 * pi * \ ((np.lcm(round(self.R * 1000), round(self.r * 1000)) / (R * 1000))) if self.typecurve == "hypo": @@ -487,12 +488,12 @@ def f(t, offset: float = 0.0): def triangle(i, T, A): - s = str(A*8/(math.pi**2))+'*(' + s = str(A*8/(pi**2))+'*(' for n in range(i): if n % 2 != 0: e = (n-1)/2 a = round(((-1)**e)/(n**2), 8) - b = round(n*math.pi/(T/2), 8) + b = round(n*pi/(T/2), 8) if n > 1: s += '+' s += str(a) + "*sin("+str(b)+"*t) " diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index f18fef6e3..96c7ec669 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -20,7 +20,13 @@ # ***** END GPL LICENCE BLOCK ***** # blender operators definitions are in this file. They mostly call the functions from utils.py +from math import ( + pi, + tan +) +import shapely +from shapely.geometry import LineString import bpy from bpy.props import ( @@ -29,29 +35,17 @@ FloatProperty, ) from bpy.types import Operator -from bpy_extras.io_utils import ImportHelper +from mathutils import Vector from . import ( - utils, - pack, polygon_utils_cam, simple, - gcodepath, - bridges, - parametric, - gcodeimportparser, - joinery + utils, ) -import shapely -from shapely.geometry import Point, LineString, Polygon -import mathutils -import math -from Equation import Expression -import numpy as np # boolean operations for curve objects -class CamCurveBoolean(bpy.types.Operator): +class CamCurveBoolean(Operator): """perform Boolean operation on two or more curves""" bl_idname = "object.curve_boolean" bl_label = "Curve Boolean" @@ -81,7 +75,7 @@ def execute(self, context): return {'CANCELLED'} -class CamCurveConvexHull(bpy.types.Operator): +class CamCurveConvexHull(Operator): """perform hull operation on single or multiple curves""" # by Alain Pelletier april 2021 bl_idname = "object.convex_hull" bl_label = "Convex Hull" @@ -97,7 +91,7 @@ def execute(self, context): # intarsion or joints -class CamCurveIntarsion(bpy.types.Operator): +class CamCurveIntarsion(Operator): """makes curve cuttable both inside and outside, for intarsion and joints""" bl_idname = "object.curve_intarsion" bl_label = "Intarsion" @@ -219,7 +213,7 @@ def execute(self, context): # intarsion or joints -class CamCurveOvercuts(bpy.types.Operator): +class CamCurveOvercuts(Operator): """Adds overcuts for slots""" bl_idname = "object.curve_overcuts" bl_label = "Add Overcuts" @@ -235,7 +229,7 @@ class CamCurveOvercuts(bpy.types.Operator): ) threshold: FloatProperty( name="threshold", - default=math.pi / 2 * .99, + default=pi / 2 * .99, min=-3.14, max=3.14, precision=4, @@ -280,11 +274,11 @@ def execute(self, context): if i2 == len(c.coords): i2 = 0 - v1 = mathutils.Vector( - co) - mathutils.Vector(c.coords[i1]) + v1 = Vector( + co) - Vector(c.coords[i1]) v1 = v1.xy # Vector((v1.x,v1.y,0)) - v2 = mathutils.Vector( - c.coords[i2]) - mathutils.Vector(co) + v2 = Vector( + c.coords[i2]) - Vector(co) v2 = v2.xy # v2 = Vector((v2.x,v2.y,0)) if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) @@ -293,18 +287,18 @@ def execute(self, context): if self.invert: # and ci>0: sign *= -1 if (sign < 0 and a < -self.threshold) or (sign > 0 and a > self.threshold): - p = mathutils.Vector((co[0], co[1])) + p = Vector((co[0], co[1])) v1.normalize() v2.normalize() v = v1 - v2 v.normalize() p = p - v * diameter / 2 - if abs(a) < math.pi / 2: + if abs(a) < pi / 2: shape = utils.Circle(diameter / 2, 64) shape = shapely.affinity.translate( shape, p.x, p.y) else: - l = math.tan(a / 2) * diameter / 2 + l = tan(a / 2) * diameter / 2 p1 = p - sign * v * l l = shapely.geometry.LineString((p, p1)) shape = l.buffer( @@ -327,7 +321,7 @@ def execute(self, context): # Overcut type B -class CamCurveOvercutsB(bpy.types.Operator): +class CamCurveOvercutsB(Operator): """Adds overcuts for slots""" bl_idname = "object.curve_overcuts_b" bl_label = "Add Overcuts-B" @@ -356,7 +350,7 @@ class CamCurveOvercutsB(bpy.types.Operator): ) threshold: FloatProperty( name="Max Inside Angle", - default=math.pi / 2, + default=pi / 2, min=-3.14, max=3.14, description='The maximum angle to be considered as an inside corner', @@ -397,10 +391,10 @@ def execute(self, context): insideCorners = [] diameter = self.diameter * 1.002 # make bit size slightly larger to allow cutter radius = diameter / 2 - anglethreshold = math.pi - self.threshold - centerv = mathutils.Vector((0, 0)) - extendedv = mathutils.Vector((0, 0)) - pos = mathutils.Vector((0, 0)) + anglethreshold = pi - self.threshold + centerv = Vector((0, 0)) + extendedv = Vector((0, 0)) + pos = Vector((0, 0)) sign = -1 if self.do_invert else 1 isTBone = self.style == 'TBONE' # indexes in insideCorner tuple @@ -411,7 +405,7 @@ def addOvercut(a): # move the overcut shape center position 1 radius in direction v pos -= centerv * radius print("abs(a)", abs(a)) - if abs(a) <= math.pi / 2 + 0.0001: + if abs(a) <= pi / 2 + 0.0001: print("<=pi/2") shape = utils.Circle(radius, 64) shape = shapely.affinity.translate(shape, pos.x, pos.y) @@ -440,7 +434,7 @@ def setCenterOffset(a): nonlocal centerv, extendedv, sign centerv = v1 - v2 centerv.normalize() - extendedv = centerv * math.tan(a / 2) * -sign + extendedv = centerv * tan(a / 2) * -sign addOvercut(a) def getCorner(idx, offset): @@ -482,10 +476,10 @@ def getCornerDelta(curidx, nextidx): if i2 == len(c.coords): i2 = 0 - v1 = mathutils.Vector( - co).xy - mathutils.Vector(c.coords[i1]).xy - v2 = mathutils.Vector( - c.coords[i2]).xy - mathutils.Vector(co).xy + v1 = Vector( + co).xy - Vector(c.coords[i1]).xy + v2 = Vector( + c.coords[i2]).xy - Vector(co).xy if not v1.length == 0 and not v2.length == 0: a = v1.angle_signed(v2) @@ -505,7 +499,7 @@ def getCornerDelta(curidx, nextidx): if insideCornerFound: # an inside corner with an overcut has been found # which means a new side has been found - pos = mathutils.Vector((co[0], co[1])) + pos = Vector((co[0], co[1])) v1.normalize() v2.normalize() # figure out which direction vector to use @@ -567,7 +561,7 @@ def getCornerDelta(curidx, nextidx): if getCornerDelta(prevCorner[IDX], idx) == 3: # check if they share the same edge a1 = v1.angle_signed( - prevCorner[V2]) * 180.0 / math.pi + prevCorner[V2]) * 180.0 / pi print('third won', a1) if a1 < -135 or a1 > 135: setOtherEdge(-v2, -v1, a) @@ -577,7 +571,7 @@ def getCornerDelta(curidx, nextidx): if getCornerDelta(idx, nextCorner[IDX]) == 3: # check if they share the same edge a1 = v2.angle_signed( - nextCorner[V1]) * 180.0 / math.pi + nextCorner[V1]) * 180.0 / pi print('fourth won', a1) if a1 < -135 or a1 > 135: setOtherEdge(v1, v2, a) @@ -597,7 +591,7 @@ def getCornerDelta(curidx, nextidx): return {'FINISHED'} -class CamCurveRemoveDoubles(bpy.types.Operator): +class CamCurveRemoveDoubles(Operator): """curve remove doubles - warning, removes beziers!""" bl_idname = "object.curve_remove_doubles" bl_label = "C-Remove doubles" @@ -629,7 +623,7 @@ def execute(self, context): return {'FINISHED'} -class CamMeshGetPockets(bpy.types.Operator): +class CamMeshGetPockets(Operator): """Detect pockets in a mesh and extract them as curves""" bl_idname = "object.mesh_get_pockets" bl_label = "Get pocket surfaces" @@ -740,7 +734,7 @@ def execute(self, context): # this operator finds the silhouette of objects(meshes, curves just get converted) and offsets it. -class CamOffsetSilhouete(bpy.types.Operator): +class CamOffsetSilhouete(Operator): """Curve offset operation """ bl_idname = "object.silhouete_offset" bl_label = "Silhouete offset" @@ -815,7 +809,7 @@ def execute(self, context): # Finds object silhouette, usefull for meshes, since with curves it's not needed. -class CamObjectSilhouete(bpy.types.Operator): +class CamObjectSilhouete(Operator): """Object silhouete """ bl_idname = "object.silhouete" bl_label = "Object silhouete" diff --git a/scripts/addons/cam/engine.py b/scripts/addons/cam/engine.py new file mode 100644 index 000000000..8e426006b --- /dev/null +++ b/scripts/addons/cam/engine.py @@ -0,0 +1,74 @@ +from bl_ui.properties_material import ( + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_settings, + EEVEE_MATERIAL_PT_surface, +) +import bpy +from bpy.types import RenderEngine + +from .ui_panels.area import CAM_AREA_Panel +from .ui_panels.chains import CAM_CHAINS_Panel +from .ui_panels.cutter import CAM_CUTTER_Panel +from .ui_panels.feedrate import CAM_FEEDRATE_Panel +from .ui_panels.gcode import CAM_GCODE_Panel +from .ui_panels.info import CAM_INFO_Panel +from .ui_panels.interface import CAM_INTERFACE_Panel +from .ui_panels.machine import CAM_MACHINE_Panel +from .ui_panels.material import CAM_MATERIAL_Panel +from .ui_panels.movement import CAM_MOVEMENT_Panel +from .ui_panels.op_properties import CAM_OPERATION_PROPERTIES_Panel +from .ui_panels.operations import CAM_OPERATIONS_Panel +from .ui_panels.optimisation import CAM_OPTIMISATION_Panel +from .ui_panels.pack import CAM_PACK_Panel +from .ui_panels.slice import CAM_SLICE_Panel + + +class BLENDERCAM_ENGINE(RenderEngine): + bl_idname = "BLENDERCAM_RENDER" + bl_label = "Cam" + bl_use_eevee_viewport = True + + +def get_panels(): + exclude_panels = { + 'RENDER_PT_eevee_performance', + 'RENDER_PT_opengl_sampling', + 'RENDER_PT_opengl_lighting', + 'RENDER_PT_opengl_color', + 'RENDER_PT_opengl_options', + 'RENDER_PT_simplify', + 'RENDER_PT_gpencil', + 'RENDER_PT_freestyle', + 'RENDER_PT_color_management', + 'MATERIAL_PT_viewport', + 'MATERIAL_PT_lineart', + } + + panels = [ + EEVEE_MATERIAL_PT_context_material, + EEVEE_MATERIAL_PT_surface, + EEVEE_MATERIAL_PT_settings, + + CAM_INTERFACE_Panel, + CAM_CHAINS_Panel, + CAM_OPERATIONS_Panel, + CAM_INFO_Panel, + CAM_MATERIAL_Panel, + CAM_OPERATION_PROPERTIES_Panel, + CAM_OPTIMISATION_Panel, + CAM_AREA_Panel, + CAM_MOVEMENT_Panel, + CAM_FEEDRATE_Panel, + CAM_CUTTER_Panel, + CAM_GCODE_Panel, + CAM_MACHINE_Panel, + CAM_PACK_Panel, + CAM_SLICE_Panel, + ] + + for panel in bpy.types.Panel.__subclasses__(): + if hasattr(panel, 'COMPAT_ENGINES') and 'BLENDER_RENDER' in panel.COMPAT_ENGINES: + if panel.__name__ not in exclude_panels: + panels.append(panel) + + return panels diff --git a/scripts/addons/cam/gcodeimportparser.py b/scripts/addons/cam/gcodeimportparser.py index 81dd9beca..ee09d06ac 100644 --- a/scripts/addons/cam/gcodeimportparser.py +++ b/scripts/addons/cam/gcodeimportparser.py @@ -17,14 +17,12 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -import bpy -import bmesh import math -import re import numpy as np +import bpy + np.set_printoptions(suppress=True) # suppress scientific notation in subdivide functions linspace diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index 5630c63d3..19b0f0c9d 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -20,44 +20,61 @@ # ***** END GPL LICENCE BLOCK ***** # here is the Gcode generaton - -import bpy +from math import ( + ceil, + floor, + pi, + sqrt +) import time -import mathutils -import math -from math import * -from mathutils import * import numpy +from shapely.geometry import polygon as spolygon -from . import chunk -from .chunk import * -from .utils import USE_PROFILER - -from . import collision -from .collision import * - -from . import simple -from .simple import * - -from .async_op import progress_async - -from . import bridges -from .bridges import * +import bpy +from mathutils import Euler, Vector -from . import utils from . import strategy - -from . import pattern -from .pattern import * - -from . import polygon_utils_cam -from .polygon_utils_cam import * - -from . import image_utils -from .image_utils import * -from .opencamlib.opencamlib import * +from .async_op import progress_async +from .bridges import useBridges +from .cam_chunk import ( + curveToChunks, + chunksRefine, + limitChunks, + chunksCoherency, + shapelyToChunks, + parentChildDist, +) +from .image_utils import ( + crazyStrokeImageBinary, + getOffsetImageCavities, + imageToShapely, + prepareArea, +) from .nc import iso +from .opencamlib.opencamlib import oclGetWaterline +from .pattern import ( + getPathPattern, + getPathPattern4axis +) +from .simple import ( + progress, + safeFileName, + strInUnits +) +from .utils import ( + cleanupIndexed, + connectChunksLow, + getAmbient, + getBounds, + getOperationSilhouete, + getOperationSources, + prepareIndexed, + sampleChunks, + sampleChunksNAxis, + sortChunks, + USE_PROFILER, +) def pointonline(a, b, c, tolerence): @@ -278,12 +295,12 @@ def startNewFile(): if o.enable_A: if o.rotation_A == 0: o.rotation_A = 0.0001 - c.rapid(a=o.rotation_A * 180 / math.pi) + c.rapid(a=o.rotation_A * 180 / pi) if o.enable_B: if o.rotation_B == 0: o.rotation_B = 0.0001 - c.rapid(a=o.rotation_B * 180 / math.pi) + c.rapid(a=o.rotation_B * 180 / pi) c.write('\n') c.flush_nc() @@ -538,7 +555,7 @@ async def getPath(context, operation): # should do all path calculations. operation.update_ambient_tag = True operation.update_bullet_collision_tag = True - utils.getOperationSources(operation) + getOperationSources(operation) operation.info.warnings = '' checkMemoryLimit(operation) @@ -600,7 +617,7 @@ def getChangeData(o): def checkMemoryLimit(o): - # utils.getBounds(o) + # getBounds(o) sx = o.max.x - o.min.x sy = o.max.y - o.min.y resx = sx / o.optimisation.pixsize @@ -610,7 +627,7 @@ def checkMemoryLimit(o): # print('co se to deje') if res > limit: ratio = (res / limit) - o.optimisation.pixsize = o.optimisation.pixsize * math.sqrt(ratio) + o.optimisation.pixsize = o.optimisation.pixsize * sqrt(ratio) o.info.warnings += f"Memory limit: sampling resolution reduced to {o.optimisation.pixsize:.2e}\n" print('changing sampling resolution to %f' % o.optimisation.pixsize) @@ -620,7 +637,7 @@ def checkMemoryLimit(o): async def getPath3axis(context, operation): s = bpy.context.scene o = operation - utils.getBounds(o) + getBounds(o) tw = time.time() if o.strategy == 'CUTOUT': @@ -642,15 +659,15 @@ async def getPath3axis(context, operation): ob = bpy.data.objects[o.curve_object] pathSamples.extend(curveToChunks(ob)) # sort before sampling - pathSamples = await utils.sortChunks(pathSamples, o) + pathSamples = await sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) elif o.strategy == 'PENCIL': await prepareArea(o) - utils.getAmbient(o) + getAmbient(o) pathSamples = getOffsetImageCavities(o, o.offset_image) pathSamples = limitChunks(pathSamples, o) # sort before sampling - pathSamples = await utils.sortChunks(pathSamples, o) + pathSamples = await sortChunks(pathSamples, o) elif o.strategy == 'CRAZY': await prepareArea(o) # pathSamples = crazyStrokeImage(o) @@ -660,21 +677,21 @@ async def getPath3axis(context, operation): pathSamples = crazyStrokeImageBinary(o, millarea, avoidarea) ##### - pathSamples = await utils.sortChunks(pathSamples, o) + pathSamples = await sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) else: if o.strategy == 'OUTLINEFILL': - utils.getOperationSilhouete(o) + getOperationSilhouete(o) pathSamples = getPathPattern(o) if o.strategy == 'OUTLINEFILL': - pathSamples = await utils.sortChunks(pathSamples, o) + pathSamples = await sortChunks(pathSamples, o) # have to be sorted once before, because of the parenting inside of samplechunks if o.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES']: - pathSamples = await utils.connectChunksLow(pathSamples, o) + pathSamples = await connectChunksLow(pathSamples, o) # print (minz) @@ -682,7 +699,7 @@ async def getPath3axis(context, operation): layers = strategy.getLayers(o, o.maxz, o.min.z) print("SAMPLE", o.name) - chunks.extend(await utils.sampleChunks(o, pathSamples, layers)) + chunks.extend(await sampleChunks(o, pathSamples, layers)) print("SAMPLE OK") if o.strategy == 'PENCIL': # and bpy.app.debug_value==-3: chunks = chunksCoherency(chunks) @@ -691,9 +708,9 @@ async def getPath3axis(context, operation): # and not o.movement.parallel_step_back: if o.strategy in ['PARALLEL', 'CROSS', 'PENCIL', 'OUTLINEFILL']: print('sorting') - chunks = await utils.sortChunks(chunks, o) + chunks = await sortChunks(chunks, o) if o.strategy == 'OUTLINEFILL': - chunks = await utils.connectChunksLow(chunks, o) + chunks = await connectChunksLow(chunks, o) if o.movement.ramp: for ch in chunks: ch.rampZigZag(ch.zstart, None, o) @@ -711,7 +728,7 @@ async def getPath3axis(context, operation): strategy.chunksToMesh(chunks, o) elif o.strategy == 'WATERLINE' and o.optimisation.use_opencamlib: - utils.getAmbient(o) + getAmbient(o) chunks = [] await oclGetWaterline(o, chunks) chunks = limitChunks(chunks, o) @@ -729,7 +746,7 @@ async def getPath3axis(context, operation): await prepareArea(o) layerstep = 1000000000 if o.use_layers: - layerstep = math.floor(o.stepdown / o.slice_detail) + layerstep = floor(o.stepdown / o.slice_detail) if layerstep == 0: layerstep = 1 @@ -743,7 +760,7 @@ async def getPath3axis(context, operation): layerstepinc = 0 slicesfilled = 0 - utils.getAmbient(o) + getAmbient(o) for h in range(0, nslices): layerstepinc += 1 @@ -798,7 +815,7 @@ async def getPath3axis(context, operation): # project paths TODO: path projection during waterline is not working if o.waterline_project: nchunks = chunksRefine(nchunks, o) - nchunks = await utils.sampleChunks(o, nchunks, layers) + nchunks = await sampleChunks(o, nchunks, layers) nchunks = limitChunks(nchunks, o, force=True) ######################### @@ -850,7 +867,7 @@ async def getPath3axis(context, operation): o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CW'): for chunk in slicechunks: chunk.reverse() - slicechunks = await utils.sortChunks(slicechunks, o) + slicechunks = await sortChunks(slicechunks, o) if topdown: slicechunks.reverse() # project chunks in between @@ -870,7 +887,7 @@ async def getPath3axis(context, operation): async def getPath4axis(context, operation): o = operation - utils.getBounds(o) + getBounds(o) if o.strategy4axis in ['PARALLELR', 'PARALLEL', 'HELIX', 'CROSS']: path_samples = getPathPattern4axis(o) @@ -879,5 +896,5 @@ async def getPath4axis(context, operation): layers = strategy.getLayers(o, 0, depth) - chunks.extend(await utils.sampleChunksNAxis(o, path_samples, layers)) + chunks.extend(await sampleChunksNAxis(o, path_samples, layers)) strategy.chunksToMesh(chunks, o) diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 34a83b2bf..074f1f5d0 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -19,31 +19,65 @@ # # ***** END GPL LICENCE BLOCK ***** -import math -import numpy +from math import ( + acos, + ceil, + cos, + floor, + pi, + radians, + sin, + tan, +) import os import random import time +import numpy + +import bpy import curve_simplify -import mathutils -from mathutils import * - -from . import simple -from .simple import * -from . import chunk -from .chunk import * -from . import simulation +from mathutils import ( + Euler, + Vector, +) + +from .simple import ( + progress, + getCachePath, +) +from .cam_chunk import ( + parentChildDist, + camPathChunkBuilder, + camPathChunk, + chunksToShapely, +) from .async_op import progress_async +from .numba_wrapper import ( + jit, + prange, +) -from .numba_wrapper import jit, prange + +def numpysave(a, iname): + inamebase = bpy.path.basename(iname) + + i = numpytoimage(a, inamebase) + + r = bpy.context.scene.render + + r.image_settings.file_format = 'OPEN_EXR' + r.image_settings.color_mode = 'BW' + r.image_settings.color_depth = '32' + + i.save_render(iname) def getCircle(r, z): car = numpy.full(shape=(r*2, r*2), fill_value=-10, dtype=numpy.double) res = 2 * r m = r - v = mathutils.Vector((0, 0, 0)) + v = Vector((0, 0, 0)) for a in range(0, res): v.x = (a + 0.5 - m) for b in range(0, res): @@ -57,7 +91,7 @@ def getCircleBinary(r): car = numpy.full(shape=(r*2, r*2), fill_value=False, dtype=bool) res = 2 * r m = r - v = mathutils.Vector((0, 0, 0)) + v = Vector((0, 0, 0)) for a in range(0, res): v.x = (a + 0.5 - m) for b in range(0, res): @@ -70,20 +104,6 @@ def getCircleBinary(r): # get cutters for the z-buffer image method -def numpysave(a, iname): - inamebase = bpy.path.basename(iname) - - i = numpytoimage(a, inamebase) - - r = bpy.context.scene.render - - r.image_settings.file_format = 'OPEN_EXR' - r.image_settings.color_mode = 'BW' - r.image_settings.color_depth = '32' - - i.save_render(iname) - - def numpytoimage(a, iname): print('numpy to image', iname) t = time.time() @@ -145,7 +165,7 @@ async def offsetArea(o, samples): minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z sourceArray = samples - cutterArray = simulation.getCutterArray(o, o.optimisation.pixsize) + cutterArray = getCutterArray(o, o.optimisation.pixsize) # progress('image size', sourceArray.shape) @@ -162,8 +182,8 @@ async def offsetArea(o, samples): sourceArray = -sourceArray + minz comparearea = o.offset_image[m: width - cwidth + m, m:height - cwidth + m] # i=0 - cutterArrayNan = np.where(cutterArray > -10, cutterArray, - np.full(cutterArray.shape, np.nan)) + cutterArrayNan = numpy.where(cutterArray > -10, cutterArray, + numpy.full(cutterArray.shape, numpy.nan)) for y in range(0, 10): y1 = (y * comparearea.shape[1])//10 y2 = ((y+1) * comparearea.shape[1])//10 @@ -187,7 +207,7 @@ def dilateAr(ar, cycles): def getOffsetImageCavities(o, i): # for pencil operation mainly """detects areas in the offset image which are 'cavities' - the curvature changes.""" # i=numpy.logical_xor(lastislice , islice) - simple.progress('detect corners in the offset image') + progress('detect corners in the offset image') vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] # if bpy.app.debug_value==2: @@ -196,19 +216,19 @@ def getOffsetImageCavities(o, i): # for pencil operation mainly if 1: # this is newer strategy, finds edges nicely, but pff.going exacty on edge, # it has tons of spikes and simply is not better than the old one - iname = simple.getCachePath(o) + '_pencilthres.exr' + iname = getCachePath(o) + '_pencilthres.exr' # numpysave(ar,iname)#save for comparison before chunks = imageEdgeSearch_online(o, ar, i) - iname = simple.getCachePath(o) + '_pencilthres_comp.exr' + iname = getCachePath(o) + '_pencilthres_comp.exr' print("new pencil strategy") # ##crop pixels that are on outer borders for chi in range(len(chunks) - 1, -1, -1): chunk = chunks[chi] chunk.clip_points(o.min.x, o.max.x, o.min.y, o.max.y) - # for si in range(len(chunk.points) - 1, -1, -1): - # if not (o.min.x < chunk.points[si][0] < o.max.x and o.min.y < chunk.points[si][1] < o.max.y): - # chunk.points.pop(si) + # for si in range(len(points) - 1, -1, -1): + # if not (o.min.x < points[si][0] < o.max.x and o.min.y < points[si][1] < o.max.y): + # points.pop(si) if chunk.count() < 2: chunks.pop(chi) @@ -247,7 +267,7 @@ def imageEdgeSearch_online(o, ar, zimage): if perc != int(100 - 100 * totpix / startpix): perc = int(100 - 100 * totpix / startpix) - simple.progress('pencil path searching', perc) + progress('pencil path searching', perc) # progress('simulation ',int(100*i/l)) success = False testangulardistance = 0 # distance from initial direction in the list of direction @@ -272,7 +292,7 @@ def imageEdgeSearch_online(o, ar, zimage): print(testvect) print(itests) else: - # nchunk.append([xs,ys])#for debugging purpose + # nappend([xs,ys])#for debugging purpose # ar.shape[0] test_direction = last_direction if testleftright: @@ -353,7 +373,7 @@ async def crazyPath(o): o.millimage = numpy.full(shape=(resx, resy), fill_value=0., dtype=numpy.float) # getting inverted cutter - o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) + o.cutterArray = -getCutterArray(o, o.optimisation.simulation_detail) def buildStroke(start, end, cutterArray): @@ -768,7 +788,7 @@ def crazyStrokeImageBinary(o, ar, avoidar): andar = numpy.logical_and(ar, numpy.logical_not(avoidar)) indices = andar.nonzero() if len(nchunk.points) > 1: - chunk.parentChildDist([nchunk], chunks, o, distance=r) + parentChildDist([nchunk], chunks, o, distance=r) chunk_builders.append(nchunk) if totpix > startpix * 0.001: @@ -824,7 +844,7 @@ def crazyStrokeImageBinary(o, ar, avoidar): print(totaltests) i = 0 if len(nchunk.points) > 1: - chunk.parentChildDist([nchunk], chunks, o, distance=r) + parentChildDist([nchunk], chunks, o, distance=r) chunk_builders.append(nchunk) for ch in chunk_builders: @@ -1080,7 +1100,7 @@ def _restore_render_settings(pairs, properties): def renderSampleImage(o): t = time.time() - simple.progress('getting zbuffer') + progress('getting zbuffer') # print(o.zbuffer_image) o.update_offsetimage_tag = True if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION': @@ -1089,8 +1109,8 @@ def renderSampleImage(o): sx = o.max.x - o.min.x sy = o.max.y - o.min.y - resx = math.ceil(sx / o.optimisation.pixsize) + 2 * o.borderwidth - resy = math.ceil(sy / o.optimisation.pixsize) + 2 * o.borderwidth + resx = ceil(sx / o.optimisation.pixsize) + 2 * o.borderwidth + resy = ceil(sy / o.optimisation.pixsize) + 2 * o.borderwidth if not o.update_zbufferimage_tag and len(o.zbuffer_image) == resx and len(o.zbuffer_image[0]) == resy: # if we call this accidentally in more functions, which currently happens... @@ -1228,7 +1248,7 @@ def renderSampleImage(o): #o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth) o.optimisation.pixsize = o.source_image_size_x / i.size[0] - simple.progress('pixel size in the image source', o.optimisation.pixsize) + progress('pixel size in the image source', o.optimisation.pixsize) rawimage = imagetonumpy(i) maxa = numpy.max(rawimage) @@ -1267,7 +1287,7 @@ def renderSampleImage(o): print('min image ', numpy.min(a)) o.zbuffer_image = a # progress('got z buffer also with conversion in:') - simple.progress(time.time() - t) + progress(time.time() - t) # progress(a) o.update_zbufferimage_tag = False @@ -1281,7 +1301,7 @@ async def prepareArea(o): renderSampleImage(o) samples = o.zbuffer_image - iname = simple.getCachePath(o) + '_off.exr' + iname = getCachePath(o) + '_off.exr' if not o.update_offsetimage_tag: progress('loading offset image') @@ -1296,3 +1316,98 @@ async def prepareArea(o): samples = numpy.maximum(samples, o.min.z - 0.00001) await offsetArea(o, samples) numpysave(o.offset_image, iname) + + +def getCutterArray(operation, pixsize): + type = operation.cutter_type + # print('generating cutter') + r = operation.cutter_diameter / 2 + operation.skin # /operation.pixsize + res = ceil((r * 2) / pixsize) + m = res / 2.0 + car = numpy.full(shape=(res, res), fill_value=-10.0, dtype=float) + + v = Vector((0, 0, 0)) + ps = pixsize + if type == 'END': + for a in range(0, res): + v.x = (a + 0.5 - m) * ps + for b in range(0, res): + v.y = (b + 0.5 - m) * ps + if v.length <= r: + car.itemset((a, b), 0) + elif type == 'BALL' or type == 'BALLNOSE': + for a in range(0, res): + v.x = (a + 0.5 - m) * ps + for b in range(0, res): + v.y = (b + 0.5 - m) * ps + if v.length <= r: + z = sin(acos(v.length / r)) * r - r + car.itemset((a, b), z) # [a,b]=z + + elif type == 'VCARVE': + angle = operation.cutter_tip_angle + s = tan(pi * (90 - angle / 2) / 180) # angle in degrees + for a in range(0, res): + v.x = (a + 0.5 - m) * ps + for b in range(0, res): + v.y = (b + 0.5 - m) * ps + if v.length <= r: + z = (-v.length * s) + car.itemset((a, b), z) + elif type == 'CYLCONE': + angle = operation.cutter_tip_angle + cyl_r = operation.cylcone_diameter/2 + s = tan(pi * (90 - angle / 2) / 180) # angle in degrees + for a in range(0, res): + v.x = (a + 0.5 - m) * ps + for b in range(0, res): + v.y = (b + 0.5 - m) * ps + if v.length <= r: + z = (-(v.length - cyl_r) * s) + if v.length <= cyl_r: + z = 0 + car.itemset((a, b), z) + elif type == 'BALLCONE': + angle = radians(operation.cutter_tip_angle)/2 + ball_r = operation.ball_radius + cutter_r = operation.cutter_diameter / 2 + conedepth = (cutter_r - ball_r)/tan(angle) + Ball_R = ball_r/cos(angle) + D_ofset = ball_r * tan(angle) + s = tan(pi/2-angle) + for a in range(0, res): + v.x = (a + 0.5 - m) * ps + for b in range(0, res): + v.y = (b + 0.5 - m) * ps + if v.length <= cutter_r: + z = -(v.length - ball_r) * s - Ball_R + D_ofset + if v.length <= ball_r: + z = sin(acos(v.length / Ball_R)) * Ball_R - Ball_R + car.itemset((a, b), z) + elif type == 'CUSTOM': + cutob = bpy.data.objects[operation.cutter_object_name] + scale = ((cutob.dimensions.x / cutob.scale.x) / 2) / r # + # print(cutob.scale) + vstart = Vector((0, 0, -10)) + vend = Vector((0, 0, 10)) + print('sampling custom cutter') + maxz = -1 + for a in range(0, res): + vstart.x = (a + 0.5 - m) * ps * scale + vend.x = vstart.x + + for b in range(0, res): + vstart.y = (b + 0.5 - m) * ps * scale + vend.y = vstart.y + v = vend - vstart + c = cutob.ray_cast(vstart, v, distance=1.70141e+38) + if c[3] != -1: + z = -c[1][2] / scale + # print(c) + if z > -9: + # print(z) + if z > maxz: + maxz = z + car.itemset((a, b), z) + car -= maxz + return car diff --git a/scripts/addons/cam/involute_gear.py b/scripts/addons/cam/involute_gear.py index c823d6cc2..bca9579c0 100644 --- a/scripts/addons/cam/involute_gear.py +++ b/scripts/addons/cam/involute_gear.py @@ -62,35 +62,34 @@ //and be separated by the sum of their pitch radii, which can be found with pitch_radius(). """ # ported to Blendercam by Alain Pelletier Jan 2022 +from math import ( + acos, + cos, + degrees, + pi, + sin, + sqrt +) +from shapely.geometry import Polygon import bpy -from bpy.props import * -from bpy.types import Operator from . import ( - utils, - polygon_utils_cam, simple, + utils, ) -import shapely -from shapely.geometry import ( - Point, - LineString, - Polygon -) -import mathutils -import math - # convert gear_polar to cartesian coordinates + + def gear_polar(r, theta): - return r * math.sin(theta), r * math.cos(theta) + return r * sin(theta), r * cos(theta) # unwind a string this many degrees to go from radius r1 to radius r2 def gear_iang(r1, r2): - return math.sqrt((r2 / r1) * (r2 / r1) - 1) - math.acos(r1 / r2) + return sqrt((r2 / r1) * (r2 / r1) - 1) - acos(r1 / r2) # radius a fraction f up the curved side of the tooth @@ -113,10 +112,9 @@ def gear_q6(b, s, t, d): def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, pressure_angle=0.3488, clearance=0.0, backlash=0.0, rim_size=0.0005, hub_diameter=0.006, spokes=4): simple.deselect() - pi = math.pi p = mm_per_tooth * number_of_teeth / pi / 2 # radius of pitch circle c = p + mm_per_tooth / pi - clearance # radius of outer circle - b = p * math.cos(pressure_angle) # radius of base circle + b = p * cos(pressure_angle) # radius of base circle r = p-(c-p)-clearance # radius of root circle t = mm_per_tooth / 2 - backlash / 2 # tooth thickness at pitch circle # angle to where involute meets base circle on each side of tooth @@ -152,7 +150,7 @@ def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, i = number_of_teeth while i > 1: simple.duplicate() - simple.rotate(2 * math.pi / number_of_teeth) + simple.rotate(2 * pi / number_of_teeth) i -= 1 simple.join_multiple('tooth') simple.active_name('_teeth') @@ -200,18 +198,17 @@ def gear(mm_per_tooth=0.003, number_of_teeth=5, hole_diameter=0.003175, name = 'gear-' + str(round(mm_per_tooth*1000, 1)) name += 'mm-pitch-' + str(number_of_teeth) - name += 'teeth-PA-' + str(round(math.degrees(pressure_angle), 1)) + name += 'teeth-PA-' + str(round(degrees(pressure_angle), 1)) simple.active_name(name) def rack(mm_per_tooth=0.01, number_of_teeth=11, height=0.012, pressure_angle=0.3488, backlash=0.0, hole_diameter=0.003175, tooth_per_hole=4): simple.deselect() - pi = math.pi mm_per_tooth *= 1000 a = mm_per_tooth / pi # addendum # tooth side is tilted so top/bottom corners move this amount - t = (a * math.sin(pressure_angle)) + t = (a * sin(pressure_angle)) a /= 1000 mm_per_tooth /= 1000 t /= 1000 @@ -245,5 +242,5 @@ def rack(mm_per_tooth=0.01, number_of_teeth=11, height=0.012, pressure_angle=0.3 simple.difference('_', '_tooth') name = 'rack-' + str(round(mm_per_tooth * 1000, 1)) - name += '-PA-' + str(round(math.degrees(pressure_angle), 1)) + name += '-PA-' + str(round(degrees(pressure_angle), 1)) simple.active_name(name) diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 85944c097..2077461aa 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -20,26 +20,26 @@ # ***** END GPL LICENCE BLOCK ***** # blender operators definitions are in this file. They mostly call the functions from utils.py +from math import ( + asin, + atan2, + degrees, + hypot, + pi, +) +from shapely.geometry import ( + LineString, + Point, +) import bpy -from bpy.types import Operator -from bpy_extras.io_utils import ImportHelper from . import ( - utils, - pack, - polygon_utils_cam, + puzzle_joinery, simple, - gcodepath, - bridges, - parametric, - puzzle_joinery + utils, ) -import shapely -from shapely.geometry import Point, LineString, Polygon -import mathutils -import math # boolean operations for curve objects @@ -76,10 +76,10 @@ def interlock_groove(length, thickness, finger_play, cx=0, cy=0, rotation=0): def interlock_twist(length, thickness, finger_play, cx=0, cy=0, rotation=0, percentage=0.5): mortise(length, thickness, finger_play, 0, 0, 0) simple.active_name("_tmp") - mortise(length * percentage, thickness, finger_play, 0, 0, math.pi / 2) + mortise(length * percentage, thickness, finger_play, 0, 0, pi / 2) simple.active_name("_tmp") - h = math.hypot(thickness, length * percentage) - oangle = math.degrees(math.asin(length * percentage / h)) + h = hypot(thickness, length * percentage) + oangle = degrees(asin(length * percentage / h)) bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Sector', Simple_startangle=90 + oangle, Simple_endangle=180 - oangle, Simple_radius=h / 2, use_cyclic_u=True, edit_mode=False) @@ -180,7 +180,7 @@ def vertical_finger(length, thickness, finger_play, amount): for i in range(amount): mortise(length, thickness, finger_play, 0, i * 2 * - length + length / 2, rotation=math.pi / 2) + length + length / 2, rotation=pi / 2) simple.active_name("_height_finger") simple.join_multiple("_height_finger") @@ -237,7 +237,7 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): # creates pockets pocket using mortise function for kerf bending dist = 3 * finger_width / 2 while dist < length: - mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) + mortise(height - 2 * finger_thick, pocket_width, 0, dist, 0, pi / 2) simple.active_name("_flex_pocket") dist += finger_width * 2 @@ -248,7 +248,7 @@ def make_flex_pocket(length, height, finger_thick, finger_width, pocket_width): def make_variable_flex_pocket(height, finger_thick, pocket_width, locations): # creates pockets pocket using mortise function for kerf bending for dist in locations: - mortise(height + 2 * finger_thick, pocket_width, 0, dist, 0, math.pi / 2) + mortise(height + 2 * finger_thick, pocket_width, 0, dist, 0, pi / 2) simple.active_name("_flex_pocket") simple.join_multiple("_flex_pocket") @@ -292,7 +292,7 @@ def create_flex_side(length, height, finger_thick, top_bottom=False): def angle(a, b): - return math.atan2(b[1] - a[1], b[0] - a[0]) + return atan2(b[1] - a[1], b[0] - a[0]) def angle_difference(a, b, c): @@ -326,8 +326,8 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, while distance <= pd: mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) - mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( - math.pi / 4)) # factor for tolerance for the finger + mad = (1 + 6 * min(mortise_angle_difference, pi / 4) / ( + pi / 4)) # factor for tolerance for the finger if base: mortise(finger_size, finger_thick, finger_tolerance * mad, distance, 0, 0) @@ -440,8 +440,8 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi while distance <= pd: mortise_angle = angle(oldp, p) mortise_angle_difference = abs(mortise_angle - old_mortise_angle) - mad = (1 + 6 * min(mortise_angle_difference, math.pi / 4) / ( - math.pi / 4)) # factor for tolerance for the finger + mad = (1 + 6 * min(mortise_angle_difference, pi / 4) / ( + pi / 4)) # factor for tolerance for the finger # move finger by the factor mad greater with larger angle difference distance += mad * finger_tolerance mortise_point = loop.interpolate(distance) @@ -469,7 +469,7 @@ def variable_finger(loop, loop_length, min_finger, finger_size, finger_thick, fi old_distance = distance old_mortise_point = mortise_point finger_sz = finger_size - next_angle_difference = math.pi + next_angle_difference = pi # adaptive finger length start while finger_sz > min_finger and next_angle_difference > adaptive: @@ -543,13 +543,13 @@ def distributed_interlock(loop, loop_length, finger_depth, finger_thick, finger_ if not_start: while distance <= pd and end_distance >= distance: if fixed_angle == 0: - groove_angle = angle(oldp, p) + math.pi / 2 + tangent + groove_angle = angle(oldp, p) + pi / 2 + tangent else: groove_angle = fixed_angle groove_point = loop.interpolate(distance) - print(j, "groove_angle", round(180 * groove_angle / math.pi), + print(j, "groove_angle", round(180 * groove_angle / pi), "distance", round(distance * 1000), "mm") single_interlock(finger_depth, finger_thick, finger_tolerance, groove_point.x, groove_point.y, groove_angle, type, twist_percentage=twist_percentage) diff --git a/scripts/addons/cam/machine_settings.py b/scripts/addons/cam/machine_settings.py new file mode 100644 index 000000000..b466dfc20 --- /dev/null +++ b/scripts/addons/cam/machine_settings.py @@ -0,0 +1,228 @@ +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, +) +from bpy.types import PropertyGroup + +from . import constants +from .utils import updateMachine + + +class machineSettings(PropertyGroup): + """stores all data for machines""" + # name = StringProperty(name="Machine Name", default="Machine") + post_processor: EnumProperty( + name='Post processor', + items=( + ('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), + ('MACH3', 'Mach3', 'default mach3'), + ('EMC', 'LinuxCNC - EMC2', + 'Linux based CNC control software - formally EMC2'), + ('FADAL', 'Fadal', 'Fadal VMC'), + ('GRBL', 'grbl', + 'optimized gcode for grbl firmware on Arduino with cnc shield'), + ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), + ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), + ('TNC151', 'Heidenhain TNC151', + 'Post Processor for the Heidenhain TNC151 machine'), + ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), + ('HM50', 'Hafco HM-50', 'Hafco HM-50'), + ('CENTROID', 'Centroid M40', 'Centroid M40'), + ('ANILAM', 'Anilam Crusader M', 'Anilam Crusader M'), + ('GRAVOS', 'Gravos', 'Gravos'), + ('WIN-PC', 'WinPC-NC', 'German CNC by Burkhard Lewetz'), + ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), + ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o') + ), + description='Post processor', + default='MACH3', + ) + # units = EnumProperty(name='Units', items = (('IMPERIAL', '')) + # position definitions: + use_position_definitions: BoolProperty( + name="Use position definitions", + description="Define own positions for op start, " + "toolchange, ending position", + default=False, + ) + starting_position: FloatVectorProperty( + name='Start position', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + mtc_position: FloatVectorProperty( + name='MTC position', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + ending_position: FloatVectorProperty( + name='End position', + default=(0, 0, 0), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + + working_area: FloatVectorProperty( + name='Work Area', + default=(0.500, 0.500, 0.100), + unit='LENGTH', + precision=constants.PRECISION, + subtype="XYZ", + update=updateMachine, + ) + feedrate_min: FloatProperty( + name="Feedrate minimum /min", + default=0.0, + min=0.00001, + max=320000, + precision=constants.PRECISION, + unit='LENGTH', + ) + feedrate_max: FloatProperty( + name="Feedrate maximum /min", + default=2, + min=0.00001, + max=320000, + precision=constants.PRECISION, + unit='LENGTH', + ) + feedrate_default: FloatProperty( + name="Feedrate default /min", + default=1.5, + min=0.00001, + max=320000, + precision=constants.PRECISION, + unit='LENGTH', + ) + hourly_rate: FloatProperty( + name="Price per hour", + default=100, + min=0.005, + precision=2, + ) + + # UNSUPPORTED: + + spindle_min: FloatProperty( + name="Spindle speed minimum RPM", + default=5000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_max: FloatProperty( + name="Spindle speed maximum RPM", + default=30000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_default: FloatProperty( + name="Spindle speed default RPM", + default=15000, + min=0.00001, + max=320000, + precision=1, + ) + spindle_start_time: FloatProperty( + name="Spindle start delay seconds", + description='Wait for the spindle to start spinning before starting ' + 'the feeds , in seconds', + default=0, + min=0.0000, + max=320000, + precision=1, + ) + + axis4: BoolProperty( + name="#4th axis", + description="Machine has 4th axis", + default=0, + ) + axis5: BoolProperty( + name="#5th axis", + description="Machine has 5th axis", + default=0, + ) + + eval_splitting: BoolProperty( + name="Split files", + description="split gcode file with large number of operations", + default=True, + ) # split large files + split_limit: IntProperty( + name="Operations per file", + description="Split files with larger number of operations than this", + min=1000, + max=20000000, + default=800000, + ) + + # rotary_axis1 = EnumProperty(name='Axis 1', + # items=( + # ('X', 'X', 'x'), + # ('Y', 'Y', 'y'), + # ('Z', 'Z', 'z')), + # description='Number 1 rotational axis', + # default='X', update = updateOffsetImage) + + collet_size: FloatProperty( + name="#Collet size", + description="Collet size for collision detection", + default=33, + min=0.00001, + max=320000, + precision=constants.PRECISION, + unit="LENGTH", + ) + # exporter_start = StringProperty(name="exporter start", default="%") + + # post processor options + + output_block_numbers: BoolProperty( + name="output block numbers", + description="output block numbers ie N10 at start of line", + default=False, + ) + + start_block_number: IntProperty( + name="start block number", + description="the starting block number ie 10", + default=10, + ) + + block_number_increment: IntProperty( + name="block number increment", + description="how much the block number should " + "increment for the next line", + default=10, + ) + + output_tool_definitions: BoolProperty( + name="output tool definitions", + description="output tool definitions", + default=True, + ) + + output_tool_change: BoolProperty( + name="output tool change commands", + description="output tool change commands ie: Tn M06", + default=True, + ) + + output_g43_on_tool_change: BoolProperty( + name="output G43 on tool change", + description="output G43 on tool change line", + default=False, + ) diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index 39c3009cc..f63740ac5 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -20,48 +20,43 @@ # ***** END GPL LICENCE BLOCK ***** # blender operators definitions are in this file. They mostly call the functions from utils.py - +import os +import subprocess +import textwrap +import threading +import traceback import bpy from bpy.props import ( EnumProperty, StringProperty, - ) -from bpy_extras.io_utils import ImportHelper +from bpy.types import ( + Operator, +) -import subprocess -import os -import threading from . import ( - utils, + bridges, + gcodepath, pack, - polygon_utils_cam, simple, - gcodepath, - bridges, simulation, ) +from .async_op import ( + AsyncCancelledException, + AsyncOperatorMixin, + progress_async, +) +from .exception import CamException from .utils import ( - was_hidden_dict, - reload_paths, - isValid, + addMachineAreaObject, + getBoundsWorldspace, isChainValid, + isValid, + reload_paths, silhoueteOffset, - getBoundsWorldspace, - addMachineAreaObject, -) -from .async_op import ( - AsyncOperatorMixin, - AsyncCancelledException, + was_hidden_dict, ) -import shapely -import mathutils -import math -import textwrap -import traceback - -from .exception import * class threadCom: # object passed to threads to read background process stdout info @@ -118,7 +113,7 @@ def timer_update(context): o.outtext = tcom.lasttext # changes -class PathsBackground(bpy.types.Operator): +class PathsBackground(Operator): """calculate CAM paths in background. File has to be saved before.""" bl_idname = "object.calculate_cam_paths_background" bl_label = "Calculate CAM paths in background" @@ -153,7 +148,7 @@ def execute(self, context): return {'FINISHED'} -class KillPathsBackground(bpy.types.Operator): +class KillPathsBackground(Operator): """Remove CAM path processes in background.""" bl_idname = "object.kill_calculate_cam_paths_background" bl_label = "Kill background computation of an operation" @@ -239,7 +234,7 @@ async def _calc_path(operator, context): return {'FINISHED', True} -class CalculatePath(bpy.types.Operator, AsyncOperatorMixin): +class CalculatePath(Operator, AsyncOperatorMixin): """calculate CAM paths""" bl_idname = "object.calculate_cam_path" bl_label = "Calculate CAM paths" @@ -260,7 +255,7 @@ async def execute_async(self, context): return retval -class PathsAll(bpy.types.Operator): +class PathsAll(Operator): """calculate all CAM paths""" bl_idname = "object.calculate_cam_paths_all" bl_label = "Calculate all CAM paths" @@ -283,7 +278,7 @@ def draw(self, context): bpy.context.scene, "cam_operations") -class CamPackObjects(bpy.types.Operator): +class CamPackObjects(Operator): """calculate all CAM paths""" bl_idname = "object.cam_pack_objects" bl_label = "Pack curves on sheet" @@ -300,7 +295,7 @@ def draw(self, context): layout = self.layout -class CamSliceObjects(bpy.types.Operator): +class CamSliceObjects(Operator): """Slice a mesh object horizontally""" # warning, this is a separate and neglected feature, it's a mess - by now it just slices up the object. bl_idname = "object.cam_slice_objects" @@ -327,7 +322,7 @@ def getChainOperations(chain): return chop -class PathsChain(bpy.types.Operator, AsyncOperatorMixin): +class PathsChain(Operator, AsyncOperatorMixin): """calculate a chain and export the gcode alltogether. """ bl_idname = "object.calculate_cam_paths_chain" bl_label = "Calculate CAM paths in current chain and export chain gcode" @@ -357,7 +352,7 @@ async def execute_async(self, context): except Exception as e: print("FAIL", e) traceback.print_tb(e.__traceback__) - operator.report({'ERROR'}, str(e)) + self.report({'ERROR'}, str(e)) return {'FINISHED'} for o in chainops: @@ -366,7 +361,7 @@ async def execute_async(self, context): return {'FINISHED'} -class PathExportChain(bpy.types.Operator): +class PathExportChain(Operator): """calculate a chain and export the gcode alltogether. """ bl_idname = "object.cam_export_paths_chain" bl_label = "Export CAM paths in current chain as gcode" @@ -394,7 +389,7 @@ def execute(self, context): return {'FINISHED'} -class PathExport(bpy.types.Operator): +class PathExport(Operator): """Export gcode. Can be used only when the path object is present""" bl_idname = "object.cam_export" bl_label = "Export operation gcode" @@ -413,7 +408,7 @@ def execute(self, context): return {'FINISHED'} -class CAMSimulate(bpy.types.Operator, AsyncOperatorMixin): +class CAMSimulate(Operator, AsyncOperatorMixin): """simulate CAM operation this is performed by: creating an image, painting Z depth of the brush substractively. Works only for some operations, can not be used for 4-5 axis.""" @@ -449,7 +444,7 @@ def draw(self, context): bpy.context.scene, "cam_operations") -class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin): +class CAMSimulateChain(Operator, AsyncOperatorMixin): """simulate CAM chain, compared to single op simulation just writes into one image and thus enables to see how ops work together.""" bl_idname = "object.cam_simulate_chain" @@ -494,7 +489,7 @@ def draw(self, context): bpy.context.scene, "cam_operations") -class CamChainAdd(bpy.types.Operator): +class CamChainAdd(Operator): """Add new CAM chain""" bl_idname = "scene.cam_chain_add" bl_label = "Add new CAM chain" @@ -517,7 +512,7 @@ def execute(self, context): return {'FINISHED'} -class CamChainRemove(bpy.types.Operator): +class CamChainRemove(Operator): """Remove CAM chain""" bl_idname = "scene.cam_chain_remove" bl_label = "Remove CAM chain" @@ -535,7 +530,7 @@ def execute(self, context): return {'FINISHED'} -class CamChainOperationAdd(bpy.types.Operator): +class CamChainOperationAdd(Operator): """Add operation to chain""" bl_idname = "scene.cam_chain_operation_add" bl_label = "Add operation to chain" @@ -555,7 +550,7 @@ def execute(self, context): return {'FINISHED'} -class CamChainOperationUp(bpy.types.Operator): +class CamChainOperationUp(Operator): """Add operation to chain""" bl_idname = "scene.cam_chain_operation_up" bl_label = "Add operation to chain" @@ -575,7 +570,7 @@ def execute(self, context): return {'FINISHED'} -class CamChainOperationDown(bpy.types.Operator): +class CamChainOperationDown(Operator): """Add operation to chain""" bl_idname = "scene.cam_chain_operation_down" bl_label = "Add operation to chain" @@ -595,7 +590,7 @@ def execute(self, context): return {'FINISHED'} -class CamChainOperationRemove(bpy.types.Operator): +class CamChainOperationRemove(Operator): """Remove operation from chain""" bl_idname = "scene.cam_chain_operation_remove" bl_label = "Remove operation from chain" @@ -625,41 +620,7 @@ def fixUnits(): # Blender CAM doesn't respect this property and there were users reporting problems, not seeing this was changed. -# add pocket op for medial axis and profile cut inside to clean unremoved material -def Add_Pocket(self, maxdepth, sname, new_cutter_diameter): - bpy.ops.object.select_all(action='DESELECT') - s = bpy.context.scene - mpocket_exists = False - for ob in s.objects: # delete old medial pocket - if ob.name.startswith("medial_poc"): - ob.select_set(True) - bpy.ops.object.delete() - - for op in s.cam_operations: # verify medial pocket operation exists - if op.name == "MedialPocket": - mpocket_exists = True - - ob = bpy.data.objects[sname] - ob.select_set(True) - bpy.context.view_layer.objects.active = ob - silhoueteOffset(ob, -new_cutter_diameter/2, 1, 0.3) - bpy.context.active_object.name = 'medial_pocket' - - if not mpocket_exists: # create a pocket operation if it does not exist already - s.cam_operations.add() - o = s.cam_operations[-1] - o.object_name = 'medial_pocket' - s.cam_active_operation = len(s.cam_operations) - 1 - o.name = 'MedialPocket' - o.filename = o.name - o.strategy = 'POCKET' - o.use_layers = False - o.material.estimate_from_model = False - o.material.size[2] = -maxdepth - o.minz_from = 'MATERIAL' - - -class CamOperationAdd(bpy.types.Operator): +class CamOperationAdd(Operator): """Add new CAM operation""" bl_idname = "scene.cam_operation_add" bl_label = "Add new CAM operation" @@ -696,7 +657,7 @@ def execute(self, context): return {'FINISHED'} -class CamOperationCopy(bpy.types.Operator): +class CamOperationCopy(Operator): """Copy CAM operation""" bl_idname = "scene.cam_operation_copy" bl_label = "Copy active CAM operation" @@ -747,7 +708,7 @@ def execute(self, context): return {'FINISHED'} -class CamOperationRemove(bpy.types.Operator): +class CamOperationRemove(Operator): """Remove CAM operation""" bl_idname = "scene.cam_operation_remove" bl_label = "Remove CAM operation" @@ -782,7 +743,7 @@ def execute(self, context): # move cam operation in the list up or down -class CamOperationMove(bpy.types.Operator): +class CamOperationMove(Operator): """Move CAM operation""" bl_idname = "scene.cam_operation_move" bl_label = "Move CAM operation in list" @@ -819,7 +780,7 @@ def execute(self, context): return {'FINISHED'} -class CamOrientationAdd(bpy.types.Operator): +class CamOrientationAdd(Operator): """Add orientation to cam operation, for multiaxis operations""" bl_idname = "scene.cam_orientation_add" bl_label = "Add orientation" @@ -846,7 +807,7 @@ def execute(self, context): return {'FINISHED'} -class CamBridgesAdd(bpy.types.Operator): +class CamBridgesAdd(Operator): """Add bridge objects to curve""" bl_idname = "scene.cam_bridges_add" bl_label = "Add bridges" diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index 64d6bfe98..3e99dbe3c 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -18,21 +18,36 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** +from math import pi +import random +import time + +import shapely +from shapely import geometry as sgeometry +from shapely import ( + affinity, + prepared, + speedups +) import bpy +from bpy.types import PropertyGroup +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, +) +from mathutils import ( + Euler, + Vector +) + from . import ( - utils, + constants, + polygon_utils_cam, simple, - polygon_utils_cam + utils, ) -import shapely -from shapely import geometry as sgeometry -from shapely import affinity, prepared -from shapely import speedups -import random -import time -import mathutils -from mathutils import Vector # this algorithm takes all selected curves, @@ -46,7 +61,7 @@ def srotate(s, r, x, y): ncoords = [] - e = mathutils.Euler((0, 0, r)) + e = Euler((0, 0, r)) for p in s.exterior.coords: v1 = Vector((p[0], p[1], 0)) v2 = Vector((x, y, 0)) @@ -197,3 +212,69 @@ def packCurves(): polygon_utils_cam.shapelyToCurve('test', sgeometry.MultiPolygon(placedpolys), 0) print(t) + + +class PackObjectsSettings(PropertyGroup): + """stores all data for machines""" + + sheet_fill_direction: EnumProperty( + name="Fill direction", + items=( + ("X", "X", "Fills sheet in X axis direction"), + ("Y", "Y", "Fills sheet in Y axis direction"), + ), + description="Fill direction of the packer algorithm", + default="Y", + ) + sheet_x: FloatProperty( + name="X size", + description="Sheet size", + min=0.001, + max=10, + default=0.5, + precision=constants.PRECISION, + unit="LENGTH", + ) + sheet_y: FloatProperty( + name="Y size", + description="Sheet size", + min=0.001, + max=10, + default=0.5, + precision=constants.PRECISION, + unit="LENGTH", + ) + distance: FloatProperty( + name="Minimum distance", + description="minimum distance between objects(should be " + "at least cutter diameter!)", + min=0.001, + max=10, + default=0.01, + precision=constants.PRECISION, + unit="LENGTH", + ) + tolerance: FloatProperty( + name="Placement Tolerance", + description="Tolerance for placement: smaller value slower placemant", + min=0.001, + max=0.02, + default=0.005, + precision=constants.PRECISION, + unit="LENGTH", + ) + rotate: BoolProperty( + name="enable rotation", + description="Enable rotation of elements", + default=True, + ) + rotate_angle: FloatProperty( + name="Placement Angle rotation step", + description="bigger rotation angle,faster placemant", + default=0.19635 * 4, + min=pi / 180, + max=pi, + precision=5, + subtype="ANGLE", + unit="ROTATION", + ) diff --git a/scripts/addons/cam/parametric.py b/scripts/addons/cam/parametric.py index 0de7f67d2..b2d60048e 100644 --- a/scripts/addons/cam/parametric.py +++ b/scripts/addons/cam/parametric.py @@ -29,10 +29,8 @@ # the iteration count to your liking. # # This code has been checked to work on Blender 2.92. +from math import pow -import math -from math import sin, cos, pi -import bmesh import bpy from mathutils import Vector @@ -60,17 +58,17 @@ def derive_bezier_handles(a, b, c, d, tb, tc): """ # Calculate matrix coefficients - matrix_a = 3 * math.pow(1 - tb, 2) * tb - matrix_b = 3 * (1 - tb) * math.pow(tb, 2) - matrix_c = 3 * math.pow(1 - tc, 2) * tc - matrix_d = 3 * (1 - tc) * math.pow(tc, 2) + matrix_a = 3 * pow(1 - tb, 2) * tb + matrix_b = 3 * (1 - tb) * pow(tb, 2) + matrix_c = 3 * pow(1 - tc, 2) * tc + matrix_d = 3 * (1 - tc) * pow(tc, 2) # Calculate the matrix determinant matrix_determinant = 1 / ((matrix_a * matrix_d) - (matrix_b * matrix_c)) # Calculate the components of the target position vector - final_b = b - (math.pow(1 - tb, 3) * a) - (math.pow(tb, 3) * d) - final_c = c - (math.pow(1 - tc, 3) * a) - (math.pow(tc, 3) * d) + final_b = b - (pow(1 - tb, 3) * a) - (pow(tb, 3) * d) + final_c = c - (pow(1 - tc, 3) * a) - (pow(tc, 3) * d) # Multiply the inversed matrix with the position vector to get the handle points bezier_b = matrix_determinant * ((matrix_d * final_b) + (-matrix_b * final_c)) @@ -191,6 +189,8 @@ def make_edge_loops(*objects): :param *objects: Positional arguments for each object to be converted and merged. """ + context = bpy.context + scene = context.scene mesh_objects = [] vertex_groups = [] diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index b02b978a2..3e84a0049 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -18,24 +18,31 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -import time -import mathutils -from mathutils import * - -from . import ( - simple, - chunk, - utils, - polygon_utils_cam +from math import ( + ceil, + floor, + pi, + sqrt ) -from .simple import * -from .chunk import * -from .polygon_utils_cam import * -import shapely -from shapely import geometry as sgeometry +import time + import numpy +import bpy +from mathutils import ( + Euler, + Vector +) + +from .cam_chunk import ( + camPathChunk, + camPathChunkBuilder, + chunksRefine, + parentChildDist, + shapelyToChunks, +) +from .simple import progress + def getPathPatternParallel(o, angle): zlevel = 1 @@ -93,7 +100,7 @@ def getPathPatternParallel(o, angle): v = Vector((0, 1, 0)) v.rotate(e) - e1 = Euler((0, 0, -math.pi / 2)) + e1 = Euler((0, 0, -pi / 2)) v1 = v.copy() v1.rotate(e1) @@ -154,7 +161,7 @@ def getPathPattern(operation): elif o.strategy == 'CROSS': pathchunks.extend(getPathPatternParallel(o, o.parallel_angle)) - pathchunks.extend(getPathPatternParallel(o, o.parallel_angle - math.pi / 2.0)) + pathchunks.extend(getPathPatternParallel(o, o.parallel_angle - pi / 2.0)) elif o.strategy == 'BLOCK': @@ -228,7 +235,7 @@ def getPathPattern(operation): # progress(x,y,midx,midy) e = Euler((0, 0, 0)) - pi = math.pi + # pi = pi chunk.points.append((midx + v.x, midy + v.y, zlevel)) while midx + v.x > o.min.x or midy + v.y > o.min.y: # v.x=x-midx @@ -268,11 +275,11 @@ def getPathPattern(operation): midy = (o.max.y + o.min.y) / 2 rx = o.max.x - o.min.x ry = o.max.y - o.min.y - maxr = math.sqrt(rx * rx + ry * ry) + maxr = sqrt(rx * rx + ry * ry) # progress(x,y,midx,midy) e = Euler((0, 0, 0)) - pi = math.pi + # pi = pi chunk = camPathChunkBuilder([]) chunk.points.append((midx, midy, zlevel)) pathchunks.append(chunk.to_chunk()) diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index 362911f4c..acd31bc46 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -19,21 +19,22 @@ # # ***** END GPL LICENCE BLOCK ***** -import math -import mathutils -import curve_simplify +from math import pi import shapely from shapely.geometry import polygon as spolygon from shapely import geometry as sgeometry +from mathutils import Euler, Vector +import curve_simplify + SHAPELY = True def Circle(r, np): c = [] - v = mathutils.Vector((r, 0, 0)) - e = mathutils.Euler((0, 0, 2.0 * math.pi / np)) + v = Vector((r, 0, 0)) + e = Euler((0, 0, 2.0 * pi / np)) for a in range(0, np): c.append((v.x, v.y)) v.rotate(e) @@ -50,7 +51,7 @@ def shapelyRemoveDoubles(p, optimize_threshold): veclist = [] for v in c: - veclist.append(mathutils.Vector((v[0], v[1]))) + veclist.append(Vector((v[0], v[1]))) s = curve_simplify.simplify_RDP(veclist, soptions) nc = [] for i in range(0, len(s)): diff --git a/scripts/addons/cam/preferences.py b/scripts/addons/cam/preferences.py new file mode 100644 index 000000000..71d83514f --- /dev/null +++ b/scripts/addons/cam/preferences.py @@ -0,0 +1,109 @@ +from bpy.props import ( + BoolProperty, + EnumProperty, + IntProperty, + StringProperty, +) +from bpy.types import ( + AddonPreferences, +) + + +class CamAddonPreferences(AddonPreferences): + # this must match the addon name, use '__package__' + # when defining this in a submodule of a python package. + bl_idname = __package__ + + op_preset_update: BoolProperty( + name="Have the Operation Presets been Updated", + default=False, + ) + + experimental: BoolProperty( + name="Show experimental features", + default=False, + ) + + update_source: StringProperty( + name="Source of updates for the addon", + description="This can be either a github repo link in which case " + "it will download the latest release on there, " + "or an api link like " + "https://api.github.com/repos//blendercam/commits" + " to get from a github repository", + default="https://github.com/pppalain/blendercam", + ) + + last_update_check: IntProperty( + name="Last update time", + default=0, + ) + + last_commit_hash: StringProperty( + name="Hash of last commit from updater", + default="", + ) + + just_updated: BoolProperty( + name="Set to true on update or initial install", + default=True, + ) + + new_version_available: StringProperty( + name="Set to new version name if one is found", + default="", + ) + + default_interface_level: EnumProperty( + name="Interface level in new file", + description="Choose visible options", + items=[ + ("0", "Basic", "Only show essential options"), + ("1", "Advanced", "Show advanced options"), + ("2", "Complete", "Show all options"), + ("3", "Experimental", "Show experimental options"), + ], + default="3", + ) + + default_machine_preset: StringProperty( + name="Machine preset in new file", + description="So that machine preset choice persists between files", + default="", + ) + + def draw(self, context): + layout = self.layout + layout.label( + text="Use experimental features when you want to help development of Blender CAM:" + ) + layout.prop(self, "experimental") + layout.prop(self, "update_source") + layout.label(text="Choose a preset update source") + + UPDATE_SOURCES = [ + ( + "https://github.com/vilemduha/blendercam", + "Stable", + "Stable releases (github.com/vilemduja/blendercam)", + ), + ( + "https://github.com/pppalain/blendercam", + "Unstable", + "Unstable releases (github.com/pppalain/blendercam)", + ), + # comments for searching in github actions release script to + # automatically set this repo if required + # REPO ON NEXT LINE + ( + "https://api.github.com/repos/pppalain/blendercam/commits", + "Direct from git (may not work)", + "Get from git commits directly", + ), + # REPO ON PREV LINE + ("", "None", "Don't do auto update"), + ] + grid = layout.grid_flow(align=True) + for url, short, long in UPDATE_SOURCES: + op = grid.operator("render.cam_set_update_source", text=short) + op.new_source = url diff --git a/scripts/addons/cam/preset_managers.py b/scripts/addons/cam/preset_managers.py new file mode 100644 index 000000000..01ef2c981 --- /dev/null +++ b/scripts/addons/cam/preset_managers.py @@ -0,0 +1,210 @@ +import bpy +from bl_operators.presets import AddPresetBase +from bpy.types import ( + Menu, + Operator, +) + + +class CAM_CUTTER_MT_presets(Menu): + bl_label = "Cutter presets" + preset_subdir = "cam_cutters" + preset_operator = "script.execute_preset" + draw = Menu.draw_preset + + +class CAM_MACHINE_MT_presets(Menu): + bl_label = "Machine presets" + preset_subdir = "cam_machines" + preset_operator = "script.execute_preset" + draw = Menu.draw_preset + + @classmethod + def post_cb(cls, context): + name = cls.bl_label + filepath = bpy.utils.preset_find(name, + cls.preset_subdir, + display_name=True, + ext=".py") + context.preferences.addons['cam'].preferences.default_machine_preset = filepath + bpy.ops.wm.save_userpref() + + +class AddPresetCamCutter(AddPresetBase, Operator): + """Add a Cutter Preset""" + bl_idname = "render.cam_preset_cutter_add" + bl_label = "Add Cutter Preset" + preset_menu = "CAM_CUTTER_MT_presets" + + preset_defines = [ + "d = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]" + ] + + preset_values = [ + "d.cutter_id", + "d.cutter_type", + "d.cutter_diameter", + "d.cutter_length", + "d.cutter_flutes", + "d.cutter_tip_angle", + "d.cutter_description", + ] + + preset_subdir = "cam_cutters" + + +class CAM_OPERATION_MT_presets(Menu): + bl_label = "Operation presets" + preset_subdir = "cam_operations" + preset_operator = "script.execute_preset" + draw = Menu.draw_preset + + +class AddPresetCamOperation(AddPresetBase, Operator): + """Add an Operation Preset""" + bl_idname = "render.cam_preset_operation_add" + bl_label = "Add Operation Preset" + preset_menu = "CAM_OPERATION_MT_presets" + + preset_defines = [ + 'import cam', + 'o = cam.utils.setup_operation_preset()', + ] + + preset_values = [ + 'o.info.duration', + 'o.info.chipload', + 'o.info.warnings', + + 'o.material.estimate_from_model', + 'o.material.size', + 'o.material.radius_around_model', + 'o.material.origin', + + 'o.movement.stay_low', + 'o.movement.free_height', + 'o.movement.insideout', + 'o.movement.spindle_rotation', + 'o.movement.type', + 'o.movement.useG64', + 'o.movement.G64', + 'o.movement.parallel_step_back', + 'o.movement.protect_vertical', + + 'o.source_image_name', + 'o.source_image_offset', + 'o.source_image_size_x', + 'o.source_image_crop', + 'o.source_image_crop_start_x', + 'o.source_image_crop_start_y', + 'o.source_image_crop_end_x', + 'o.source_image_crop_end_y', + 'o.source_image_scale_z', + + 'o.optimisation.optimize', + 'o.optimisation.optimize_threshold', + 'o.optimisation.use_exact', + 'o.optimisation.exact_subdivide_edges', + 'o.optimisation.simulation_detail', + 'o.optimisation.pixsize', + 'o.optimisation.circle_detail', + + 'o.cut_type', + 'o.cutter_tip_angle', + 'o.cutter_id', + 'o.cutter_diameter', + 'o.cutter_type', + 'o.cutter_flutes', + 'o.cutter_length', + + 'o.ambient_behaviour', + 'o.ambient_radius', + + 'o.curve_object', + 'o.curve_object1', + 'o.limit_curve', + 'o.use_limit_curve', + + 'o.feedrate', + 'o.plunge_feedrate', + + 'o.dist_along_paths', + 'o.dist_between_paths', + + 'o.max', + 'o.min', + 'o.minz_from', + 'o.minz', + + 'o.skin', + 'o.spindle_rpm', + 'o.use_layers', + 'o.carve_depth', + + 'o.update_offsetimage_tag', + 'o.slice_detail', + 'o.drill_type', + 'o.dont_merge', + 'o.update_silhouete_tag', + 'o.inverse', + 'o.waterline_fill', + 'o.strategy', + 'o.update_zbufferimage_tag', + 'o.stepdown', + 'o.path_object_name', + 'o.pencil_threshold', + 'o.geometry_source', + 'o.object_name', + 'o.parallel_angle', + + 'o.output_header', + 'o.gcode_header', + 'o.output_trailer', + 'o.gcode_trailer', + 'o.use_modifiers', + + 'o.enable_A', + 'o.enable_B', + 'o.A_along_x', + 'o.rotation_A', + 'o.rotation_B', + 'o.straight' + ] + + preset_subdir = "cam_operations" + + +class AddPresetCamMachine(AddPresetBase, Operator): + """Add a Cam Machine Preset""" + bl_idname = "render.cam_preset_machine_add" + bl_label = "Add Machine Preset" + preset_menu = "CAM_MACHINE_MT_presets" + + preset_defines = [ + "d = bpy.context.scene.cam_machine", + "s = bpy.context.scene.unit_settings" + ] + preset_values = [ + "d.post_processor", + "s.system", + "d.use_position_definitions", + "d.starting_position", + "d.mtc_position", + "d.ending_position", + "d.working_area", + "d.feedrate_min", + "d.feedrate_max", + "d.feedrate_default", + "d.spindle_min", + "d.spindle_max", + "d.spindle_default", + "d.axis4", + "d.axis5", + "d.collet_size", + "d.output_tool_change", + "d.output_block_numbers", + "d.output_tool_definitions", + "d.output_g43_on_tool_change", + ] + + preset_subdir = "cam_machines" diff --git a/scripts/addons/cam/puzzle_joinery.py b/scripts/addons/cam/puzzle_joinery.py index 7a554a4b6..f547b19c3 100644 --- a/scripts/addons/cam/puzzle_joinery.py +++ b/scripts/addons/cam/puzzle_joinery.py @@ -20,29 +20,22 @@ # ***** END GPL LICENCE BLOCK ***** # blender operators definitions are in this file. They mostly call the functions from curvecamcreate.py -from typing import Any +from math import ( + cos, + degrees, + pi, + sin, + sqrt, + tan, +) import bpy -from bpy.types import Operator from . import ( - utils, - pack, - polygon_utils_cam, - simple, - gcodepath, - bridges, - parametric, joinery, + simple, + utils, ) -import shapely -from shapely.geometry import ( - Point, - LineString, - Polygon, -) -import mathutils -import math DT = 1.025 @@ -134,7 +127,7 @@ def twistf(name, length, diameter, tolerance, twist, tneck, tthick, twist_keep=F # add twist lock to receptacle if twist: joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) - simple.rotate(math.pi / 2) + simple.rotate(pi / 2) simple.move(y=-tthick / 2 + 2 * diameter + 2 * tolerance) simple.active_name('xtemptwist') if twist_keep: @@ -151,7 +144,7 @@ def twistm(name, length, diameter, tolerance, twist, tneck, tthick, angle, twist global DT if twist: joinery.interlock_twist(length, tthick, tolerance, cx=0, cy=0, rotation=0, percentage=tneck) - simple.rotate(math.pi / 2) + simple.rotate(pi / 2) simple.move(y=-tthick / 2 + 2 * diameter * DT) simple.rotate(angle) simple.move(x=x, y=y) @@ -190,25 +183,25 @@ def bar(width, thick, diameter, tolerance, amount=0, stem=1, twist=False, tneck= if which == 'MM' or which == 'M' or which == 'MF': simple.rename('fingers', '_tmpfingers') - simple.rotate(-math.pi / 2) + simple.rotate(-pi / 2) simple.move(x=width / 2) simple.rename('tmprect', '_tmprect') simple.union('_tmp') simple.active_name("tmprect") - twistm('tmprect', thick, diameter, tolerance, twist, tneck, tthick, -math.pi / 2, + twistm('tmprect', thick, diameter, tolerance, twist, tneck, tthick, -pi / 2, x=width / 2, twist_keep=twist_keep) twistf('receptacle', thick, diameter, tolerance, twist, tneck, tthick, twist_keep=twist_keep) simple.rename('receptacle', '_tmpreceptacle') if which == 'FF' or which == 'F' or which == 'MF': - simple.rotate(-math.pi / 2) + simple.rotate(-pi / 2) simple.move(x=-width / 2) simple.rename('tmprect', '_tmprect') simple.difference('_tmp', '_tmprect') simple.active_name("tmprect") if twist_keep: simple.make_active('twist_keep_f') - simple.rotate(-math.pi / 2) + simple.rotate(-pi / 2) simple.move(x=-width / 2) simple.remove_multiple("_") # Remove temporary base and holes @@ -259,13 +252,13 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False # generate arc bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Segment', Simple_a=radius - thick / 2, - Simple_b=radius + thick / 2, Simple_startangle=-0.0001, Simple_endangle=math.degrees(angle), + Simple_b=radius + thick / 2, Simple_startangle=-0.0001, Simple_endangle=degrees(angle), Simple_radius=radius, use_cyclic_u=False, edit_mode=False) bpy.context.active_object.name = "tmparc" simple.rename('fingers', '_tmpfingers') - simple.rotate(math.pi) + simple.rotate(pi) simple.move(x=radius) bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') @@ -273,7 +266,7 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False if which == 'MF' or which == 'M': simple.union('_tmp') simple.active_name("base") - twistm('base', thick, diameter, tolerance, twist, tneck, tthick, math.pi, x=radius) + twistm('base', thick, diameter, tolerance, twist, tneck, tthick, pi, x=radius) simple.rename('base', '_tmparc') simple.rename('receptacle', '_tmpreceptacle') @@ -293,13 +286,13 @@ def arc(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twist=False simple.rotate(-angle) simple.mirrory() bpy.ops.object.transform_apply(location=True, rotation=True, scale=False) - simple.rotate(-math.pi / 2) + simple.rotate(-pi / 2) simple.move(y=radius) simple.rename('PUZZLE_arc', 'PUZZLE_arc_male') elif which == 'F': simple.mirrorx() simple.move(x=radius) - simple.rotate(math.pi / 2) + simple.rotate(pi / 2) simple.rename('PUZZLE_arc', 'PUZZLE_arc_receptacle') else: simple.move(x=-radius) @@ -398,7 +391,7 @@ def arcbar(length, radius, thick, angle, diameter, tolerance, amount=0, stem=1, if which == 'FF' or which == 'FM': bar(length, thick, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck, tthick=tthick, which='F', twist_keep=twist_keep, twist_line=twist_line, twist_line_amount=twist_line_amount) - simple.rotate(math.pi) + simple.rotate(pi) simple.active_name('tmprect') # Generate female section and join to base @@ -443,7 +436,7 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis r_exterior = radius + thick / 2 r_interior = radius - thick / 2 - height = math.sqrt(r_exterior * r_exterior - radius * radius) + r_interior / 4 + height = sqrt(r_exterior * r_exterior - radius * radius) + r_interior / 4 bpy.ops.curve.simple(align='WORLD', location=(0, height, 0), rotation=(0, 0, 0), Simple_Type='Rectangle', @@ -453,7 +446,7 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis bpy.ops.curve.simple(align='WORLD', location=(0, 0, 0), rotation=(0, 0, 0), Simple_Type='Circle', Simple_sides=4, Simple_radius=r_interior, shape='3D', use_cyclic_u=True, edit_mode=False) - simple.move(y=radius * math.tan(angle)) + simple.move(y=radius * tan(angle)) simple.active_name('tmpCircle') arc(radius, thick, angle, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck, tthick=tthick, @@ -468,7 +461,7 @@ def multiangle(radius, thick, angle, diameter, tolerance, amount=0, stem=1, twis which='M') simple.active_name('tmp_arc') simple.mirrory() - simple.rotate(math.pi / 2) + simple.rotate(pi / 2) simple.union("tmp_") simple.difference('tmp', 'tmp_') simple.active_name('multiAngle60') @@ -535,11 +528,11 @@ def curved_t(length, thick, radius, diameter, tolerance, amount=0, stem=1, twist simple.active_name("tmp_rect") if base_gender == 'MF': - arc(radius, thick, math.pi / 2, diameter, tolerance, + arc(radius, thick, pi / 2, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck, tthick=tthick, which='M') simple.move(-radius) simple.active_name('tmp_arc') - arc(radius, thick, math.pi / 2, diameter, tolerance, + arc(radius, thick, pi / 2, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck, tthick=tthick, which='F') simple.move(radius) simple.mirrory() @@ -550,7 +543,7 @@ def curved_t(length, thick, radius, diameter, tolerance, amount=0, stem=1, twist simple.union('tmp_arc') simple.difference('tmp_', 'tmp_arc') else: - arc(radius, thick, math.pi / 2, diameter, tolerance, + arc(radius, thick, pi / 2, diameter, tolerance, amount=amount, stem=stem, twist=twist, tneck=tneck, tthick=tthick, which=base_gender) simple.active_name('tmp_arc') simple.difference('tmp_', 'tmp_arc') @@ -615,16 +608,16 @@ def mitre(length, thick, angle, angleb, diameter, tolerance, amount=0, stem=1, t simple.make_active('fingers') simple.duplicate() simple.active_name('tmpfingers') - simple.rotate(angle - math.pi / 2) - h = thick / math.cos(angle) + simple.rotate(angle - pi / 2) + h = thick / cos(angle) h /= 2 - simple.move(x=length / 2 + h * math.sin(angle), y=-thick / 2) + simple.move(x=length / 2 + h * sin(angle), y=-thick / 2) if which == 'M': simple.rename('fingers', 'tmpfingers') - simple.rotate(angleb - math.pi / 2) - h = thick / math.cos(angleb) + simple.rotate(angleb - pi / 2) + h = thick / cos(angleb) h /= 2 - simple.move(x=length / 2 + h * math.sin(angleb), y=-thick / 2) + simple.move(x=length / 2 + h * sin(angleb), y=-thick / 2) simple.mirrorx() simple.union('tmp') @@ -636,17 +629,17 @@ def mitre(length, thick, angle, angleb, diameter, tolerance, amount=0, stem=1, t simple.mirrory() simple.duplicate() simple.active_name('tmpreceptacle') - simple.rotate(angleb - math.pi / 2) - h = thick / math.cos(angleb) + simple.rotate(angleb - pi / 2) + h = thick / cos(angleb) h /= 2 - simple.move(x=length / 2 + h * math.sin(angleb), y=-thick / 2) + simple.move(x=length / 2 + h * sin(angleb), y=-thick / 2) simple.mirrorx() if which == 'F': simple.rename('receptacle', 'tmpreceptacle2') - simple.rotate(angle - math.pi / 2) - h = thick / math.cos(angle) + simple.rotate(angle - pi / 2) + h = thick / cos(angle) h /= 2 - simple.move(x=length / 2 + h * math.sin(angle), y=-thick / 2) + simple.move(x=length / 2 + h * sin(angle), y=-thick / 2) simple.difference('tmp', 'tmprect') simple.remove_multiple('receptacle') @@ -673,8 +666,8 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, coords = list(line.coords) - start_angle = joinery.angle(coords[0], coords[1]) + math.pi/2 - end_angle = joinery.angle(coords[-1], coords[-2]) + math.pi/2 + start_angle = joinery.angle(coords[0], coords[1]) + pi/2 + end_angle = joinery.angle(coords[-1], coords[-2]) + pi/2 p_start = coords[0] p_end = coords[-1] @@ -709,18 +702,18 @@ def open_curve(line, thick, diameter, tolerance, amount=0, stem=1, twist=False, twistf('receptacle', thick, diameter, tolerance, twist, t_neck, t_thick, twist_keep=twist_keep) simple.rename('receptacle', 'tmp') - simple.rotate(start_angle+math.pi) + simple.rotate(start_angle+pi) simple.move(x=p_start[0], y=p_start[1]) simple.difference('tmp', 'tmp_curve') if twist_keep: simple.make_active('twist_keep_f') - simple.rotate(start_angle + math.pi) + simple.rotate(start_angle + pi) simple.move(x=p_start[0], y=p_start[1]) if twist_amount > 0 and twist: twist_start = line.length / (twist_amount+1) joinery.distributed_interlock(line, line.length, thick, t_thick, tolerance, twist_amount, - tangent=math.pi/2, fixed_angle=0, start=twist_start, end=twist_start, + tangent=pi/2, fixed_angle=0, start=twist_start, end=twist_start, closed=False, type='TWIST', twist_percentage=t_neck) if twist_keep: simple.duplicate() @@ -761,12 +754,12 @@ def tile(diameter, tolerance, tile_x_amount, tile_y_amount, stem=1): simple.rename('base', '_base') simple.remove_doubles() simple.rename('fingers', '_fingers') - simple.rotate(math.pi/2) + simple.rotate(pi/2) simple.move(x=-width/2) simple.union('_') simple.active_name('_base') simple.rename('receptacle', '_receptacle') - simple.rotate(math.pi/2) + simple.rotate(pi/2) simple.move(x=width/2) simple.difference('_', '_base') simple.active_name('tile_ ' + str(tile_x_amount) + '_' + str(tile_y_amount)) diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index c05cf96be..8d52cfe52 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -18,25 +18,21 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -# Solves: No module named 'shapely' (even if it is installed) -# help('modules') - -import math -import sys +from math import ( + hypot, + pi, +) import os import string +import sys import time + +from shapely.geometry import Polygon + import bpy -import mathutils -from mathutils import * -from math import * -from shapely.geometry import ( - Point, - LineString, - Polygon, - multilinestring -) +from mathutils import Vector + +from .constants import BULLET_SCALE def tuple_add(t, t1): # add two tuples as Vectors @@ -96,7 +92,7 @@ def activate(o): def dist2d(v1, v2): """distance between two points in 2d""" - return math.hypot((v1[0] - v2[0]), (v1[1] - v2[1])) + return hypot((v1[0] - v2[0]), (v1[1] - v2[1])) def delob(ob): @@ -325,7 +321,7 @@ def remove_doubles(): def add_overcut(diametre, overcut=True): if overcut: name = bpy.context.active_object.name - bpy.ops.object.curve_overcuts(diameter=diametre, threshold=math.pi/2.05) + bpy.ops.object.curve_overcuts(diameter=diametre, threshold=pi/2.05) overcut_name = bpy.context.active_object.name make_active(name) bpy.ops.object.delete() diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 3682813af..398c13498 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -20,17 +20,24 @@ # ***** END GPL LICENCE BLOCK ***** # here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. - -import bpy -import mathutils import math import time -from . import utils + import numpy as np -from . import simple -from . import image_utils +import bpy +from mathutils import Vector + from .async_op import progress_async +from .image_utils import ( + getCutterArray, + numpysave, +) +from .simple import getSimulationPath +from .utils import ( + getBoundsMultiple, + getOperationSources, +) def createSimulationObject(name, operations, i): @@ -93,16 +100,16 @@ def createSimulationObject(name, operations, i): async def doSimulation(name, operations): """perform simulation of operations. Currently only for 3 axis""" for o in operations: - utils.getOperationSources(o) - limits = utils.getBoundsMultiple( + getOperationSources(o) + limits = getBoundsMultiple( operations) # this is here because some background computed operations still didn't have bounds data i = await generateSimulationImage(operations, limits) -# cp = simple.getCachePath(operations[0])[:-len(operations[0].name)] + name - cp = simple.getSimulationPath()+name +# cp = getCachePath(operations[0])[:-len(operations[0].name)] + name + cp = getSimulationPath()+name print('cp=', cp) iname = cp + '_sim.exr' - image_utils.numpysave(i, iname) + numpysave(i, iname) i = bpy.data.images.load(iname) createSimulationObject(name, operations, i) @@ -288,101 +295,6 @@ async def generateSimulationImage(operations, limits): return si -def getCutterArray(operation, pixsize): - type = operation.cutter_type - # print('generating cutter') - r = operation.cutter_diameter / 2 + operation.skin # /operation.pixsize - res = math.ceil((r * 2) / pixsize) - m = res / 2.0 - car = np.full(shape=(res, res), fill_value=-10.0, dtype=float) - - v = mathutils.Vector((0, 0, 0)) - ps = pixsize - if type == 'END': - for a in range(0, res): - v.x = (a + 0.5 - m) * ps - for b in range(0, res): - v.y = (b + 0.5 - m) * ps - if v.length <= r: - car.itemset((a, b), 0) - elif type == 'BALL' or type == 'BALLNOSE': - for a in range(0, res): - v.x = (a + 0.5 - m) * ps - for b in range(0, res): - v.y = (b + 0.5 - m) * ps - if v.length <= r: - z = math.sin(math.acos(v.length / r)) * r - r - car.itemset((a, b), z) # [a,b]=z - - elif type == 'VCARVE': - angle = operation.cutter_tip_angle - s = math.tan(math.pi * (90 - angle / 2) / 180) # angle in degrees - for a in range(0, res): - v.x = (a + 0.5 - m) * ps - for b in range(0, res): - v.y = (b + 0.5 - m) * ps - if v.length <= r: - z = (-v.length * s) - car.itemset((a, b), z) - elif type == 'CYLCONE': - angle = operation.cutter_tip_angle - cyl_r = operation.cylcone_diameter/2 - s = math.tan(math.pi * (90 - angle / 2) / 180) # angle in degrees - for a in range(0, res): - v.x = (a + 0.5 - m) * ps - for b in range(0, res): - v.y = (b + 0.5 - m) * ps - if v.length <= r: - z = (-(v.length - cyl_r) * s) - if v.length <= cyl_r: - z = 0 - car.itemset((a, b), z) - elif type == 'BALLCONE': - angle = math.radians(operation.cutter_tip_angle)/2 - ball_r = operation.ball_radius - cutter_r = operation.cutter_diameter / 2 - conedepth = (cutter_r - ball_r)/math.tan(angle) - Ball_R = ball_r/math.cos(angle) - D_ofset = ball_r * math.tan(angle) - s = math.tan(math.pi/2-angle) - for a in range(0, res): - v.x = (a + 0.5 - m) * ps - for b in range(0, res): - v.y = (b + 0.5 - m) * ps - if v.length <= cutter_r: - z = -(v.length - ball_r) * s - Ball_R + D_ofset - if v.length <= ball_r: - z = math.sin(math.acos(v.length / Ball_R)) * Ball_R - Ball_R - car.itemset((a, b), z) - elif type == 'CUSTOM': - cutob = bpy.data.objects[operation.cutter_object_name] - scale = ((cutob.dimensions.x / cutob.scale.x) / 2) / r # - # print(cutob.scale) - vstart = mathutils.Vector((0, 0, -10)) - vend = mathutils.Vector((0, 0, 10)) - print('sampling custom cutter') - maxz = -1 - for a in range(0, res): - vstart.x = (a + 0.5 - m) * ps * scale - vend.x = vstart.x - - for b in range(0, res): - vstart.y = (b + 0.5 - m) * ps * scale - vend.y = vstart.y - v = vend - vstart - c = cutob.ray_cast(vstart, v, distance=1.70141e+38) - if c[3] != -1: - z = -c[1][2] / scale - # print(c) - if z > -9: - # print(z) - if z > maxz: - maxz = z - car.itemset((a, b), z) - car -= maxz - return car - - def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False): """simulates a cutter cutting into stock, taking away the volume, and optionally returning the volume that has been milled. This is now used for feedrate tweaking.""" diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index f0364be44..8dcbeaaf8 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -23,8 +23,16 @@ # completely rewritten April 2021 import bpy +from bpy.props import ( + BoolProperty, + FloatProperty, +) +from bpy.types import PropertyGroup -from . import utils +from . import ( + constants, + utils, +) def slicing2d(ob, height): # April 2020 Alain Pelletier @@ -134,3 +142,33 @@ def sliceObject(ob): # April 2020 Alain Pelletier # select all slices for obj in bpy.data.collections['Slices'].all_objects: obj.select_set(True) + + +class SliceObjectsSettings(PropertyGroup): + """stores all data for machines""" + + slice_distance: FloatProperty( + name="Slicing distance", + description="slices distance in z, should be most often " + "thickness of plywood sheet.", + min=0.001, + max=10, + default=0.005, + precision=constants.PRECISION, + unit="LENGTH", + ) + slice_above0: BoolProperty( + name="Slice above 0", + description="only slice model above 0", + default=False, + ) + slice_3d: BoolProperty( + name="3d slice", + description="for 3d carving", + default=False, + ) + indexes: BoolProperty( + name="add indexes", + description="adds index text of layer + index", + default=True, + ) diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index d4fa6ec5e..98ebc0781 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -20,55 +20,88 @@ # ***** END GPL LICENCE BLOCK ***** # here is the strategy functionality of Blender CAM. The functions here are called with operators defined in ops.py. - -import bpy -import time -import math -from math import * -from bpy_extras import object_utils -from . import ( - chunk, - collision, - simple, - pattern, - utils, - bridges, - ops, - polygon_utils_cam, - image_utils +from math import ( + ceil, + pi, + radians, + sqrt, + tan, ) -from .chunk import * -from .collision import * -from .simple import * -from .pattern import * -from .utils import * -from .polygon_utils_cam import * -from .image_utils import * +import sys +import time +import shapely from shapely.geometry import polygon as spolygon +from shapely.geometry import Point # Double check this import! from shapely import geometry as sgeometry from shapely import affinity +import bpy +from bpy_extras import object_utils +from mathutils import ( + Euler, + Vector +) + +from .bridges import useBridges +from .cam_chunk import ( + camPathChunk, + chunksRefine, + chunksRefineThreshold, + curveToChunks, + limitChunks, + optimizeChunk, + parentChildDist, + parentChildPoly, + setChunksZ, + shapelyToChunks, +) +from .collision import cleanupBulletCollision +from .exception import CamException +from .polygon_utils_cam import Circle, shapelyToCurve +from .simple import ( + activate, + delob, + join_multiple, + progress, + remove_multiple, +) +from .utils import ( + Add_Pocket, + checkEqual, + extendChunks5axis, + getObjectOutline, + getObjectSilhouete, + getOperationSilhouete, + getOperationSources, + Helix, + # Point, + sampleChunksNAxis, + sortChunks, + unique, +) + + SHAPELY = True # cutout strategy is completely here: async def cutout(o): max_depth = checkminz(o) - cutter_angle = math.radians(o.cutter_tip_angle / 2) + cutter_angle = radians(o.cutter_tip_angle / 2) c_offset = o.cutter_diameter / 2 # cutter ofset print("cuttertype:", o.cutter_type, "max_depth:", max_depth) if o.cutter_type == 'VCARVE': - c_offset = -max_depth * math.tan(cutter_angle) + c_offset = -max_depth * tan(cutter_angle) elif o.cutter_type == 'CYLCONE': - c_offset = -max_depth * math.tan(cutter_angle) + o.cylcone_diameter / 2 + c_offset = -max_depth * tan(cutter_angle) + o.cylcone_diameter / 2 elif o.cutter_type == 'BALLCONE': - c_offset = -max_depth * math.tan(cutter_angle) + o.ball_radius + c_offset = -max_depth * tan(cutter_angle) + o.ball_radius elif o.cutter_type == 'BALLNOSE': r = o.cutter_diameter / 2 print("cutter radius:", r, " skin", o.skin) if -max_depth < r: - c_offset = math.sqrt(r ** 2 - (r + max_depth) ** 2) + c_offset = sqrt(r ** 2 - (r + max_depth) ** 2) print("offset:", c_offset) if c_offset > o.cutter_diameter / 2: c_offset = o.cutter_diameter / 2 @@ -95,14 +128,14 @@ async def cutout(o): else: chunksFromCurve = [] if o.cut_type == 'ONLINE': - p = utils.getObjectOutline(0, o, True) + p = getObjectOutline(0, o, True) else: offset = True if o.cut_type == 'INSIDE': offset = False - p = utils.getObjectOutline(c_offset, o, offset) + p = getObjectOutline(c_offset, o, offset) if o.outlines_count > 1: for i in range(1, o.outlines_count): chunksFromCurve.extend(shapelyToChunks(p, -1)) @@ -121,7 +154,7 @@ async def cutout(o): if not o.dont_merge: parentChildPoly(chunksFromCurve, chunksFromCurve, o) if o.outlines_count == 1: - chunksFromCurve = await utils.sortChunks(chunksFromCurve, o) + chunksFromCurve = await sortChunks(chunksFromCurve, o) if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW') or ( o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW'): @@ -164,7 +197,7 @@ async def cutout(o): if o.use_bridges: # add bridges to chunks print('using bridges') - simple.remove_multiple(o.name+'_cut_bridges') + remove_multiple(o.name+'_cut_bridges') print("old briddge cut removed") bridgeheight = min(o.max.z, o.min.z + abs(o.bridges_height)) @@ -173,7 +206,7 @@ async def cutout(o): chunk = chl[0] layer = chl[1] if layer[1] < bridgeheight: - bridges.useBridges(chunk, o) + useBridges(chunk, o) if o.profile_start > 0: print("cutout change profile start") @@ -211,7 +244,7 @@ async def cutout(o): async def curve(o): print('operation: curve') pathSamples = [] - utils.getOperationSources(o) + getOperationSources(o) if not o.onlycurves: raise CamException("All objects must be curves for this operation.") @@ -219,7 +252,7 @@ async def curve(o): # make the chunks from curve here pathSamples.extend(curveToChunks(ob)) # sort before sampling - pathSamples = await utils.sortChunks(pathSamples, o) + pathSamples = await sortChunks(pathSamples, o) pathSamples = chunksRefine(pathSamples, o) # simplify # layers here @@ -264,7 +297,7 @@ async def proj_curve(s, o): targetCurve = s.objects[o.curve_object1] - from cam import chunk + from cam import cam_chunk if targetCurve.type != 'CURVE': raise CamException('Projection target and source have to be curve objects!') @@ -302,7 +335,7 @@ async def proj_curve(s, o): ch.set_points(ch_points) layers = getLayers(o, 0, ch.depth) - chunks.extend(utils.sampleChunksNAxis(o, pathSamples, layers)) + chunks.extend(sampleChunksNAxis(o, pathSamples, layers)) chunksToMesh(chunks, o) @@ -310,24 +343,24 @@ async def pocket(o): print('operation: pocket') scene = bpy.context.scene - simple.remove_multiple("3D_poc") + remove_multiple("3D_poc") max_depth = checkminz(o) + o.skin - cutter_angle = math.radians(o.cutter_tip_angle / 2) + cutter_angle = radians(o.cutter_tip_angle / 2) c_offset = o.cutter_diameter / 2 if o.cutter_type == 'VCARVE': - c_offset = -max_depth * math.tan(cutter_angle) + c_offset = -max_depth * tan(cutter_angle) elif o.cutter_type == 'CYLCONE': - c_offset = -max_depth * math.tan(cutter_angle) + o.cylcone_diameter / 2 + c_offset = -max_depth * tan(cutter_angle) + o.cylcone_diameter / 2 elif o.cutter_type == 'BALLCONE': - c_offset = -max_depth * math.tan(cutter_angle) + o.ball_radius + c_offset = -max_depth * tan(cutter_angle) + o.ball_radius if c_offset > o.cutter_diameter / 2: c_offset = o.cutter_diameter / 2 c_offset += o.skin # add skin print("cutter offset", c_offset) - p = utils.getObjectOutline(c_offset, o, False) + p = getObjectOutline(c_offset, o, False) approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 print("approximative:" + str(approxn)) print(o) @@ -342,7 +375,7 @@ async def pocket(o): while not p.is_empty: if o.pocketToCurve: # make a curve starting with _3dpocket - polygon_utils_cam.shapelyToCurve('3dpocket', p, 0.0) + shapelyToCurve('3dpocket', p, 0.0) nchunks = shapelyToChunks(p, o.min.z) # print("nchunks") @@ -372,7 +405,7 @@ async def pocket(o): for ch in chunksFromCurve: ch.reverse() - chunksFromCurve = await utils.sortChunks(chunksFromCurve, o) + chunksFromCurve = await sortChunks(chunksFromCurve, o) chunks = [] layers = getLayers(o, o.maxz, checkminz(o)) @@ -494,10 +527,10 @@ async def pocket(o): if o.first_down: if o.pocket_option == "OUTSIDE": chunks.reverse() - chunks = await utils.sortChunks(chunks, o) + chunks = await sortChunks(chunks, o) if o.pocketToCurve: # make curve instead of a path - simple.join_multiple("3dpocket") + join_multiple("3dpocket") else: chunksToMesh(chunks, o) # make normal pocket path @@ -593,14 +626,14 @@ async def drill(o): newchunk.setZ(o.maxz) chunklayers.append(newchunk) - chunklayers = await utils.sortChunks(chunklayers, o) + chunklayers = await sortChunks(chunklayers, o) chunksToMesh(chunklayers, o) async def medial_axis(o): print('operation: Medial Axis') - simple.remove_multiple("medialMesh") + remove_multiple("medialMesh") from .voronoi import Site, computeVoronoiDiagram @@ -608,8 +641,8 @@ async def medial_axis(o): gpoly = spolygon.Polygon() angle = o.cutter_tip_angle - slope = math.tan(math.pi * (90 - angle / 2) / 180) # angle in degrees - # slope = math.tan((math.pi-angle)/2) #angle in radian + slope = tan(pi * (90 - angle / 2) / 180) # angle in degrees + # slope = tan((pi-angle)/2) #angle in radian new_cutter_diameter = o.cutter_diameter m_o_ob = o.object_name if o.cutter_type == 'VCARVE': @@ -638,7 +671,7 @@ async def medial_axis(o): if ob.data.resolution_u < 64: ob.data.resolution_u = 64 - polys = utils.getOperationSilhouete(o) + polys = getOperationSilhouete(o) if isinstance(polys, list): if len(polys) == 1 and isinstance(polys[0], shapely.MultiPolygon): mpoly = polys[0] @@ -758,7 +791,7 @@ async def medial_axis(o): # generate a mesh from the medial calculations if o.add_mesh_for_medial: - polygon_utils_cam.shapelyToCurve('medialMesh', lines, 0.0) + shapelyToCurve('medialMesh', lines, 0.0) bpy.ops.object.convert(target='MESH') oi = 0 @@ -768,7 +801,7 @@ async def medial_axis(o): oi += 1 # bpy.ops.object.join() - chunks = await utils.sortChunks(chunks, o) + chunks = await sortChunks(chunks, o) layers = getLayers(o, o.maxz, o.min.z) @@ -782,17 +815,17 @@ async def medial_axis(o): chunklayers.append(newchunk) if o.first_down: - chunklayers = await utils.sortChunks(chunklayers, o) + chunklayers = await sortChunks(chunklayers, o) if o.add_mesh_for_medial: # make curve instead of a path - simple.join_multiple("medialMesh") + join_multiple("medialMesh") chunksToMesh(chunklayers, o) # add pocket operation for medial if add pocket checked if o.add_pocket_for_medial: # o.add_pocket_for_medial = False # export medial axis parameter to pocket op - ops.Add_Pocket(None, maxdepth, m_o_ob, new_cutter_diameter) + Add_Pocket(None, maxdepth, m_o_ob, new_cutter_diameter) def getLayers(operation, startdepth, enddepth): @@ -805,7 +838,7 @@ def getLayers(operation, startdepth, enddepth): "and should usually be negative. Set this in the CAM Operation Area panel.") if operation.use_layers: layers = [] - n = math.ceil((startdepth - enddepth) / operation.stepdown) + n = ceil((startdepth - enddepth) / operation.stepdown) print("start " + str(startdepth) + " end " + str(enddepth) + " n " + str(n)) layerstart = operation.maxz diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 830b1ceff..4decdd0c7 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -18,12 +18,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -import sys import bpy -from . import simple, utils -from .simple import * +from .gcodepath import getPath +from .simple import activate def addTestCurve(loc): @@ -138,7 +136,7 @@ def testOperation(i): report = '' report += 'testing operation ' + o.name + '\n' - utils.getPath(bpy.context, o) + getPath(bpy.context, o) newresult = bpy.data.objects[o.path_object_name] origname = "test_cam_path_" + o.name diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index 8034cbd57..fe56733ae 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -18,42 +18,21 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ***** END GPL LICENCE BLOCK ***** - -import sys -import bpy from bpy_extras.io_utils import ImportHelper from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, StringProperty, ) from bpy.types import ( Panel, - Menu, Operator, - UIList + UIList, + PropertyGroup, ) -from . import ( - gcodeimportparser, - simple -) -from .simple import * - -from .ui_panels.buttons_panel import CAMButtonsPanel -from .ui_panels.interface import * -from .ui_panels.info import * -from .ui_panels.operations import * -from .ui_panels.cutter import * -from .ui_panels.machine import * -from .ui_panels.material import * -from .ui_panels.chains import * -from .ui_panels.op_properties import * -from .ui_panels.movement import * -from .ui_panels.feedrate import * -from .ui_panels.optimisation import * -from .ui_panels.area import * -from .ui_panels.gcode import * -from .ui_panels.pack import * -from .ui_panels.slice import * +from .gcodeimportparser import import_gcode class CAM_UL_orientations(UIList): @@ -68,7 +47,7 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn # panel containing all tools -class VIEW3D_PT_tools_curvetools(bpy.types.Panel): +class VIEW3D_PT_tools_curvetools(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" @@ -87,7 +66,7 @@ def draw(self, context): layout.operator("object.mesh_get_pockets") -class VIEW3D_PT_tools_create(bpy.types.Panel): +class VIEW3D_PT_tools_create(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" @@ -115,7 +94,7 @@ def draw(self, context): # ------------------------------------------------------------------------ -class CustomPanel(bpy.types.Panel): +class CustomPanel(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" @@ -167,4 +146,34 @@ class WM_OT_gcode_import(Operator, ImportHelper): def execute(self, context): print(self.filepath) - return gcodeimportparser.import_gcode(context, self.filepath) + return import_gcode(context, self.filepath) + + +class import_settings(PropertyGroup): + split_layers: BoolProperty( + name="Split Layers", + description="Save every layer as single Objects in Collection", + default=False, + ) + subdivide: BoolProperty( + name="Subdivide", + description="Only Subdivide gcode segments that are " + "bigger than 'Segment length' ", + default=False, + ) + output: EnumProperty( + name="output type", + items=( + ("mesh", "Mesh", "Make a mesh output"), + ("curve", "Curve", "Make curve output"), + ), + default="curve", + ) + max_segment_size: FloatProperty( + name="", + description="Only Segments bigger then this value get subdivided", + default=0.001, + min=0.0001, + max=1.0, + unit="LENGTH", + ) diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index 9423bd83c..c07a0b68f 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -1,9 +1,11 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel +from ..simple import strInUnits -class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_AREA_Panel(CAMButtonsPanel, Panel): """CAM operation area panel""" bl_label = "CAM operation area " bl_idname = "WORLD_PT_CAM_OPERATION_AREA" @@ -15,8 +17,7 @@ class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel): 'draw_minz': 1, 'draw_ambient': 1, 'draw_limit_curve': 1, - 'draw_first_down': 1 - + 'draw_first_down': 1, } def draw_use_layers(self): diff --git a/scripts/addons/cam/ui_panels/buttons_panel.py b/scripts/addons/cam/ui_panels/buttons_panel.py index c3a118acf..07dfc7973 100644 --- a/scripts/addons/cam/ui_panels/buttons_panel.py +++ b/scripts/addons/cam/ui_panels/buttons_panel.py @@ -1,9 +1,9 @@ -import bpy import inspect -# Panel definitions +import bpy +# Panel definitions class CAMButtonsPanel: bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index bf8b7c0cc..15c112d1b 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -1,8 +1,7 @@ - import bpy -from bpy.types import UIList -from .buttons_panel import CAMButtonsPanel +from bpy.types import UIList, Panel +from .buttons_panel import CAMButtonsPanel from ..utils import isChainValid @@ -34,7 +33,7 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn layout.label(text="", icon_value=icon) -class CAM_CHAINS_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_CHAINS_Panel(CAMButtonsPanel, Panel): """CAM chains panel""" bl_label = "CAM chains" bl_idname = "WORLD_PT_CAM_CHAINS" diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index 08afeb270..31973e170 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_CUTTER_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_CUTTER_Panel(CAMButtonsPanel, Panel): """CAM cutter panel""" bl_label = "CAM Cutter" bl_idname = "WORLD_PT_CAM_CUTTER" diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index a1a410d82..e66f041d9 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -1,8 +1,10 @@ import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_FEEDRATE_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_FEEDRATE_Panel(CAMButtonsPanel, Panel): """CAM feedrate panel""" bl_label = "CAM feedrate" bl_idname = "WORLD_PT_CAM_FEEDRATE" diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index d802f63e0..934eaefba 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_GCODE_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_GCODE_Panel(CAMButtonsPanel, Panel): """CAM operation g-code options panel""" bl_label = "CAM g-code options " bl_idname = "WORLD_PT_CAM_GCODE" diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index c96efb769..4acbd0578 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -1,13 +1,17 @@ import bpy from bpy.props import ( StringProperty, - FloatProperty + FloatProperty, +) +from bpy.types import ( + Panel, + PropertyGroup, ) from .buttons_panel import CAMButtonsPanel from ..utils import ( + opencamlib_version, update_operation, - opencamlib_version ) from ..constants import ( PRECISION, @@ -21,7 +25,7 @@ # This panel gives general information about the current operation -class CAM_INFO_Properties(bpy.types.PropertyGroup): +class CAM_INFO_Properties(PropertyGroup): warnings: StringProperty( name='warnings', @@ -44,7 +48,7 @@ class CAM_INFO_Properties(bpy.types.PropertyGroup): ) -class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_INFO_Panel(CAMButtonsPanel, Panel): bl_label = "CAM info & warnings" bl_idname = "WORLD_PT_CAM_INFO" panel_interface_level = 0 diff --git a/scripts/addons/cam/ui_panels/interface.py b/scripts/addons/cam/ui_panels/interface.py index 6c158929e..61ba69410 100644 --- a/scripts/addons/cam/ui_panels/interface.py +++ b/scripts/addons/cam/ui_panels/interface.py @@ -1,7 +1,10 @@ import bpy from bpy.props import EnumProperty +from bpy.types import ( + Panel, + PropertyGroup +) -import math from .buttons_panel import CAMButtonsPanel @@ -11,7 +14,7 @@ def update_interface(self, context): bpy.ops.wm.save_userpref() -class CAM_INTERFACE_Properties(bpy.types.PropertyGroup): +class CAM_INTERFACE_Properties(PropertyGroup): level: EnumProperty( name="Interface", description="Choose visible options", @@ -26,7 +29,7 @@ class CAM_INTERFACE_Properties(bpy.types.PropertyGroup): ) -class CAM_INTERFACE_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_INTERFACE_Panel(CAMButtonsPanel, Panel): bl_label = "Interface" bl_idname = "WORLD_PT_CAM_INTERFACE" always_show_panel = True diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index e54b829ba..8e2b18689 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_MACHINE_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_MACHINE_Panel(CAMButtonsPanel, Panel): """CAM machine panel""" bl_label = "CAM Machine" bl_idname = "WORLD_PT_CAM_MACHINE" diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 7f218fc71..28ee78cfd 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -5,16 +5,21 @@ FloatProperty, FloatVectorProperty, ) +from bpy.types import ( + Operator, + Panel, + PropertyGroup, +) from .buttons_panel import CAMButtonsPanel from ..utils import ( + positionObject, update_material, - positionObject ) from ..constants import PRECISION -class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): +class CAM_MATERIAL_Properties(PropertyGroup): estimate_from_model: BoolProperty( name="Estimate cut area from model", @@ -84,7 +89,7 @@ class CAM_MATERIAL_Properties(bpy.types.PropertyGroup): # Position object for CAM operation. Tests object bounds and places them so the object # is aligned to be positive from x and y and negative from z.""" -class CAM_MATERIAL_PositionObject(bpy.types.Operator): +class CAM_MATERIAL_PositionObject(Operator): bl_idname = "object.material_cam_position" bl_label = "position object for CAM operation" @@ -107,7 +112,7 @@ def draw(self, context): self, "operation", bpy.context.scene, "cam_operations") -class CAM_MATERIAL_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_MATERIAL_Panel(CAMButtonsPanel, Panel): bl_label = "CAM Material size and position" bl_idname = "WORLD_PT_CAM_MATERIAL" panel_interface_level = 0 diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index b66db7228..a29885528 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -1,20 +1,25 @@ +from math import pi + import bpy from bpy.props import ( BoolProperty, EnumProperty, FloatProperty, ) +from bpy.types import ( + Panel, + PropertyGroup +) -import math from .buttons_panel import CAMButtonsPanel from ..utils import update_operation from ..constants import ( PRECISION, - G64_INCOMPATIBLE_MACHINES + G64_INCOMPATIBLE_MACHINES, ) -class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): +class CAM_MOVEMENT_Properties(PropertyGroup): # movement parallel_step_back type: EnumProperty( name='Movement type', @@ -100,9 +105,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ramp_in_angle: FloatProperty( name="Ramp in angle", - default=math.pi / 6, + default=pi / 6, min=0, - max=math.pi * 0.4999, + max=pi * 0.4999, precision=1, subtype="ANGLE", unit="ROTATION", @@ -136,9 +141,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ramp_out_angle: FloatProperty( name="Ramp out angle", - default=math.pi / 6, + default=pi / 6, min=0, - max=math.pi * 0.4999, + max=pi * 0.4999, precision=1, subtype="ANGLE", unit="ROTATION", @@ -198,9 +203,9 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): protect_vertical_limit: FloatProperty( name="Verticality limit", description="What angle is allready considered vertical", - default=math.pi / 45, + default=pi / 45, min=0, - max=math.pi * 0.5, + max=pi * 0.5, precision=0, subtype="ANGLE", unit="ROTATION", @@ -208,7 +213,7 @@ class CAM_MOVEMENT_Properties(bpy.types.PropertyGroup): ) -class CAM_MOVEMENT_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_MOVEMENT_Panel(CAMButtonsPanel, Panel): """CAM movement panel""" bl_label = "CAM movement" bl_idname = "WORLD_PT_CAM_MOVEMENT" diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index e9e0d2eff..dc52da053 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, Panel): """CAM operation properties panel""" bl_label = "CAM operation setup" bl_idname = "WORLD_PT_CAM_OPERATION" diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index e7d3973b0..7fa38ef27 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -1,5 +1,6 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel # Operations panel @@ -12,7 +13,7 @@ # For each operation, generate the corresponding gcode and export the gcode file -class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_OPERATIONS_Panel(CAMButtonsPanel, Panel): """CAM operations panel""" bl_label = "CAM operations" bl_idname = "WORLD_PT_CAM_OPERATIONS" diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index ae1ac28e9..4b6490119 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -4,19 +4,23 @@ FloatProperty, IntProperty, ) +from bpy.types import ( + Panel, + PropertyGroup, +) from .buttons_panel import CAMButtonsPanel from ..utils import ( - update_operation, + opencamlib_version, update_exact_mode, - update_zbuffer_image, update_opencamlib, - opencamlib_version, + update_operation, + update_zbuffer_image, ) from ..constants import PRECISION -class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): +class CAM_OPTIMISATION_Properties(PropertyGroup): optimize: BoolProperty( name="Reduce path points", @@ -96,7 +100,7 @@ class CAM_OPTIMISATION_Properties(bpy.types.PropertyGroup): ) -class CAM_OPTIMISATION_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_OPTIMISATION_Panel(CAMButtonsPanel, Panel): """CAM optimisation panel""" bl_label = "CAM optimisation" bl_idname = "WORLD_PT_CAM_OPTIMISATION" diff --git a/scripts/addons/cam/ui_panels/pack.py b/scripts/addons/cam/ui_panels/pack.py index acda772f1..6d2f3767e 100644 --- a/scripts/addons/cam/ui_panels/pack.py +++ b/scripts/addons/cam/ui_panels/pack.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_PACK_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_PACK_Panel(CAMButtonsPanel, Panel): """CAM material panel""" bl_label = "Pack curves on sheet" bl_idname = "WORLD_PT_CAM_PACK" diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index 6f42868d3..10ef67029 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -1,9 +1,10 @@ - import bpy +from bpy.types import Panel + from .buttons_panel import CAMButtonsPanel -class CAM_SLICE_Panel(CAMButtonsPanel, bpy.types.Panel): +class CAM_SLICE_Panel(CAMButtonsPanel, Panel): """CAM slicer panel""" bl_label = "Slice model to plywood sheets" bl_idname = "WORLD_PT_CAM_SLICE" diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index e2ed68344..0b76428d7 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -21,39 +21,68 @@ # ***** END GPL LICENCE BLOCK ***** # here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. - -import bpy +from math import ( + ceil, + pi +) +from pathlib import Path +import pickle +import shutil +import sys import time -import mathutils -import math -from math import * -from mathutils import * -from bpy_extras import object_utils -import sys import numpy -import pickle +import shapely +from shapely import ops as sops +from shapely import geometry as sgeometry +from shapely.geometry import polygon as spolygon +from shapely.geometry import MultiPolygon -from .chunk import * -from .collision import * -from .simple import * -from .pattern import * -from .polygon_utils_cam import * -from .image_utils import * -from .exception import * +import bpy +from bpy.app.handlers import persistent +from bpy_extras import object_utils +from mathutils import Euler, Vector from .async_op import progress_async +from .cam_chunk import ( + curveToChunks, + parentChild, + camPathChunk, + camPathChunkBuilder, + parentChildDist, + chunksToShapely +) +from .collision import ( + getSampleBullet, + getSampleBulletNAxis, + prepareBulletCollision +) +from .exception import CamException +from .image_utils import ( + imageToChunks, + getSampleImage, + renderSampleImage, + prepareArea, +) from .opencamlib.opencamlib import ( oclSample, - oclSamplePoints, oclResampleChunks, - oclGetWaterline ) - -from shapely.geometry import polygon as spolygon -from shapely.geometry import MultiPolygon -from shapely import ops as sops -from shapely import geometry as sgeometry +from .polygon_utils_cam import shapelyToCurve +from .simple import ( + activate, + progress, + select_multiple, + delob, + timingadd, + timinginit, + timingstart, + tuple_add, + tuple_mul, + tuple_sub, + isVerticalLimit, + getCachePath +) # from shapely.geometry import * not possible until Polygon libs gets out finally.. SHAPELY = True @@ -959,8 +988,8 @@ def polygonConvexHull(context): c = (v.co.x, v.co.y) coords.append(c) - simple.select_multiple('_tmp') # delete temporary mesh - simple.select_multiple('ConvexHull') # delete old hull + select_multiple('_tmp') # delete temporary mesh + select_multiple('ConvexHull') # delete old hull # convert coordinates to shapely MultiPoint datastructure points = sgeometry.MultiPoint(coords) @@ -973,9 +1002,8 @@ def polygonConvexHull(context): def Helix(r, np, zstart, pend, rev): c = [] - pi = math.pi - v = mathutils.Vector((r, 0, zstart)) - e = mathutils.Euler((0, 0, 2.0 * pi / np)) + v = Vector((r, 0, zstart)) + e = Euler((0, 0, 2.0 * pi / np)) zstep = (zstart - pend[2]) / (np * rev) for a in range(0, int(np * rev)): c.append((v.x + pend[0], v.y + pend[1], zstart - (a * zstep))) @@ -1707,7 +1735,7 @@ def rotTo2axes(e, axescombination): # print(v) # print(bangle) - return (angle1, angle2) + # return (angle1, angle2) def reload_paths(o): @@ -1786,7 +1814,7 @@ def setup_operation_preset(): def updateMachine(self, context): print('update machine ') - if not utils._IS_LOADING_DEFAULTS: + if not _IS_LOADING_DEFAULTS: addMachineAreaObject() @@ -1909,7 +1937,7 @@ def updateChipload(self, context): # underestanding of python or programming in genereal. Hopefuly some one can have a look at this and with any luck # we will be one tiny step on the way to a slightly better chipload calculating function. - # self.chipload = ((0.5*(o.cutter_diameter/o.dist_between_paths))/(math.sqrt((o.feedrate*1000)/(o.spindle_rpm*o.cutter_diameter*o.cutter_flutes)*(o.cutter_diameter/o.dist_between_paths)-1))) + # self.chipload = ((0.5*(o.cutter_diameter/o.dist_between_paths))/(sqrt((o.feedrate*1000)/(o.spindle_rpm*o.cutter_diameter*o.cutter_flutes)*(o.cutter_diameter/o.dist_between_paths)-1))) print(o.info.chipload) @@ -2067,3 +2095,88 @@ def update_zbuffer_image(self, context): # from . import updateZbufferImage active_op = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation] updateZbufferImage(active_op, bpy.context) + + +# Moved from init - part 3 + +@bpy.app.handlers.persistent +def check_operations_on_load(context): + """checks any broken computations on load and reset them.""" + s = bpy.context.scene + for o in s.cam_operations: + if o.computing: + o.computing = False + # set interface level to previously used level for a new file + if not bpy.data.filepath: + _IS_LOADING_DEFAULTS = True + s.interface.level = bpy.context.preferences.addons["cam"].preferences.default_interface_level + machine_preset = bpy.context.preferences.addons[ + "cam"].preferences.machine_preset = bpy.context.preferences.addons["cam"].preferences.default_machine_preset + if len(machine_preset) > 0: + print("Loading preset:", machine_preset) + # load last used machine preset + bpy.ops.script.execute_preset( + filepath=machine_preset, menu_idname="CAM_MACHINE_MT_presets" + ) + _IS_LOADING_DEFAULTS = False + # check for updated version of the plugin + bpy.ops.render.cam_check_updates() + # copy presets if not there yet + if bpy.context.preferences.addons["cam"].preferences.just_updated: + preset_source_path = Path(__file__).parent / "presets" + preset_target_path = Path(bpy.utils.script_path_user()) / "presets" + + def copy_if_not_exists(src, dst): + if Path(dst).exists() == False: + shutil.copy2(src, dst) + + shutil.copytree( + preset_source_path, + preset_target_path, + copy_function=copy_if_not_exists, + dirs_exist_ok=True, + ) + + bpy.context.preferences.addons["cam"].preferences.just_updated = False + bpy.ops.wm.save_userpref() + + if not bpy.context.preferences.addons["cam"].preferences.op_preset_update: + # Update the Operation presets + op_presets_source = Path(__file__).parent / "presets" / "cam_operations" + op_presets_target = Path(bpy.utils.script_path_user()) / "presets" / "cam_operations" + shutil.copytree(op_presets_source, op_presets_target, dirs_exist_ok=True) + bpy.context.preferences.addons["cam"].preferences.op_preset_update = True + + +# add pocket op for medial axis and profile cut inside to clean unremoved material +def Add_Pocket(self, maxdepth, sname, new_cutter_diameter): + bpy.ops.object.select_all(action='DESELECT') + s = bpy.context.scene + mpocket_exists = False + for ob in s.objects: # delete old medial pocket + if ob.name.startswith("medial_poc"): + ob.select_set(True) + bpy.ops.object.delete() + + for op in s.cam_operations: # verify medial pocket operation exists + if op.name == "MedialPocket": + mpocket_exists = True + + ob = bpy.data.objects[sname] + ob.select_set(True) + bpy.context.view_layer.objects.active = ob + silhoueteOffset(ob, -new_cutter_diameter/2, 1, 0.3) + bpy.context.active_object.name = 'medial_pocket' + + if not mpocket_exists: # create a pocket operation if it does not exist already + s.cam_operations.add() + o = s.cam_operations[-1] + o.object_name = 'medial_pocket' + s.cam_active_operation = len(s.cam_operations) - 1 + o.name = 'MedialPocket' + o.filename = o.name + o.strategy = 'POCKET' + o.use_layers = False + o.material.estimate_from_model = False + o.material.size[2] = -maxdepth + o.minz_from = 'MATERIAL' diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index 58cc84d41..eb9ecdf17 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -63,7 +63,6 @@ ############################################################################# import math import sys -import getopt TOLERANCE = 1e-9 BIG_FLOAT = 1e38 From 00910eaa15e5c58eecaa6131af4e5a1f63c208b2 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 11:11:09 -0400 Subject: [PATCH 057/100] Import and init cleanup All imports made explicit All redundant and unused imports removed All circular import errors resolved Imports alphabetized and sorted by source, in the following order: - Python Standard Library - pip Packages - Blender Libraries - cam Modules (from .) init split into multiple files: New: - cam_operation - chain - engine - machine_settings - preset_managers Existing: - pack - slice - ui - utils Rewrote get_panels to remove legacy panels and updated to follow Blender's guide: https://docs.blender.org/api/current/bpy.types.RenderEngine.html --- scripts/addons/cam/tests/gcode_generator.py | 3 ++- scripts/addons/cam/tests/install_addon.py | 7 ++++--- scripts/addons/cam/tests/test_suite.py | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/addons/cam/tests/gcode_generator.py b/scripts/addons/cam/tests/gcode_generator.py index a74adb8c2..79a012b18 100644 --- a/scripts/addons/cam/tests/gcode_generator.py +++ b/scripts/addons/cam/tests/gcode_generator.py @@ -1,7 +1,8 @@ -import bpy import sys import warnings +import bpy + warnings.simplefilter("once") # Get the scene diff --git a/scripts/addons/cam/tests/install_addon.py b/scripts/addons/cam/tests/install_addon.py index fd55faa51..9470bf56d 100644 --- a/scripts/addons/cam/tests/install_addon.py +++ b/scripts/addons/cam/tests/install_addon.py @@ -1,8 +1,9 @@ -import tempfile -import sys -import subprocess import pathlib import shutil +import subprocess +import sys +import tempfile + INSTALL_CODE = f""" import bpy diff --git a/scripts/addons/cam/tests/test_suite.py b/scripts/addons/cam/tests/test_suite.py index 6fcbf386f..d15b3cdde 100644 --- a/scripts/addons/cam/tests/test_suite.py +++ b/scripts/addons/cam/tests/test_suite.py @@ -1,8 +1,8 @@ import difflib -import unittest -import subprocess import os +import subprocess import sys +import unittest class BlenderCAMTest(unittest.TestCase): From a2a6eb0408e32cef66edfc147bc876b19e13ef21 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 11:12:24 -0400 Subject: [PATCH 058/100] Import and init cleanup All imports made explicit All redundant and unused imports removed All circular import errors resolved Imports alphabetized and sorted by source, in the following order: - Python Standard Library - pip Packages - Blender Libraries - cam Modules (from .) init split into multiple files: New: - cam_operation - chain - engine - machine_settings - preset_managers Existing: - pack - slice - ui - utils Rewrote get_panels to remove legacy panels and updated to follow Blender's guide: https://docs.blender.org/api/current/bpy.types.RenderEngine.html --- scripts/addons/cam/opencamlib/oclSample.py | 15 ++++++---- scripts/addons/cam/opencamlib/opencamlib.py | 31 ++++++++++----------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index e29767326..6be08d42d 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -1,4 +1,8 @@ -import os +from math import ( + radians, + tan +) + try: import ocl except ImportError: @@ -6,13 +10,12 @@ import opencamlib as ocl except ImportError: pass -import tempfile from io_mesh_stl import blender_utils import mathutils -import math + from ..simple import activate -from ..exception import * +from ..exception import CamException from ..async_op import progress_async OCL_SCALE = 1000.0 @@ -50,9 +53,9 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): op_cutter_type = operation.cutter_type op_cutter_diameter = operation.cutter_diameter op_minz = operation.minz - op_cutter_tip_angle = math.radians(operation.cutter_tip_angle)/2 + op_cutter_tip_angle = radians(operation.cutter_tip_angle)/2 if op_cutter_type == "VCARVE": - cutter_length = (op_cutter_diameter/math.tan(op_cutter_tip_angle))/2 + cutter_length = (op_cutter_diameter/tan(op_cutter_tip_angle))/2 else: cutter_length = 10 diff --git a/scripts/addons/cam/opencamlib/opencamlib.py b/scripts/addons/cam/opencamlib/opencamlib.py index 8ca9e4150..d84e2680d 100644 --- a/scripts/addons/cam/opencamlib/opencamlib.py +++ b/scripts/addons/cam/opencamlib/opencamlib.py @@ -1,6 +1,9 @@ # used by OpenCAMLib sampling +import os +from subprocess import call +import tempfile -import bpy +import numpy as np try: import ocl except ImportError: @@ -8,18 +11,14 @@ import opencamlib as ocl except ImportError: pass -import os -import tempfile -import numpy as np -from subprocess import call -from ..collision import BULLET_SCALE -from .. import simple +import bpy + +from ..constants import BULLET_SCALE +from ..simple import activate from .. import utils -from ..chunk import camPathChunk -from ..simple import * +from ..cam_chunk import camPathChunk from ..async_op import progress_async -from shapely import geometry as sgeometry from .oclSample import ( get_oclSTL, ocl_sample @@ -146,12 +145,12 @@ def oclWaterlineLayerHeights(operation): return layers -def oclGetMedialAxis(operation, chunks): - oclWaterlineHeightsToOCL(operation) - operationSettingsToOCL(operation) - curvesToOCL(operation) - call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")]) - waterlineChunksFromOCL(operation, chunks) +# def oclGetMedialAxis(operation, chunks): +# oclWaterlineHeightsToOCL(operation) +# operationSettingsToOCL(operation) +# curvesToOCL(operation) +# call([PYTHON_BIN, os.path.join(bpy.utils.script_path_pref(), "addons", "cam", "opencamlib", "ocl.py")]) +# waterlineChunksFromOCL(operation, chunks) async def oclGetWaterline(operation, chunks): From e924c246c0dff6ec0f6d86eeb47887f01f572f8d Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 12:45:32 -0400 Subject: [PATCH 059/100] Delete scripts/addons/cam/chunk.py --- scripts/addons/cam/chunk.py | 1257 ----------------------------------- 1 file changed, 1257 deletions(-) delete mode 100644 scripts/addons/cam/chunk.py diff --git a/scripts/addons/cam/chunk.py b/scripts/addons/cam/chunk.py deleted file mode 100644 index b2071df38..000000000 --- a/scripts/addons/cam/chunk.py +++ /dev/null @@ -1,1257 +0,0 @@ -# blender CAM chunk.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - - -import shapely -from shapely.geometry import polygon as spolygon -from shapely import geometry as sgeometry -from . import polygon_utils_cam -from .simple import * -from .exception import CamException -from .numba_wrapper import jit, prange - -import math -import numpy as np - - -def Rotate_pbyp(originp, p, ang): # rotate point around another point with angle - ox, oy, oz = originp - px, py, oz = p - - if ang == abs(math.pi / 2): - d = ang / abs(ang) - qx = ox + d * (oy - py) - qy = oy + d * (px - ox) - else: - qx = ox + math.cos(ang) * (px - ox) - math.sin(ang) * (py - oy) - qy = oy + math.sin(ang) * (px - ox) + math.cos(ang) * (py - oy) - rot_p = [qx, qy, oz] - return rot_p - - -@jit(nopython=True, parallel=True, fastmath=True, cache=True) -def _internalXyDistanceTo(ourpoints, theirpoints, cutoff): - v1 = ourpoints[0] - v2 = theirpoints[0] - minDistSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 - cutoffSq = cutoff**2 - for v1 in ourpoints: - for v2 in theirpoints: - distSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 - if distSq < cutoffSq: - return sqrt(distSq) - minDistSq = min(distSq, minDistSq) - return sqrt(minDistSq) - - -# for building points - stores points as lists for easy insert /append behaviour -class camPathChunkBuilder: - def __init__(self, inpoints=None, startpoints=None, endpoints=None, rotations=None): - if inpoints is None: - inpoints = [] - self.points = inpoints - self.startpoints = startpoints or [] - self.endpoints = endpoints or [] - self.rotations = rotations or [] - self.depth = None - - def to_chunk(self): - chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) - if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): - chunk.closed = True - if self.depth is not None: - chunk.depth = self.depth - - return chunk - -# an actual chunk - stores points as numpy arrays - - -class camPathChunk: - # parents=[] - # children=[] - # sorted=False - - # progressIndex=-1# for e.g. parallel strategy, when trying to save time.. - def __init__(self, inpoints, startpoints=None, endpoints=None, rotations=None): - # name this as _points so nothing external accesses it directly - # for 3 axes, this is only storage of points. For N axes, here go the sampled points - if len(inpoints) == 0: - self.points = np.empty(shape=(0, 3)) - else: - self.points = np.array(inpoints) - self.poly = None # get polygon just in time - self.simppoly = None - if startpoints: - # from where the sweep test begins, but also retract point for given path - self.startpoints = startpoints - else: - self.startpoints = [] - if endpoints: - self.endpoints = endpoints - else: - self.endpoints = [] # where sweep test ends - if rotations: - self.rotations = rotations - else: - self.rotations = [] # rotation of the machine axes - self.closed = False - self.children = [] - self.parents = [] - # self.unsortedchildren=False - self.sorted = False # if the chunk has allready been milled in the simulation - self.length = 0 # this is total length of this chunk. - self.zstart = 0 # this is stored for ramps mainly, - # because they are added afterwards, but have to use layer info - self.zend = 0 # - - def update_poly(self): - if len(self.points) > 2: - self.poly = sgeometry.Polygon(self.points[:, 0:2]) - else: - self.poly = sgeometry.Polygon() - - def get_point(self, n): - return self.points[n].tolist() - - def get_points(self): - return self.points.tolist() - - def get_points_np(self): - return self.points - - def set_points(self, points): - self.points = np.array(points) - - def count(self): - return len(self.points) - - def copy(self): - nchunk = camPathChunk(inpoints=self.points.copy(), startpoints=self.startpoints, - endpoints=self.endpoints, rotations=self.rotations) - nchunk.closed = self.closed - nchunk.children = self.children - nchunk.parents = self.parents - nchunk.sorted = self.sorted - nchunk.length = self.length - return nchunk - - def shift(self, x, y, z): - self.points = self.points + np.array([x, y, z]) - for i, p in enumerate(self.startpoints): - self.startpoints[i] = (p[0] + x, p[1] + y, p[2] + z) - for i, p in enumerate(self.endpoints): - self.endpoints[i] = (p[0] + x, p[1] + y, p[2] + z) - - def setZ(self, z, if_bigger=False): - if if_bigger: - self.points[:, 2] = z if z > self.points[:, 2] else self.points[:, 2] - else: - self.points[:, 2] = z - - def offsetZ(self, z): - self.points[:, 2] += z - - def flipX(self, x_centre): - self.points[:, 0] = x_centre - self.points[:, 0] - - def isbelowZ(self, z): - return np.any(self.points[:, 2] < z) - - def clampZ(self, z): - np.clip(self.points[:, 2], z, None, self.points[:, 2]) - - def clampmaxZ(self, z): - np.clip(self.points[:, 2], None, z, self.points[:, 2]) - - def dist(self, pos, o): - if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 - return sqrt(np.min(dist_sq)) - else: - if o.movement.type == 'MEANDER': - d1 = dist2d(pos, self.points[0]) - d2 = dist2d(pos, self.points[-1]) - # if d22 points - # simplify them if they aren't already, to speed up distance finding - if self.simppoly is None: - self.simppoly = self.poly.simplify(0.0003).boundary - if other.simppoly is None: - other.simppoly = other.poly.simplify(0.0003).boundary - return self.simppoly.distance(other.simppoly) - else: # this is the old method, preferably should be replaced in most cases except parallel - # where this method works probably faster. - # print('warning, sorting will be slow due to bad parenting in parentChildDist') - return _internalXyDistanceTo(self.points, other.points, cutoff) - - def adaptdist(self, pos, o): - # reorders chunk so that it starts at the closest point to pos. - if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 - point_idx = np.argmin(dist_sq) - new_points = np.concatenate((self.points[point_idx:], self.points[:point_idx+1])) - self.points = new_points - else: - if o.movement.type == 'MEANDER': - d1 = dist2d(pos, self.points[0]) - d2 = dist2d(pos, self.points[-1]) - if d2 < d1: - self.points = np.flip(self.points, axis=0) - - def getNextClosest(self, o, pos): - # finds closest chunk that can be milled, when inside sorting hierarchy. - mind = 100000000000 - - self.cango = False - closest = None - testlist = [] - testlist.extend(self.children) - tested = [] - tested.extend(self.children) - ch = None - while len(testlist) > 0: - chtest = testlist.pop() - if not chtest.sorted: - self.cango = False - cango = True - - for child in chtest.children: - if not child.sorted: - if child not in tested: - testlist.append(child) - tested.append(child) - cango = False - - if cango: - d = chtest.dist(pos, o) - if d < mind: - ch = chtest - mind = d - if ch is not None: - # print('found some') - return ch - # print('returning none') - return None - - def getLength(self): - # computes length of the chunk - in 3d - - point_differences = self.points[0:-1, :] - self.points[1:, :] - distances = np.linalg.norm(point_differences, axis=1) - self.length = np.sum(distances) - - def reverse(self): - self.points = np.flip(self.points, axis=0) - self.startpoints.reverse() - self.endpoints.reverse() - self.rotations.reverse() - - def pop(self, index): - print("WARNING: Popping from chunk is slow", self, index) - self.points = np.concatenate((self.points[0:index], self.points[index+1:]), axis=0) - if len(self.startpoints) > 0: - self.startpoints.pop(index) - self.endpoints.pop(index) - self.rotations.pop(index) - - def dedupePoints(self): - if len(self.points) > 1: - keep_points = np.empty(self.points.shape[0], dtype=bool) - keep_points[0] = True - diff_points = np.sum((self.points[1:]-self.points[:1])**2, axis=1) - keep_points[1:] = diff_points > 0.000000001 - self.points = self.points[keep_points, :] - - def insert(self, at_index, point, startpoint=None, endpoint=None, rotation=None): - self.append(point, startpoint=startpoint, endpoint=endpoint, - rotation=rotation, at_index=at_index) - - def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index=None): - if at_index is None: - self.points = np.concatenate((self.points, np.array([point]))) - if startpoint is not None: - self.startpoints.append(startpoint) - if endpoint is not None: - self.endpoints.append(endpoint) - if rotation is not None: - self.rotations.append(rotation) - else: - self.points = np.concatenate( - (self.points[0:at_index], np.array([point]), self.points[at_index:])) - if startpoint is not None: - self.startpoints[at_index:at_index] = [startpoint] - if endpoint is not None: - self.endpoints[at_index:at_index] = [endpoint] - if rotation is not None: - self.rotations[at_index:at_index] = [rotation] - - def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_index=None): - if len(points) == 0: - return - if at_index is None: - self.points = np.concatenate((self.points, np.array(points))) - if startpoints is not None: - self.startpoints.extend(startpoints) - if endpoints is not None: - self.endpoints.extend(endpoints) - if rotations is not None: - self.rotations.extend(rotations) - else: - self.points = np.concatenate( - (self.points[0:at_index], np.array(points), self.points[at_index:])) - if startpoints is not None: - self.startpoints[at_index:at_index] = startpoints - if endpoints is not None: - self.endpoints[at_index:at_index] = endpoints - if rotations is not None: - self.rotations[at_index:at_index] = rotations - - def clip_points(self, minx, maxx, miny, maxy): - """ remove any points outside this range """ - included_values = (self.points[:, 0] >= minx) and ((self.points[:, 0] <= maxx) - and (self.points[:, 1] >= maxy) and (self.points[:, 1] <= maxy)) - self.points = self.points[included_values] - - def rampContour(self, zstart, zend, o): - - stepdown = zstart - zend - chunk_points = [] - estlength = (zstart - zend) / tan(o.movement.ramp_in_angle) - self.getLength() - ramplength = estlength # min(ch.length,estlength) - ltraveled = 0 - endpoint = None - i = 0 - # z=zstart - znew = 10 - rounds = 0 # for counting if ramping makes more layers - while endpoint is None and not (znew == zend and i == 0): # - # for i,s in enumerate(ch.points): - # print(i, znew, zend, len(ch.points)) - s = self.points[i] - - if i > 0: - s2 = self.points[i - 1] - ltraveled += dist2d(s, s2) - ratio = ltraveled / ramplength - elif rounds > 0 and i == 0: - s2 = self.points[-1] - ltraveled += dist2d(s, s2) - ratio = ltraveled / ramplength - else: - ratio = 0 - znew = zstart - stepdown * ratio - if znew <= zend: - - ratio = ((z - zend) / (z - znew)) - v1 = Vector(chunk_points[-1]) - v2 = Vector((s[0], s[1], znew)) - v = v1 + ratio * (v2 - v1) - chunk_points.append((v.x, v.y, max(s[2], v.z))) - - if zend == o.min.z and endpoint is None and self.closed: - endpoint = i + 1 - if endpoint == len(self.points): - endpoint = 0 - # print(endpoint,len(ch.points)) - # else: - znew = max(znew, zend, s[2]) - chunk_points.append((s[0], s[1], znew)) - z = znew - if endpoint is not None: - break - i += 1 - if i >= len(self.points): - i = 0 - rounds += 1 - # if not o.use_layers: - # endpoint=0 - if endpoint is not None: # append final contour on the bottom z level - i = endpoint - started = False - # print('finaliz') - if i == len(self.points): - i = 0 - while i != endpoint or not started: - started = True - s = self.points[i] - chunk_points.append((s[0], s[1], s[2])) - # print(i,endpoint) - i += 1 - if i == len(self.points): - i = 0 - # ramp out - if o.movement.ramp_out and (not o.use_layers or not o.first_down or (o.first_down and endpoint is not None)): - z = zend - # i=endpoint - - while z < o.maxz: - if i == len(self.points): - i = 0 - s1 = self.points[i] - i2 = i - 1 - if i2 < 0: - i2 = len(self.points) - 1 - s2 = self.points[i2] - l = dist2d(s1, s2) - znew = z + tan(o.movement.ramp_out_angle) * l - if znew > o.maxz: - ratio = ((z - o.maxz) / (z - znew)) - v1 = Vector(chunk_points[-1]) - v2 = Vector((s1[0], s1[1], znew)) - v = v1 + ratio * (v2 - v1) - chunk_points.append((v.x, v.y, v.z)) - - else: - chunk_points.append((s1[0], s1[1], znew)) - z = znew - i += 1 - - # TODO: convert to numpy properly - self.points = np.array(chunk_points) - - def rampZigZag(self, zstart, zend, o): - # TODO: convert to numpy properly - if zend == None: - zend = self.points[0][2] - chunk_points = [] - # print(zstart,zend) - if zend < zstart: # this check here is only for stupid setup, - # when the chunks lie actually above operation start z. - - stepdown = zstart - zend - - estlength = (zstart - zend) / tan(o.movement.ramp_in_angle) - self.getLength() - if self.length > 0: # for single point chunks.. - ramplength = estlength - zigzaglength = ramplength / 2.000 - turns = 1 - print('turns %i' % turns) - if zigzaglength > self.length: - turns = ceil(zigzaglength / self.length) - ramplength = turns * self.length * 2.0 - zigzaglength = self.length - ramppoints = self.points.tolist() - - else: - zigzagtraveled = 0.0 - haspoints = False - ramppoints = [(self.points[0][0], self.points[0][1], self.points[0][2])] - i = 1 - while not haspoints: - # print(i,zigzaglength,zigzagtraveled) - p1 = ramppoints[-1] - p2 = self.points[i] - d = dist2d(p1, p2) - zigzagtraveled += d - if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): - ratio = 1 - (zigzagtraveled - zigzaglength) / d - if (i + 1 == len( - self.points)): # this condition is for a rare case of combined layers+bridges+ramps.. - ratio = 1 - v = p1 + ratio * (p2 - p1) - ramppoints.append(v.tolist()) - haspoints = True - else: - ramppoints.append(p2) - i += 1 - negramppoints = ramppoints.copy() - negramppoints.reverse() - ramppoints.extend(negramppoints[1:]) - - traveled = 0.0 - chunk_points.append( - (self.points[0][0], self.points[0][1], max(self.points[0][2], zstart))) - for r in range(turns): - for p in range(0, len(ramppoints)): - p1 = chunk_points[-1] - p2 = ramppoints[p] - d = dist2d(p1, p2) - traveled += d - ratio = traveled / ramplength - znew = zstart - stepdown * ratio - # max value here is so that it doesn't go - chunk_points.append((p2[0], p2[1], max(p2[2], znew))) - # below surface in the case of 3d paths - - # chunks = setChunksZ([ch],zend) - chunk_points.extend(self.points.tolist()) - - ###################################### - # ramp out - this is the same thing, just on the other side.. - if o.movement.ramp_out: - zstart = o.maxz - zend = self.points[-1][2] - # again, sometimes a chunk could theoretically end above the starting level. - if zend < zstart: - stepdown = zstart - zend - - estlength = (zstart - zend) / tan(o.movement.ramp_out_angle) - self.getLength() - if self.length > 0: - ramplength = estlength - zigzaglength = ramplength / 2.000 - turns = 1 - print('turns %i' % turns) - if zigzaglength > self.length: - turns = ceil(zigzaglength / self.length) - ramplength = turns * self.length * 2.0 - zigzaglength = self.length - ramppoints = self.points.tolist() - # revert points here, we go the other way. - ramppoints.reverse() - - else: - zigzagtraveled = 0.0 - haspoints = False - ramppoints = [(self.points[-1][0], self.points[-1] - [1], self.points[-1][2])] - i = len(self.points) - 2 - while not haspoints: - # print(i,zigzaglength,zigzagtraveled) - p1 = ramppoints[-1] - p2 = self.points[i] - d = dist2d(p1, p2) - zigzagtraveled += d - if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): - ratio = 1 - (zigzagtraveled - zigzaglength) / d - if (i + 1 == len( - self.points)): # this condition is for a rare case of - # combined layers+bridges+ramps... - ratio = 1 - # print((ratio,zigzaglength)) - v = p1 + ratio * (p2 - p1) - ramppoints.append(v.tolist()) - haspoints = True - # elif : - - else: - ramppoints.append(p2) - i -= 1 - negramppoints = ramppoints.copy() - negramppoints.reverse() - ramppoints.extend(negramppoints[1:]) - - traveled = 0.0 - for r in range(turns): - for p in range(0, len(ramppoints)): - p1 = chunk_points[-1] - p2 = ramppoints[p] - d = dist2d(p1, p2) - traveled += d - ratio = 1 - (traveled / ramplength) - znew = zstart - stepdown * ratio - chunk_points.append((p2[0], p2[1], max(p2[2], znew))) - # max value here is so that it doesn't go below surface in the case of 3d paths - self.points = np.array(chunk_points) - - # modify existing path start point - def changePathStart(self, o): - if o.profile_start > 0: - newstart = o.profile_start - chunkamt = len(self.points) - newstart = newstart % chunkamt - self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) - - def breakPathForLeadinLeadout(self, o): - iradius = o.lead_in - oradius = o.lead_out - if iradius + oradius > 0: - chunkamt = len(self.points) - - for i in range(chunkamt - 1): - apoint = self.points[i] - bpoint = self.points[i + 1] - bmax = bpoint[0] - apoint[0] - bmay = bpoint[1] - apoint[1] - segmentLength = math.hypot(bmax, bmay) # find segment length - - if segmentLength > 2 * max(iradius, - oradius): # Be certain there is enough room for the leadin and leadiout - # add point on the line here - # average of the two x points to find center - newpointx = (bpoint[0] + apoint[0]) / 2 - # average of the two y points to find center - newpointy = (bpoint[1] + apoint[1]) / 2 - self.points = np.concatenate( - (self.points[:i+1], np.array([[newpointx, newpointy, apoint[2]]]), self.points[i+1:])) - - def leadContour(self, o): - perimeterDirection = 1 # 1 is clockwise, 0 is CCW - if o.movement.spindle_rotation == 'CW': - if o.movement.type == 'CONVENTIONAL': - perimeterDirection = 0 - - if self.parents: # if it is inside another parent - perimeterDirection ^= 1 # toggle with a bitwise XOR - print("has parent") - - if perimeterDirection == 1: - print("path direction is Clockwise") - else: - print("path direction is counterclockwise") - iradius = o.lead_in - oradius = o.lead_out - start = self.points[0] - nextp = self.points[1] - rpoint = Rotate_pbyp(start, nextp, math.pi / 2) - dx = rpoint[0] - start[0] - dy = rpoint[1] - start[1] - la = math.hypot(dx, dy) - pvx = (iradius * dx) / la + start[0] # arc center(x) - pvy = (iradius * dy) / la + start[1] # arc center(y) - arc_c = [pvx, pvy, start[2]] - - # TODO: this could easily be numpy - chunk_points = [] # create a new cutting path - - # add lead in arc in the begining - if round(o.lead_in, 6) > 0.0: - for i in range(15): - iangle = -i * (math.pi / 2) / 15 - arc_p = Rotate_pbyp(arc_c, start, iangle) - chunk_points.insert(0, arc_p) - - # glue rest of the path to the arc - chunk_points.extend(self.points.tolist()) - # for i in range(len(self.points)): - # chunk_points.append(self.points[i]) - - # add lead out arc to the end - if round(o.lead_in, 6) > 0.0: - for i in range(15): - iangle = i * (math.pi / 2) / 15 - arc_p = Rotate_pbyp(arc_c, start, iangle) - chunk_points.append(arc_p) - - self.points = np.array(chunk_points) - - -def chunksCoherency(chunks): - # checks chunks for their stability, for pencil path. - # it checks if the vectors direction doesn't jump too much too quickly, - # if this happens it splits the chunk on such places, - # too much jumps = deletion of the chunk. this is because otherwise the router has to slow down too often, - # but also means that some parts detected by cavity algorithm won't be milled - nchunks = [] - for chunk in chunks: - if len(chunk.points) > 2: - nchunk = camPathChunkBuilder() - - # doesn't check for 1 point chunks here, they shouldn't get here at all. - lastvec = Vector(chunk.points[1]) - Vector(chunk.points[0]) - for i in range(0, len(chunk.points) - 1): - nchunk.points.append(chunk.points[i]) - vec = Vector(chunk.points[i + 1]) - Vector(chunk.points[i]) - angle = vec.angle(lastvec, vec) - # print(angle,i) - if angle > 1.07: # 60 degrees is maximum toleration for pencil paths. - if len(nchunk_points) > 4: # this is a testing threshold - nchunks.append(nchunk.to_chunk()) - nchunk = camPathChunkBuilder() - lastvec = vec - if len(nchunk_points) > 4: # this is a testing threshold - nchunk.points = np.array(nchunk_points) - nchunks.append(nchunk) - return nchunks - - -def setChunksZ(chunks, z): - newchunks = [] - for ch in chunks: - chunk = ch.copy() - chunk.setZ(z) - newchunks.append(chunk) - return newchunks - -# don't make this @jit parallel, because it sometimes gets called with small N -# and the overhead of threading is too much. - - -@jit(nopython=True, fastmath=True, cache=True) -def _optimize_internal(points, keep_points, e, protect_vertical, protect_vertical_limit): - # inlined so that numba can optimize it nicely - def _mag_sq(v1): - return v1[0]**2 + v1[1]**2 + v1[2]**2 - - def _dot_pr(v1, v2): - return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] - - def _applyVerticalLimit(v1, v2, cos_limit): - """test path segment on verticality threshold, for protect_vertical option""" - z = abs(v1[2] - v2[2]) - if z > 0: - # don't use this vector because dot product of 0,0,1 is trivially just v2[2] - # vec_up = np.array([0, 0, 1]) - vec_diff = v1-v2 - vec_diff2 = v2-v1 - vec_diff_mag = np.sqrt(_mag_sq(vec_diff)) - # dot product = cos(angle) * mag1 * mag2 - cos1_times_mag = vec_diff[2] - cos2_times_mag = vec_diff2[2] - if cos1_times_mag > cos_limit*vec_diff_mag: - # vertical, moving down - v1[0] = v2[0] - v1[1] = v2[1] - elif cos2_times_mag > cos_limit*vec_diff_mag: - # vertical, moving up - v2[0] = v1[0] - v2[1] = v1[1] - - cos_limit = cos(protect_vertical_limit) - prev_i = 0 - for i in range(1, points.shape[0]-1): - v1 = points[prev_i] - v2 = points[i+1] - vmiddle = points[i] - - line_direction = v2-v1 - line_length = sqrt(_mag_sq(line_direction)) - if line_length == 0: - # don't keep duplicate points - keep_points[i] = False - continue - # normalize line direction - line_direction *= (1.0/line_length) # N in formula below - # X = A + tN (line formula) Distance to point P - # A = v1, N = line_direction, P = vmiddle - # distance = || (P - A) - ((P-A).N)N || - point_offset = vmiddle - v1 - distance_sq = _mag_sq(point_offset - (line_direction * - _dot_pr(point_offset, line_direction))) - # compare on squared distance to save a sqrt - if distance_sq < e*e: - keep_points[i] = False - else: - keep_points[i] = True - if protect_vertical: - _applyVerticalLimit(points[prev_i], points[i], cos_limit) - prev_i = i - - -def optimizeChunk(chunk, operation): - if len(chunk.points) > 2: - points = chunk.points - naxispoints = False - if len(chunk.startpoints) > 0: - startpoints = chunk.startpoints - endpoints = chunk.endpoints - naxispoints = True - - protect_vertical = operation.movement.protect_vertical and operation.machine_axes == '3' - keep_points = np.full(points.shape[0], True) - # shape points need to be on line, - # but we need to protect vertical - which - # means changing point values - # bits of this are moved from simple.py so that - # numba can optimize as a whole - _optimize_internal(points, keep_points, operation.optimisation.optimize_threshold * - 0.000001, protect_vertical, operation.movement.protect_vertical_limit) - - # now do numpy select by boolean array - chunk.points = points[keep_points] - if naxispoints: - # list comprehension so we don't have to do tons of appends - chunk.startpoints = [chunk.startpoints[i] - for i, b in enumerate(keep_points) if b == True] - chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] - chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] - return chunk - - -def limitChunks(chunks, o, - force=False): # TODO: this should at least add point on area border... - # but shouldn't be needed at all at the first place... - if o.use_limit_curve or force: - nchunks = [] - for ch in chunks: - prevsampled = True - nch = camPathChunkBuilder() - nch1 = None - closed = True - for s in ch.points: - sampled = o.ambient.contains(sgeometry.Point(s[0], s[1])) - if not sampled and len(nch.points) > 0: - nch.closed = False - closed = False - nchunks.append(nch.to_chunk()) - if nch1 is None: - nch1 = nchunks[-1] - nch = camPathChunkBuilder() - elif sampled: - nch.points.append(s) - prevsampled = sampled - if len(nch.points) > 2 and closed and ch.closed and np.array_equal(ch.points[0], ch.points[-1]): - nch.closed = True - elif ch.closed and nch1 is not None and len(nch.points) > 1 and np.array_equal(nch.points[-1], nch1.points[0]): - # here adds beginning of closed chunk to the end, if the chunks were split during limiting - nch.points.extend(nch1.points.tolist()) - nchunks.remove(nch1) - print('joining stuff') - if len(nch.points) > 0: - nchunks.append(nch.to_chunk()) - return nchunks - else: - return chunks - - -def parentChildPoly(parents, children, o): - # hierarchy based on polygons - a polygon inside another is his child. - # hierarchy works like this: - children get milled first. - - for parent in parents: - if parent.poly is None: - parent.update_poly() - for child in children: - if child.poly is None: - child.update_poly() - if child != parent: # and len(child.poly)>0 - if parent.poly.contains(sgeometry.Point(child.poly.boundary.coords[0])): - parent.children.append(child) - child.parents.append(parent) - - -def parentChildDist(parents, children, o, distance=None): - # parenting based on x,y distance between chunks - # hierarchy works like this: - children get milled first. - - if distance is None: - dlim = o.dist_between_paths * 2 - if (o.strategy == 'PARALLEL' or o.strategy == 'CROSS') and o.movement.parallel_step_back: - dlim = dlim * 2 - else: - dlim = distance - - for child in children: - for parent in parents: - isrelation = False - if parent != child: - if parent.xyDistanceWithin(child, cutoff=dlim): - parent.children.append(child) - child.parents.append(parent) - - -def parentChild(parents, children, o): - # connect all children to all parents. Useful for any type of defining hierarchy. - # hierarchy works like this: - children get milled first. - - for child in children: - for parent in parents: - if parent != child: - parent.children.append(child) - child.parents.append(parent) - - -# this does more cleve chunks to Poly with hierarchies... ;) -def chunksToShapely(chunks): - # print ('analyzing paths') - for ch in chunks: # first convert chunk to poly - if len(ch.points) > 2: - # pchunk=[] - ch.poly = sgeometry.Polygon(ch.points[:, 0:2]) - if not ch.poly.is_valid: - ch.poly = sgeometry.Polygon() - else: - ch.poly = sgeometry.Polygon() - - for ppart in chunks: # then add hierarchy relations - for ptest in chunks: - if ppart != ptest: - if not ppart.poly.is_empty and not ptest.poly.is_empty: - if ptest.poly.contains(ppart.poly): - # hierarchy works like this: - children get milled first. - ppart.parents.append(ptest) - - for ch in chunks: # now make only simple polygons with holes, not more polys inside others - found = False - if len(ch.parents) % 2 == 1: - - for parent in ch.parents: - if len(parent.parents) + 1 == len(ch.parents): - # nparents serves as temporary storage for parents, - ch.nparents = [parent] - # not to get mixed with the first parenting during the check - found = True - break - - if not found: - ch.nparents = [] - - for ch in chunks: # then subtract the 1st level holes - ch.parents = ch.nparents - ch.nparents = None - if len(ch.parents) > 0: - - try: - ch.parents[0].poly = ch.parents[0].poly.difference( - ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) - except: - - print('chunksToShapely oops!') - - lastPt = None - tolerance = 0.0000003 - newPoints = [] - - for pt in ch.points: - toleranceXok = True - toleranceYok = True - if lastPt is not None: - if abs(pt[0] - lastPt[0]) < tolerance: - toleranceXok = False - if abs(pt[1] - lastPt[1]) < tolerance: - toleranceYok = False - - if toleranceXok or toleranceYok: - newPoints.append(pt) - lastPt = pt - else: - newPoints.append(pt) - lastPt = pt - - toleranceXok = True - toleranceYok = True - if abs(newPoints[0][0] - lastPt[0]) < tolerance: - toleranceXok = False - if abs(newPoints[0][1] - lastPt[1]) < tolerance: - toleranceYok = False - - if not toleranceXok and not toleranceYok: - newPoints.pop() - - ch.points = np.array(newPoints) - ch.poly = sgeometry.Polygon(ch.points) - - try: - ch.parents[0].poly = ch.parents[0].poly.difference(ch.poly) - except: - - # print('chunksToShapely double oops!') - - lastPt = None - tolerance = 0.0000003 - newPoints = [] - - for pt in ch.parents[0].points: - toleranceXok = True - toleranceYok = True - # print( '{0:.9f}, {0:.9f}, {0:.9f}'.format(pt[0], pt[1], pt[2]) ) - # print(pt) - if lastPt is not None: - if abs(pt[0] - lastPt[0]) < tolerance: - toleranceXok = False - if abs(pt[1] - lastPt[1]) < tolerance: - toleranceYok = False - - if toleranceXok or toleranceYok: - newPoints.append(pt) - lastPt = pt - else: - newPoints.append(pt) - lastPt = pt - - toleranceXok = True - toleranceYok = True - if abs(newPoints[0][0] - lastPt[0]) < tolerance: - toleranceXok = False - if abs(newPoints[0][1] - lastPt[1]) < tolerance: - toleranceYok = False - - if not toleranceXok and not toleranceYok: - newPoints.pop() - # print('starting and ending points too close, removing ending point') - - ch.parents[0].points = np.array(newPoints) - ch.parents[0].poly = sgeometry.Polygon(ch.parents[0].points) - - ch.parents[0].poly = ch.parents[0].poly.difference( - ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) - - returnpolys = [] - - for polyi in range(0, len(chunks)): # export only the booleaned polygons - ch = chunks[polyi] - if not ch.poly.is_empty: - if len(ch.parents) == 0: - returnpolys.append(ch.poly) - from shapely.geometry import MultiPolygon - polys = MultiPolygon(returnpolys) - return polys - - -def meshFromCurveToChunk(object): - mesh = object.data - # print('detecting contours from curve') - chunks = [] - chunk = camPathChunkBuilder() - ek = mesh.edge_keys - d = {} - for e in ek: - d[e] = 1 # - dk = d.keys() - x = object.location.x - y = object.location.y - z = object.location.z - lastvi = 0 - vtotal = len(mesh.vertices) - perc = 0 - progress('processing curve - START - Vertices: ' + str(vtotal)) - for vi in range(0, len(mesh.vertices) - 1): - co = (mesh.vertices[vi].co + object.location).to_tuple() - if not dk.isdisjoint([(vi, vi + 1)]) and d[(vi, vi + 1)] == 1: - chunk.points.append(co) - else: - chunk.points.append(co) - if len(chunk.points) > 2 and (not (dk.isdisjoint([(vi, lastvi)])) or not ( - dk.isdisjoint([(lastvi, vi)]))): # this was looping chunks of length of only 2 points... - # print('itis') - - chunk.closed = True - chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) - # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z - lastvi = vi + 1 - chunk = chunk.to_chunk() - chunk.dedupePoints() - if chunk.count() >= 1: - # dump single point chunks - chunks.append(chunk) - chunk = camPathChunkBuilder() - - progress('processing curve - FINISHED') - - vi = len(mesh.vertices) - 1 - chunk.points.append((mesh.vertices[vi].co.x + x, - mesh.vertices[vi].co.y + y, mesh.vertices[vi].co.z + z)) - if not (dk.isdisjoint([(vi, lastvi)])) or not (dk.isdisjoint([(lastvi, vi)])): - chunk.closed = True - chunk.points.append( - (mesh.vertices[lastvi].co.x + x, mesh.vertices[lastvi].co.y + y, mesh.vertices[lastvi].co.z + z)) - chunk = chunk.to_chunk() - chunk.dedupePoints() - if chunk.count() >= 1: - # dump single point chunks - chunks.append(chunk) - return chunks - - -def makeVisible(o): - storage = [True, []] - - if not o.visible_get(): - storage[0] = False - - cam_collection = D.collections.new("cam") - C.scene.collection.children.link(cam_collection) - cam_collection.objects.link(C.object) - - for i in range(0, 20): - storage[1].append(o.layers[i]) - - o.layers[i] = bpy.context.scene.layers[i] - - return storage - - -def restoreVisibility(o, storage): - o.hide_viewport = storage[0] - # print(storage) - for i in range(0, 20): - o.layers[i] = storage[1][i] - - -def meshFromCurve(o, use_modifiers=False): - activate(o) - bpy.ops.object.duplicate() - - bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') - - co = bpy.context.active_object - - # support for text objects is only and only here, just convert them to curves. - if co.type == 'FONT': - bpy.ops.object.convert(target='CURVE', keep_original=False) - elif co.type != 'CURVE': # curve must be a curve... - bpy.ops.object.delete() # delete temporary object - raise CamException("Source curve object must be of type CURVE") - co.data.dimensions = '3D' - co.data.bevel_depth = 0 - co.data.extrude = 0 - - # first, convert to mesh to avoid parenting issues with hooks, then apply locrotscale. - bpy.ops.object.convert(target='MESH', keep_original=False) - - if use_modifiers: - eval_object = co.evaluated_get(bpy.context.evaluated_depsgraph_get()) - newmesh = bpy.data.meshes.new_from_object(eval_object) - oldmesh = co.data - co.modifiers.clear() - co.data = newmesh - bpy.data.meshes.remove(oldmesh) - - try: - bpy.ops.object.transform_apply(location=True, rotation=False, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=True, scale=False) - bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) - - except: - pass - - return bpy.context.active_object - - -def curveToChunks(o, use_modifiers=False): - co = meshFromCurve(o, use_modifiers) - chunks = meshFromCurveToChunk(co) - - co = bpy.context.active_object - - bpy.ops.object.select_all(action='DESELECT') - bpy.data.objects[co.name].select_set(True) - bpy.ops.object.delete() - - return chunks - - -def shapelyToChunks(p, zlevel): # - chunk_builders = [] - # p=sortContours(p) - seq = polygon_utils_cam.shapelyToCoords(p) - i = 0 - for s in seq: - # progress(p[i]) - if len(s) > 1: - chunk = camPathChunkBuilder([]) - for v in s: - if p.has_z: - chunk.points.append((v[0], v[1], v[2])) - else: - chunk.points.append((v[0], v[1], zlevel)) - - chunk_builders.append(chunk) - i += 1 - chunk_builders.reverse() # this is for smaller shapes first. - return [c.to_chunk() for c in chunk_builders] - - -def chunkToShapely(chunk): - p = spolygon.Polygon(chunk.points) - return p - - -def chunksRefine(chunks, o): - """add extra points in between for chunks""" - for ch in chunks: - # print('before',len(ch)) - newchunk = [] - v2 = Vector(ch.points[0]) - # print(ch.points) - for s in ch.points: - - v1 = Vector(s) - v = v1 - v2 - - if v.length > o.dist_along_paths: - d = v.length - v.normalize() - i = 0 - vref = Vector((0, 0, 0)) - - while vref.length < d: - i += 1 - vref = v * o.dist_along_paths * i - if vref.length < d: - p = v2 + vref - - newchunk.append((p.x, p.y, p.z)) - - newchunk.append(s) - v2 = v1 - ch.points = np.array(newchunk) - - return chunks - - -def chunksRefineThreshold(chunks, distance, limitdistance): - """add extra points in between for chunks. For medial axis strategy only !""" - for ch in chunks: - newchunk = [] - v2 = Vector(ch.points[0]) - - for s in ch.points: - - v1 = Vector(s) - v = v1 - v2 - - if v.length > limitdistance: - d = v.length - v.normalize() - i = 1 - vref = Vector((0, 0, 0)) - while vref.length < d / 2: - - vref = v * distance * i - if vref.length < d: - p = v2 + vref - - newchunk.append((p.x, p.y, p.z)) - i += 1 - # because of the condition, so it doesn't run again. - vref = v * distance * i - while i > 0: - vref = v * distance * i - if vref.length < d: - p = v1 - vref - - newchunk.append((p.x, p.y, p.z)) - i -= 1 - - newchunk.append(s) - v2 = v1 - ch.points = np.array(newchunk) - - return chunks From 9a6e9612f3e9cabe9bfd05d6834d5e96daf871b0 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 12:47:42 -0400 Subject: [PATCH 060/100] Fixed var nchunk_points In the function `chunksCoherency` I had mistakenly changed `nchunk_points` to `nchunk.points`, this has been reverted. --- scripts/addons/cam/cam_chunk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py index cd5baedb0..0e0cf8432 100644 --- a/scripts/addons/cam/cam_chunk.py +++ b/scripts/addons/cam/cam_chunk.py @@ -705,12 +705,12 @@ def chunksCoherency(chunks): angle = vec.angle(lastvec, vec) # print(angle,i) if angle > 1.07: # 60 degrees is maximum toleration for pencil paths. - if len(nchunk.points) > 4: # this is a testing threshold + if len(nchunk_points) > 4: # this is a testing threshold nchunks.append(nchunk.to_chunk()) nchunk = camPathChunkBuilder() lastvec = vec - if len(nchunk.points) > 4: # this is a testing threshold - nchunk.points = np.array(nchunk.points) + if len(nchunk_points) > 4: # this is a testing threshold + nchunk.points = np.array(nchunk_points) nchunks.append(nchunk) return nchunks From 7f461d14b1805e71ec6db6216a8a16b782134245 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Tue, 2 Apr 2024 12:57:59 -0400 Subject: [PATCH 061/100] Changing `nchunk_points` back to `nchunk.points` I submitted the last commit after checking my `cam_chunk` file against the `chunk` file in Alain's repo to make sure I hadn't introduced any errors. When I saw that his code had `nchunk_points` I assumed that my change was a mistake, and I reverted it. However, upon closer inspection of the code, I believe that it should, in fact, be changed. There is no reference to `nchunk_points` elsewhere in the code, though there is a similarly named `chunk_points` variable. `nchunk` is an instance of `camPathChunkBuilder` which has `points`, but not `nchunk_points`. --- scripts/addons/cam/cam_chunk.py | 289 ++++++++++++++++++++++---------- 1 file changed, 198 insertions(+), 91 deletions(-) diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py index 0e0cf8432..16e0a85df 100644 --- a/scripts/addons/cam/cam_chunk.py +++ b/scripts/addons/cam/cam_chunk.py @@ -65,11 +65,11 @@ def Rotate_pbyp(originp, p, ang): # rotate point around another point with angl def _internalXyDistanceTo(ourpoints, theirpoints, cutoff): v1 = ourpoints[0] v2 = theirpoints[0] - minDistSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 + minDistSq = (v1[0] - v2[0]) ** 2 + (v1[1] - v2[1]) ** 2 cutoffSq = cutoff**2 for v1 in ourpoints: for v2 in theirpoints: - distSq = (v1[0]-v2[0])**2 + (v1[1]-v2[1])**2 + distSq = (v1[0] - v2[0]) ** 2 + (v1[1] - v2[1]) ** 2 if distSq < cutoffSq: return sqrt(distSq) minDistSq = min(distSq, minDistSq) @@ -88,7 +88,9 @@ def __init__(self, inpoints=None, startpoints=None, endpoints=None, rotations=No self.depth = None def to_chunk(self): - chunk = camPathChunk(self.points, self.startpoints, self.endpoints, self.rotations) + chunk = camPathChunk( + self.points, self.startpoints, self.endpoints, self.rotations + ) if len(self.points) > 2 and np.array_equal(self.points[0], self.points[-1]): chunk.closed = True if self.depth is not None: @@ -96,6 +98,7 @@ def to_chunk(self): return chunk + # an actual chunk - stores points as numpy arrays @@ -159,8 +162,12 @@ def count(self): return len(self.points) def copy(self): - nchunk = camPathChunk(inpoints=self.points.copy(), startpoints=self.startpoints, - endpoints=self.endpoints, rotations=self.rotations) + nchunk = camPathChunk( + inpoints=self.points.copy(), + startpoints=self.startpoints, + endpoints=self.endpoints, + rotations=self.rotations, + ) nchunk.closed = self.closed nchunk.children = self.children nchunk.parents = self.parents @@ -198,10 +205,12 @@ def clampmaxZ(self, z): def dist(self, pos, o): if self.closed: - dist_sq = (pos[0]-self.points[:, 0])**2 + (pos[1]-self.points[:, 1])**2 + dist_sq = (pos[0] - self.points[:, 0]) ** 2 + ( + pos[1] - self.points[:, 1] + ) ** 2 return sqrt(np.min(dist_sq)) else: - if o.movement.type == 'MEANDER': + if o.movement.type == "MEANDER": d1 = dist2d(pos, self.points[0]) d2 = dist2d(pos, self.points[-1]) # if d2 0: self.startpoints.pop(index) self.endpoints.pop(index) @@ -316,15 +331,22 @@ def dedupePoints(self): if len(self.points) > 1: keep_points = np.empty(self.points.shape[0], dtype=bool) keep_points[0] = True - diff_points = np.sum((self.points[1:]-self.points[:1])**2, axis=1) + diff_points = np.sum((self.points[1:] - self.points[:1]) ** 2, axis=1) keep_points[1:] = diff_points > 0.000000001 self.points = self.points[keep_points, :] def insert(self, at_index, point, startpoint=None, endpoint=None, rotation=None): - self.append(point, startpoint=startpoint, endpoint=endpoint, - rotation=rotation, at_index=at_index) - - def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index=None): + self.append( + point, + startpoint=startpoint, + endpoint=endpoint, + rotation=rotation, + at_index=at_index, + ) + + def append( + self, point, startpoint=None, endpoint=None, rotation=None, at_index=None + ): if at_index is None: self.points = np.concatenate((self.points, np.array([point]))) if startpoint is not None: @@ -335,7 +357,8 @@ def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index= self.rotations.append(rotation) else: self.points = np.concatenate( - (self.points[0:at_index], np.array([point]), self.points[at_index:])) + (self.points[0:at_index], np.array([point]), self.points[at_index:]) + ) if startpoint is not None: self.startpoints[at_index:at_index] = [startpoint] if endpoint is not None: @@ -343,7 +366,9 @@ def append(self, point, startpoint=None, endpoint=None, rotation=None, at_index= if rotation is not None: self.rotations[at_index:at_index] = [rotation] - def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_index=None): + def extend( + self, points, startpoints=None, endpoints=None, rotations=None, at_index=None + ): if len(points) == 0: return if at_index is None: @@ -356,7 +381,8 @@ def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_in self.rotations.extend(rotations) else: self.points = np.concatenate( - (self.points[0:at_index], np.array(points), self.points[at_index:])) + (self.points[0:at_index], np.array(points), self.points[at_index:]) + ) if startpoints is not None: self.startpoints[at_index:at_index] = startpoints if endpoints is not None: @@ -365,9 +391,12 @@ def extend(self, points, startpoints=None, endpoints=None, rotations=None, at_in self.rotations[at_index:at_index] = rotations def clip_points(self, minx, maxx, miny, maxy): - """ remove any points outside this range """ - included_values = (self.points[:, 0] >= minx) and ((self.points[:, 0] <= maxx) - and (self.points[:, 1] >= maxy) and (self.points[:, 1] <= maxy)) + """remove any points outside this range""" + included_values = (self.points[:, 0] >= minx) and ( + (self.points[:, 0] <= maxx) + and (self.points[:, 1] >= maxy) + and (self.points[:, 1] <= maxy) + ) self.points = self.points[included_values] def rampContour(self, zstart, zend, o): @@ -401,7 +430,7 @@ def rampContour(self, zstart, zend, o): znew = zstart - stepdown * ratio if znew <= zend: - ratio = ((z - zend) / (z - znew)) + ratio = (z - zend) / (z - znew) v1 = Vector(chunk_points[-1]) v2 = Vector((s[0], s[1], znew)) v = v1 + ratio * (v2 - v1) @@ -439,7 +468,11 @@ def rampContour(self, zstart, zend, o): if i == len(self.points): i = 0 # ramp out - if o.movement.ramp_out and (not o.use_layers or not o.first_down or (o.first_down and endpoint is not None)): + if o.movement.ramp_out and ( + not o.use_layers + or not o.first_down + or (o.first_down and endpoint is not None) + ): z = zend # i=endpoint @@ -454,7 +487,7 @@ def rampContour(self, zstart, zend, o): l = dist2d(s1, s2) znew = z + tan(o.movement.ramp_out_angle) * l if znew > o.maxz: - ratio = ((z - o.maxz) / (z - znew)) + ratio = (z - o.maxz) / (z - znew) v1 = Vector(chunk_points[-1]) v2 = Vector((s1[0], s1[1], znew)) v = v1 + ratio * (v2 - v1) @@ -485,7 +518,7 @@ def rampZigZag(self, zstart, zend, o): ramplength = estlength zigzaglength = ramplength / 2.000 turns = 1 - print('turns %i' % turns) + print("turns %i" % turns) if zigzaglength > self.length: turns = ceil(zigzaglength / self.length) ramplength = turns * self.length * 2.0 @@ -495,7 +528,9 @@ def rampZigZag(self, zstart, zend, o): else: zigzagtraveled = 0.0 haspoints = False - ramppoints = [(self.points[0][0], self.points[0][1], self.points[0][2])] + ramppoints = [ + (self.points[0][0], self.points[0][1], self.points[0][2]) + ] i = 1 while not haspoints: # print(i,zigzaglength,zigzagtraveled) @@ -505,8 +540,9 @@ def rampZigZag(self, zstart, zend, o): zigzagtraveled += d if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): ratio = 1 - (zigzagtraveled - zigzaglength) / d - if (i + 1 == len( - self.points)): # this condition is for a rare case of combined layers+bridges+ramps.. + if i + 1 == len( + self.points + ): # this condition is for a rare case of combined layers+bridges+ramps.. ratio = 1 v = p1 + ratio * (p2 - p1) ramppoints.append(v.tolist()) @@ -520,7 +556,12 @@ def rampZigZag(self, zstart, zend, o): traveled = 0.0 chunk_points.append( - (self.points[0][0], self.points[0][1], max(self.points[0][2], zstart))) + ( + self.points[0][0], + self.points[0][1], + max(self.points[0][2], zstart), + ) + ) for r in range(turns): for p in range(0, len(ramppoints)): p1 = chunk_points[-1] @@ -551,7 +592,7 @@ def rampZigZag(self, zstart, zend, o): ramplength = estlength zigzaglength = ramplength / 2.000 turns = 1 - print('turns %i' % turns) + print("turns %i" % turns) if zigzaglength > self.length: turns = ceil(zigzaglength / self.length) ramplength = turns * self.length * 2.0 @@ -563,8 +604,13 @@ def rampZigZag(self, zstart, zend, o): else: zigzagtraveled = 0.0 haspoints = False - ramppoints = [(self.points[-1][0], self.points[-1] - [1], self.points[-1][2])] + ramppoints = [ + ( + self.points[-1][0], + self.points[-1][1], + self.points[-1][2], + ) + ] i = len(self.points) - 2 while not haspoints: # print(i,zigzaglength,zigzagtraveled) @@ -572,10 +618,13 @@ def rampZigZag(self, zstart, zend, o): p2 = self.points[i] d = dist2d(p1, p2) zigzagtraveled += d - if zigzagtraveled >= zigzaglength or i + 1 == len(self.points): + if zigzagtraveled >= zigzaglength or i + 1 == len( + self.points + ): ratio = 1 - (zigzagtraveled - zigzaglength) / d - if (i + 1 == len( - self.points)): # this condition is for a rare case of + if i + 1 == len( + self.points + ): # this condition is for a rare case of # combined layers+bridges+ramps... ratio = 1 # print((ratio,zigzaglength)) @@ -610,7 +659,9 @@ def changePathStart(self, o): newstart = o.profile_start chunkamt = len(self.points) newstart = newstart % chunkamt - self.points = np.concatenate((self.points[newstart:], self.points[:newstart])) + self.points = np.concatenate( + (self.points[newstart:], self.points[:newstart]) + ) def breakPathForLeadinLeadout(self, o): iradius = o.lead_in @@ -625,20 +676,26 @@ def breakPathForLeadinLeadout(self, o): bmay = bpoint[1] - apoint[1] segmentLength = hypot(bmax, bmay) # find segment length - if segmentLength > 2 * max(iradius, - oradius): # Be certain there is enough room for the leadin and leadiout + if segmentLength > 2 * max( + iradius, oradius + ): # Be certain there is enough room for the leadin and leadiout # add point on the line here # average of the two x points to find center newpointx = (bpoint[0] + apoint[0]) / 2 # average of the two y points to find center newpointy = (bpoint[1] + apoint[1]) / 2 self.points = np.concatenate( - (self.points[:i+1], np.array([[newpointx, newpointy, apoint[2]]]), self.points[i+1:])) + ( + self.points[: i + 1], + np.array([[newpointx, newpointy, apoint[2]]]), + self.points[i + 1:], + ) + ) def leadContour(self, o): perimeterDirection = 1 # 1 is clockwise, 0 is CCW - if o.movement.spindle_rotation == 'CW': - if o.movement.type == 'CONVENTIONAL': + if o.movement.spindle_rotation == "CW": + if o.movement.type == "CONVENTIONAL": perimeterDirection = 0 if self.parents: # if it is inside another parent @@ -705,12 +762,12 @@ def chunksCoherency(chunks): angle = vec.angle(lastvec, vec) # print(angle,i) if angle > 1.07: # 60 degrees is maximum toleration for pencil paths. - if len(nchunk_points) > 4: # this is a testing threshold + if len(nchunk.points) > 4: # this is a testing threshold nchunks.append(nchunk.to_chunk()) nchunk = camPathChunkBuilder() lastvec = vec - if len(nchunk_points) > 4: # this is a testing threshold - nchunk.points = np.array(nchunk_points) + if len(nchunk.points) > 4: # this is a testing threshold + nchunk.points = np.array(nchunk.points) nchunks.append(nchunk) return nchunks @@ -723,18 +780,21 @@ def setChunksZ(chunks, z): newchunks.append(chunk) return newchunks + # don't make this @jit parallel, because it sometimes gets called with small N # and the overhead of threading is too much. @jit(nopython=True, fastmath=True, cache=True) -def _optimize_internal(points, keep_points, e, protect_vertical, protect_vertical_limit): +def _optimize_internal( + points, keep_points, e, protect_vertical, protect_vertical_limit +): # inlined so that numba can optimize it nicely def _mag_sq(v1): - return v1[0]**2 + v1[1]**2 + v1[2]**2 + return v1[0] ** 2 + v1[1] ** 2 + v1[2] ** 2 def _dot_pr(v1, v2): - return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] + return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] def _applyVerticalLimit(v1, v2, cos_limit): """test path segment on verticality threshold, for protect_vertical option""" @@ -742,44 +802,45 @@ def _applyVerticalLimit(v1, v2, cos_limit): if z > 0: # don't use this vector because dot product of 0,0,1 is trivially just v2[2] # vec_up = np.array([0, 0, 1]) - vec_diff = v1-v2 - vec_diff2 = v2-v1 + vec_diff = v1 - v2 + vec_diff2 = v2 - v1 vec_diff_mag = np.sqrt(_mag_sq(vec_diff)) # dot product = cos(angle) * mag1 * mag2 cos1_times_mag = vec_diff[2] cos2_times_mag = vec_diff2[2] - if cos1_times_mag > cos_limit*vec_diff_mag: + if cos1_times_mag > cos_limit * vec_diff_mag: # vertical, moving down v1[0] = v2[0] v1[1] = v2[1] - elif cos2_times_mag > cos_limit*vec_diff_mag: + elif cos2_times_mag > cos_limit * vec_diff_mag: # vertical, moving up v2[0] = v1[0] v2[1] = v1[1] cos_limit = cos(protect_vertical_limit) prev_i = 0 - for i in range(1, points.shape[0]-1): + for i in range(1, points.shape[0] - 1): v1 = points[prev_i] - v2 = points[i+1] + v2 = points[i + 1] vmiddle = points[i] - line_direction = v2-v1 + line_direction = v2 - v1 line_length = sqrt(_mag_sq(line_direction)) if line_length == 0: # don't keep duplicate points keep_points[i] = False continue # normalize line direction - line_direction *= (1.0/line_length) # N in formula below + line_direction *= 1.0 / line_length # N in formula below # X = A + tN (line formula) Distance to point P # A = v1, N = line_direction, P = vmiddle # distance = || (P - A) - ((P-A).N)N || point_offset = vmiddle - v1 - distance_sq = _mag_sq(point_offset - (line_direction * - _dot_pr(point_offset, line_direction))) + distance_sq = _mag_sq( + point_offset - (line_direction * _dot_pr(point_offset, line_direction)) + ) # compare on squared distance to save a sqrt - if distance_sq < e*e: + if distance_sq < e * e: keep_points[i] = False else: keep_points[i] = True @@ -797,29 +858,42 @@ def optimizeChunk(chunk, operation): endpoints = chunk.endpoints naxispoints = True - protect_vertical = operation.movement.protect_vertical and operation.machine_axes == '3' + protect_vertical = ( + operation.movement.protect_vertical and operation.machine_axes == "3" + ) keep_points = np.full(points.shape[0], True) # shape points need to be on line, # but we need to protect vertical - which # means changing point values # bits of this are moved from simple.py so that # numba can optimize as a whole - _optimize_internal(points, keep_points, operation.optimisation.optimize_threshold * - 0.000001, protect_vertical, operation.movement.protect_vertical_limit) + _optimize_internal( + points, + keep_points, + operation.optimisation.optimize_threshold * 0.000001, + protect_vertical, + operation.movement.protect_vertical_limit, + ) # now do numpy select by boolean array chunk.points = points[keep_points] if naxispoints: # list comprehension so we don't have to do tons of appends - chunk.startpoints = [chunk.startpoints[i] - for i, b in enumerate(keep_points) if b == True] - chunk.endpoints = [chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True] - chunk.rotations = [chunk.rotations[i] for i, b in enumerate(keep_points) if b == True] + chunk.startpoints = [ + chunk.startpoints[i] for i, b in enumerate(keep_points) if b == True + ] + chunk.endpoints = [ + chunk.endpoints[i] for i, b in enumerate(keep_points) if b == True + ] + chunk.rotations = [ + chunk.rotations[i] for i, b in enumerate(keep_points) if b == True + ] return chunk -def limitChunks(chunks, o, - force=False): # TODO: this should at least add point on area border... +def limitChunks( + chunks, o, force=False +): # TODO: this should at least add point on area border... # but shouldn't be needed at all at the first place... if o.use_limit_curve or force: nchunks = [] @@ -840,13 +914,23 @@ def limitChunks(chunks, o, elif sampled: nch.points.append(s) prevsampled = sampled - if len(nch.points) > 2 and closed and ch.closed and np.array_equal(ch.points[0], ch.points[-1]): + if ( + len(nch.points) > 2 + and closed + and ch.closed + and np.array_equal(ch.points[0], ch.points[-1]) + ): nch.closed = True - elif ch.closed and nch1 is not None and len(nch.points) > 1 and np.array_equal(nch.points[-1], nch1.points[0]): + elif ( + ch.closed + and nch1 is not None + and len(nch.points) > 1 + and np.array_equal(nch.points[-1], nch1.points[0]) + ): # here adds beginning of closed chunk to the end, if the chunks were split during limiting nch.points.extend(nch1.points.tolist()) nchunks.remove(nch1) - print('joining stuff') + print("joining stuff") if len(nch.points) > 0: nchunks.append(nch.to_chunk()) return nchunks @@ -876,7 +960,9 @@ def parentChildDist(parents, children, o, distance=None): if distance is None: dlim = o.dist_between_paths * 2 - if (o.strategy == 'PARALLEL' or o.strategy == 'CROSS') and o.movement.parallel_step_back: + if ( + o.strategy == "PARALLEL" or o.strategy == "CROSS" + ) and o.movement.parallel_step_back: dlim = dlim * 2 else: dlim = distance @@ -921,7 +1007,11 @@ def chunksToShapely(chunks): # hierarchy works like this: - children get milled first. ppart.parents.append(ptest) - for ch in chunks: # now make only simple polygons with holes, not more polys inside others + for ( + ch + ) in ( + chunks + ): # now make only simple polygons with holes, not more polys inside others found = False if len(ch.parents) % 2 == 1: @@ -943,10 +1033,11 @@ def chunksToShapely(chunks): try: ch.parents[0].poly = ch.parents[0].poly.difference( - ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) + ch.poly + ) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) except: - print('chunksToShapely oops!') + print("chunksToShapely oops!") lastPt = None tolerance = 0.0000003 @@ -1024,7 +1115,8 @@ def chunksToShapely(chunks): ch.parents[0].poly = sgeometry.Polygon(ch.parents[0].points) ch.parents[0].poly = ch.parents[0].poly.difference( - ch.poly) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) + ch.poly + ) # sgeometry.Polygon( ch.parents[0].poly, ch.poly) returnpolys = [] @@ -1034,6 +1126,7 @@ def chunksToShapely(chunks): if len(ch.parents) == 0: returnpolys.append(ch.poly) from shapely.geometry import MultiPolygon + polys = MultiPolygon(returnpolys) return polys @@ -1054,19 +1147,23 @@ def meshFromCurveToChunk(object): lastvi = 0 vtotal = len(mesh.vertices) perc = 0 - progress('processing curve - START - Vertices: ' + str(vtotal)) + progress("processing curve - START - Vertices: " + str(vtotal)) for vi in range(0, len(mesh.vertices) - 1): co = (mesh.vertices[vi].co + object.location).to_tuple() if not dk.isdisjoint([(vi, vi + 1)]) and d[(vi, vi + 1)] == 1: chunk.points.append(co) else: chunk.points.append(co) - if len(chunk.points) > 2 and (not (dk.isdisjoint([(vi, lastvi)])) or not ( - dk.isdisjoint([(lastvi, vi)]))): # this was looping chunks of length of only 2 points... + if len(chunk.points) > 2 and ( + not (dk.isdisjoint([(vi, lastvi)])) + or not (dk.isdisjoint([(lastvi, vi)])) + ): # this was looping chunks of length of only 2 points... # print('itis') chunk.closed = True - chunk.points.append((mesh.vertices[lastvi].co + object.location).to_tuple()) + chunk.points.append( + (mesh.vertices[lastvi].co + object.location).to_tuple() + ) # add first point to end#originally the z was mesh.vertices[lastvi].co.z+z lastvi = vi + 1 chunk = chunk.to_chunk() @@ -1076,15 +1173,25 @@ def meshFromCurveToChunk(object): chunks.append(chunk) chunk = camPathChunkBuilder() - progress('processing curve - FINISHED') + progress("processing curve - FINISHED") vi = len(mesh.vertices) - 1 - chunk.points.append((mesh.vertices[vi].co.x + x, - mesh.vertices[vi].co.y + y, mesh.vertices[vi].co.z + z)) + chunk.points.append( + ( + mesh.vertices[vi].co.x + x, + mesh.vertices[vi].co.y + y, + mesh.vertices[vi].co.z + z, + ) + ) if not (dk.isdisjoint([(vi, lastvi)])) or not (dk.isdisjoint([(lastvi, vi)])): chunk.closed = True chunk.points.append( - (mesh.vertices[lastvi].co.x + x, mesh.vertices[lastvi].co.y + y, mesh.vertices[lastvi].co.z + z)) + ( + mesh.vertices[lastvi].co.x + x, + mesh.vertices[lastvi].co.y + y, + mesh.vertices[lastvi].co.z + z, + ) + ) chunk = chunk.to_chunk() chunk.dedupePoints() if chunk.count() >= 1: @@ -1122,22 +1229,22 @@ def meshFromCurve(o, use_modifiers=False): activate(o) bpy.ops.object.duplicate() - bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + bpy.ops.object.parent_clear(type="CLEAR_KEEP_TRANSFORM") co = bpy.context.active_object # support for text objects is only and only here, just convert them to curves. - if co.type == 'FONT': - bpy.ops.object.convert(target='CURVE', keep_original=False) - elif co.type != 'CURVE': # curve must be a curve... + if co.type == "FONT": + bpy.ops.object.convert(target="CURVE", keep_original=False) + elif co.type != "CURVE": # curve must be a curve... bpy.ops.object.delete() # delete temporary object raise CamException("Source curve object must be of type CURVE") - co.data.dimensions = '3D' + co.data.dimensions = "3D" co.data.bevel_depth = 0 co.data.extrude = 0 # first, convert to mesh to avoid parenting issues with hooks, then apply locrotscale. - bpy.ops.object.convert(target='MESH', keep_original=False) + bpy.ops.object.convert(target="MESH", keep_original=False) if use_modifiers: eval_object = co.evaluated_get(bpy.context.evaluated_depsgraph_get()) @@ -1164,7 +1271,7 @@ def curveToChunks(o, use_modifiers=False): co = bpy.context.active_object - bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_all(action="DESELECT") bpy.data.objects[co.name].select_set(True) bpy.ops.object.delete() From 725e335d97d20b1fcaf6e5523354481aa3c12756 Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 3 Apr 2024 12:24:14 +0000 Subject: [PATCH 062/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index f5c3cfe88..e44ededac 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 14), + "version":(1,0,15), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 4edf85a92..7b6e39820 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,14) \ No newline at end of file +__version__=(1,0,15) \ No newline at end of file From 94d717136608a27555fa30133f05966b7de6d4bf Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 6 Apr 2024 12:38:11 -0400 Subject: [PATCH 063/100] Operation Preset Update Operation Presets have been updated to create a new operation by default, they will also now follow the naming convention - OP_ObjectName_OperationNumber_PresetName, to keep in line with the naming of default operations. --- scripts/addons/cam/preset_managers.py | 7 +++++-- .../cam_operations/Fin_Ball_3,0_Block_All.py | 10 ++++++---- .../Fin_Ball_3,0_Block_Around.py | 10 ++++++---- .../Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py | 10 ++++++---- ...in_Ball_3,0_Circles_Around_EXPERIMENTAL.py | 10 ++++++---- .../cam_operations/Fin_Ball_3,0_Cross_All.py | 10 ++++++---- .../Fin_Ball_3,0_Cross_Around.py | 10 ++++++---- .../cam_operations/Fin_Ball_3,0_Cutout.py | 10 ++++++---- .../Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py | 10 ++++++---- .../Fin_Ball_3,0_Parallel_All.py | 10 ++++++---- .../Fin_Ball_3,0_Parallel_Around.py | 10 ++++++---- .../Fin_Ball_3,0_Pencil_EXPERIMENTAL.py | 10 ++++++---- .../Fin_Ball_3,0_Pocket_EXPERIMENTAL.py | 10 ++++++---- .../cam_operations/Fin_Ball_3,0_Spiral_All.py | 10 ++++++---- .../Fin_Ball_3,0_Spiral_Around.py | 10 ++++++---- .../cam_operations/Finishing_3mm_ballnose.py | 10 ++++++---- .../cam_operations/Rou_Ball_3,0_Block_All.py | 10 ++++++---- .../Rou_Ball_3,0_Block_Around.py | 10 ++++++---- .../Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py | 10 ++++++---- ...ou_Ball_3,0_Circles_Around_EXPERIMENTAL.py | 10 ++++++---- .../cam_operations/Rou_Ball_3,0_Cross_All.py | 10 ++++++---- .../Rou_Ball_3,0_Cross_Around.py | 10 ++++++---- .../cam_operations/Rou_Ball_3,0_Cutout.py | 10 ++++++---- .../Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py | 10 ++++++---- .../Rou_Ball_3,0_Parallel_All.py | 10 ++++++---- .../Rou_Ball_3,0_Parallel_Around.py | 10 ++++++---- .../Rou_Ball_3,0_Pencil_EXPERIMENTAL.py | 10 ++++++---- .../Rou_Ball_3,0_Pocket_EXPERIMENTAL.py | 10 ++++++---- .../cam_operations/Rou_Ball_3,0_Spiral_All.py | 10 ++++++---- .../Rou_Ball_3,0_Spiral_Around.py | 10 ++++++---- scripts/addons/cam/utils.py | 20 +++++++++---------- 31 files changed, 189 insertions(+), 128 deletions(-) diff --git a/scripts/addons/cam/preset_managers.py b/scripts/addons/cam/preset_managers.py index 01ef2c981..16c4986a3 100644 --- a/scripts/addons/cam/preset_managers.py +++ b/scripts/addons/cam/preset_managers.py @@ -67,8 +67,11 @@ class AddPresetCamOperation(AddPresetBase, Operator): preset_menu = "CAM_OPERATION_MT_presets" preset_defines = [ - 'import cam', - 'o = cam.utils.setup_operation_preset()', + 'from pathlib import Path', + 'bpy.ops.scene.cam_operation_add()', + 'scene = bpy.context.scene', + 'o = scene.cam_operations[scene.cam_active_operation]', + "o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}'", ] preset_values = [ diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py index 38aa6b1f7..69f2f9a08 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Block' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Block' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py index cf6d990d5..e64836eab 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Block_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Block' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Block' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py index ba371116a..111af80c7 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_All_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Circles' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Circles' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py index 143899f29..f7164bc56 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Circles_Around_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Circles' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Circles' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py index 13c92baf9..ee3f28a55 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Cross' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Cross' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py index 6eb0b19ce..f5a9cf836 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cross_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Cross' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Cross' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py index b924eee1c..ae65ebd40 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Cutout.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Cutout' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Cutout' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py index 8d12df58d..d1e7b9241 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Outline_Fill_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Outline' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Outline' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py index e2120df05..35982ec45 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Parallel' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Parallel' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py index a9f322937..cd584ff7c 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Parallel_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Parallel' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Parallel' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py index a838e3e5d..919e28edc 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pencil_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Pencil' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Pencil' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py index fcc4423fd..8011d563d 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Pocket_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Pocket' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Pocket' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py index d8a0afe3f..ad0a8bf8d 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Spiral' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Spiral' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py index e80c3acd4..3354b848d 100644 --- a/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Fin_Ball_3,0_Spiral_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 3.0 -o.filename = 'Fin_Ball_3,0_Spiral' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Fin_Ball_3,0_Spiral' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py b/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py index b148d9960..747ef9a95 100644 --- a/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py +++ b/scripts/addons/cam/presets/cam_operations/Finishing_3mm_ballnose.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Operation_1' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.009999999776482582 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Operation_1' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py index 3307dab7d..ee56d278b 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Block' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Block' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py index d72034238..71c1f61cc 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Block_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Block' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Block' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py index 6fb00c9a4..8313b0539 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_All_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Circles' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Circles' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py index cde6b0a08..8a7015601 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Circles_Around_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Circles' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Circles' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py index 388b5252b..7dc0b0fce 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Cross' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Cross' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py index b96afee52..5f25d95ee 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cross_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Cross' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Cross' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py index 27e28df1b..12f9ddd51 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Cutout.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Cutout' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Cutout' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py index 1dc946596..059c98d30 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Outline_Fill_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Outline' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Outline' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py index 8cf7fb169..db5e04329 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Parallel' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Parallel' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py index 40b316006..5da91d2ed 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Parallel_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Parallel' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Parallel' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py index 5bf667eb1..e42744409 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pencil_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Pencil' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Pencil' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py index 696f213f1..3a071807f 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Pocket_EXPERIMENTAL.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Pocket' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Pocket' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py index 2c437b48e..fca586cb8 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_All.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'ALL' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Spiral' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Spiral' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py index a19068d84..30135eb83 100644 --- a/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py +++ b/scripts/addons/cam/presets/cam_operations/Rou_Ball_3,0_Spiral_Around.py @@ -1,7 +1,10 @@ import bpy -import cam +from pathlib import Path -o = cam.utils.setup_operation_preset() +bpy.ops.scene.cam_operation_add() + +scene = bpy.context.scene +o = scene.cam_operations[scene.cam_active_operation] o.ambient_behaviour = 'AROUND' o.ambient_radius = 0.009999999776482582 @@ -20,7 +23,7 @@ o.dont_merge = False o.duration = 96.3156509399414 o.feedrate = 1.0 -o.filename = 'Rou_Ball_3,0_Spiral' +o.filename = o.name = f'OP_{o.object_name}_{scene.cam_active_operation + 1}_{Path(__file__).stem}' o.free_movement_height = 0.01 o.geometry_source = 'OBJECT' o.inverse = False @@ -34,7 +37,6 @@ o.minz = -0.1281193494796753 o.minz_from_ob = True o.movement_type = 'MEANDER' -o.name = 'Rou_Ball_3,0_Spiral' o.object = None o.optimize = True o.optimize_threshold = 4.999999873689376e-05 diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 0b76428d7..476e7ad16 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -1792,16 +1792,16 @@ def reload_paths(o): bpy.data.meshes.remove(old_pathmesh) -def setup_operation_preset(): - scene = bpy.context.scene - cam_operations = scene.cam_operations - active_operation = scene.cam_active_operation - try: - o = cam_operations[active_operation] - except IndexError: - bpy.ops.scene.cam_operation_add() - o = cam_operations[active_operation] - return o +# def setup_operation_preset(): +# scene = bpy.context.scene +# cam_operations = scene.cam_operations +# active_operation = scene.cam_active_operation +# try: +# o = cam_operations[active_operation] +# except IndexError: +# bpy.ops.scene.cam_operation_add() +# o = cam_operations[active_operation] +# return o # Moved from init - the following code was moved here to permit the import fix From f9be0d35c7efa909a95cd6561cb26375e7f46edc Mon Sep 17 00:00:00 2001 From: abosafia Date: Mon, 8 Apr 2024 11:57:20 +0200 Subject: [PATCH 064/100] remove curve doubles without converting to mesh --- scripts/addons/cam/curvecamtools.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 96c7ec669..00fb1a8c7 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -592,7 +592,7 @@ def getCornerDelta(curidx, nextidx): class CamCurveRemoveDoubles(Operator): - """curve remove doubles - warning, removes beziers!""" + """curve remove doubles""" bl_idname = "object.curve_remove_doubles" bl_label = "C-Remove doubles" bl_options = {'REGISTER', 'UNDO'} @@ -605,20 +605,11 @@ def execute(self, context): obs = bpy.context.selected_objects for ob in obs: bpy.context.view_layer.objects.active = ob - - mode = False - if bpy.context.mode == 'EDIT_CURVE': + if bpy.context.mode == 'OBJECT': bpy.ops.object.editmode_toggle() - mode = True - bpy.ops.object.convert(target='MESH') - bpy.ops.object.editmode_toggle() - bpy.ops.mesh.select_all(action='TOGGLE') - bpy.ops.mesh.remove_doubles() + bpy.ops.curve.select_all() + bpy.ops.curve.remove_double() bpy.ops.object.editmode_toggle() - bpy.ops.object.convert(target='CURVE') - - if mode: - bpy.ops.object.editmode_toggle() return {'FINISHED'} From fd22ca611ed683f6206b9df9fa04db349116c952 Mon Sep 17 00:00:00 2001 From: Release robot Date: Mon, 8 Apr 2024 14:26:10 +0000 Subject: [PATCH 065/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index e44ededac..6aba9b312 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,15), + "version":(1,0,16), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 7b6e39820..5944b8dcd 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,15) \ No newline at end of file +__version__=(1,0,16) \ No newline at end of file From 043c95bb447710779796e5fd113d815a37938951 Mon Sep 17 00:00:00 2001 From: abosafia Date: Tue, 9 Apr 2024 06:54:29 +0200 Subject: [PATCH 066/100] remove_doubles_ merge distance --- scripts/addons/cam/curvecamtools.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 00fb1a8c7..dae598fcc 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -603,12 +603,15 @@ def poll(cls, context): def execute(self, context): obs = bpy.context.selected_objects + #bpy.context.object.data.dimensions = '3D' + bpy.context.object.data.resolution_u = 32 for ob in obs: bpy.context.view_layer.objects.active = ob if bpy.context.mode == 'OBJECT': - bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() bpy.ops.curve.select_all() - bpy.ops.curve.remove_double() + bpy.ops.curve.decimate(ratio=1) + bpy.ops.curve.remove_double(distance=0.0001) bpy.ops.object.editmode_toggle() return {'FINISHED'} From 1eb85e9c132a89a48c5a302add48930aa78eff98 Mon Sep 17 00:00:00 2001 From: Release robot Date: Tue, 9 Apr 2024 18:41:16 +0000 Subject: [PATCH 067/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 6aba9b312..593d1c898 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,16), + "version":(1,0,17), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 5944b8dcd..98d021a92 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,16) \ No newline at end of file +__version__=(1,0,17) \ No newline at end of file From b0691035e7265539e850dc4123cf001991f67fee Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 11 Apr 2024 14:53:37 -0400 Subject: [PATCH 068/100] Title Case for Names, Labels, Strings Enforced consistency and title case for all names, labels, print and progress strings throughout the addon. Fixed typos. --- scripts/addons/cam/async_op.py | 10 +- scripts/addons/cam/autoupdate.py | 18 +- scripts/addons/cam/basrelief.py | 104 +++--- scripts/addons/cam/bridges.py | 4 +- scripts/addons/cam/cam_chunk.py | 22 +- scripts/addons/cam/cam_operation.py | 335 +++++++++--------- scripts/addons/cam/chain.py | 14 +- scripts/addons/cam/collision.py | 12 +- scripts/addons/cam/curvecamcreate.py | 178 +++++----- scripts/addons/cam/curvecamequation.py | 68 ++-- scripts/addons/cam/curvecamtools.py | 78 ++-- scripts/addons/cam/engine.py | 2 +- scripts/addons/cam/gcodeimportparser.py | 2 +- scripts/addons/cam/gcodepath.py | 18 +- scripts/addons/cam/image_utils.py | 22 +- scripts/addons/cam/joinery.py | 6 +- scripts/addons/cam/machine_settings.py | 74 ++-- scripts/addons/cam/ops.py | 126 +++---- scripts/addons/cam/pack.py | 16 +- scripts/addons/cam/pattern.py | 4 +- scripts/addons/cam/polygon_utils_cam.py | 2 +- scripts/addons/cam/preferences.py | 30 +- scripts/addons/cam/preset_managers.py | 6 +- scripts/addons/cam/simple.py | 14 +- scripts/addons/cam/simulation.py | 6 +- scripts/addons/cam/slice.py | 16 +- scripts/addons/cam/strategy.py | 66 ++-- scripts/addons/cam/testing.py | 10 +- scripts/addons/cam/ui.py | 12 +- scripts/addons/cam/ui_panels/area.py | 22 +- scripts/addons/cam/ui_panels/chains.py | 14 +- scripts/addons/cam/ui_panels/cutter.py | 10 +- scripts/addons/cam/ui_panels/feedrate.py | 4 +- scripts/addons/cam/ui_panels/gcode.py | 4 +- scripts/addons/cam/ui_panels/info.py | 25 +- scripts/addons/cam/ui_panels/machine.py | 2 +- scripts/addons/cam/ui_panels/material.py | 26 +- scripts/addons/cam/ui_panels/movement.py | 56 +-- scripts/addons/cam/ui_panels/op_properties.py | 14 +- scripts/addons/cam/ui_panels/operations.py | 12 +- scripts/addons/cam/ui_panels/optimisation.py | 22 +- scripts/addons/cam/ui_panels/pack.py | 8 +- scripts/addons/cam/ui_panels/slice.py | 4 +- scripts/addons/cam/utils.py | 94 ++--- scripts/addons/cam/voronoi.py | 12 +- 45 files changed, 803 insertions(+), 801 deletions(-) diff --git a/scripts/addons/cam/async_op.py b/scripts/addons/cam/async_op.py index bb298a626..5cc115662 100644 --- a/scripts/addons/cam/async_op.py +++ b/scripts/addons/cam/async_op.py @@ -6,7 +6,7 @@ @types.coroutine def progress_async(text, n=None, value_type='%'): - """function for reporting during the script, works for background operations in the header.""" + """Function for Reporting During the Script, Works for Background Operations in the Header.""" throw_exception = yield ('progress', {'text': text, 'n': n, "value_type": value_type}) if throw_exception is not None: raise throw_exception @@ -65,7 +65,7 @@ def tick(self, context): self.coroutine = self.execute_async(context) try: if self._is_cancelled: - (msg, args) = self.coroutine.send(AsyncCancelledException("Cancelled with ESC key")) + (msg, args) = self.coroutine.send(AsyncCancelledException("Cancelled with ESC Key")) raise StopIteration else: (msg, args) = self.coroutine.send(None) @@ -77,7 +77,7 @@ def tick(self, context): except StopIteration: return False except Exception as e: - print("Exception thrown in tick:", e) + print("Exception Thrown in Tick:", e) def execute(self, context): if bpy.app.background: @@ -93,9 +93,9 @@ def execute(self, context): class AsyncTestOperator(bpy.types.Operator, AsyncOperatorMixin): - """test async operator""" + """Test Async Operator""" bl_idname = "object.cam_async_test_operator" - bl_label = "Test operator for async stuff" + bl_label = "Test Operator for Async Stuff" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} async def execute_async(self, context): diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 70856e9c4..7ac6a32a9 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -16,9 +16,9 @@ class UpdateChecker(bpy.types.Operator): - """check for updates""" + """Check for Updates""" bl_idname = "render.cam_check_updates" - bl_label = "Check for updates in blendercam plugin" + bl_label = "Check for Updates in BlenderCAM Plugin" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -31,7 +31,7 @@ def execute(self, context): if match: update_source = f"https://api.github.com/repos/{match.group(1)}/releases" - print(f"update check: {update_source}") + print(f"Update Check: {update_source}") if update_source == "None" or len(update_source) == 0: return {'FINISHED'} @@ -46,7 +46,7 @@ def execute(self, context): if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] - print(f"Found release: {tag}") + print(f"Found Release: {tag}") match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: version_num = tuple(map(int, match.groups())) @@ -71,13 +71,13 @@ def execute(self, context): class Updater(bpy.types.Operator): - """update to newer version if possible """ + """Update to Newer Version if Possible""" bl_idname = "render.cam_update_now" bl_label = "Update" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - print("update check") + print("Update Check") last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check today = date.today().toordinal() update_source = bpy.context.preferences.addons['cam'].preferences.update_source @@ -96,7 +96,7 @@ def execute(self, context): if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] - print(f"Found release: {tag}") + print(f"Found Release: {tag}") match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: version_num = tuple(map(int, match.groups())) @@ -105,7 +105,7 @@ def execute(self, context): bpy.ops.wm.save_userpref() if version_num > current_version: - print("Version is newer, downloading source") + print("Version Is Newer, Downloading Source") zip_url = release["zipball_url"] self.install_zip_from_url(zip_url) return {'FINISHED'} @@ -166,7 +166,7 @@ def install_zip_from_url(self, zip_url): class UpdateSourceOperator(bpy.types.Operator): bl_idname = "render.cam_set_update_source" - bl_label = "Set blendercam update source" + bl_label = "Set BlenderCAM Update Source" new_source: StringProperty( default='', diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index f1c4ad195..08a2aa4ba 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -557,7 +557,7 @@ def numpysave(a, iname): def numpytoimage(a, iname): t = time.time() - print('numpy to image - here') + print('Numpy to Image - Here') t = time.time() print(a.shape[0], a.shape[1]) foundimage = False @@ -625,7 +625,7 @@ def tonemap(i, exponent): def vert(column, row, z, XYscaling, Zscaling): - """ Create a single vert """ + """ Create a Single Vert """ return column * XYscaling, row * XYscaling, z * Zscaling @@ -641,7 +641,7 @@ def buildMesh(mesh_z, br): bpy.data.objects.remove(object) print("old basrelief removed") - print("Building mesh") + print("Building Mesh") numY = mesh_z.shape[1] numX = mesh_z.shape[0] print(numX, numY) @@ -687,24 +687,24 @@ def buildMesh(mesh_z, br): bpy.context.active_object.location = (float( br.justifyx)*br.widthmm/1000, float(br.justifyy)*br.heightmm/1000, float(br.justifyz)*br.thicknessmm/1000) - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) + print("Faces:" + str(len(ob.data.polygons))) + print("Vertices:" + str(len(ob.data.vertices))) if decimateRatio > 0.95: - print("skipping decimate ratio > 0.95") + print("Skipping Decimate Ratio > 0.95") else: m = ob.modifiers.new(name="Foo", type='DECIMATE') m.ratio = decimateRatio - print("decimating with ratio:"+str(decimateRatio)) + print("Decimating with Ratio:"+str(decimateRatio)) bpy.ops.object.modifier_apply(modifier=m.name) - print("decimated") - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) + print("Decimated") + print("Faces:" + str(len(ob.data.polygons))) + print("Vertices:" + str(len(ob.data.vertices))) # Switches to cycles render to CYCLES to render the sceen then switches it back to BLENDERCAM_RENDER for basRelief def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view_layer): - print("rendering scene") + print("Rendering Scene") scene = bpy.context.scene # make sure we're in object mode or else bad things happen if bpy.context.active_object: @@ -745,7 +745,7 @@ def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view if our_viewer is not None: nodes.remove(our_viewer) bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' - print("done rendering") + print("Done Rendering") def problemAreas(br): @@ -869,7 +869,7 @@ def relief(br): print("Range:", nar.min(), nar.max()) if nar.min() - nar.max() == 0: raise ReliefError( - "Input image is blank - check you have the correct view layer or input image set.") + "Input Image Is Blank - Check You Have the Correct View Layer or Input Image Set.") gx = nar.copy() gx.fill(0) @@ -994,20 +994,20 @@ def filterwindow(x, y, cx=0, cy=0): # , curve=None): class BasReliefsettings(bpy.types.PropertyGroup): use_image_source: BoolProperty( - name="Use image source", + name="Use Image Source", description="", default=False, ) source_image_name: StringProperty( - name='Image source', + name='Image Source', description='image source', ) view_layer_name: StringProperty( - name='View layer source', + name='View Layer Source', description='Make a bas-relief from whatever is on this view layer', ) bit_diameter: FloatProperty( - name="Diameter of ball end in mm", + name="Diameter of Ball End in mm", description="Diameter of bit which will be used for carving", min=0.01, max=50.0, @@ -1015,7 +1015,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) pass_per_radius: IntProperty( - name="Passes per radius", + name="Passes per Radius", description="Amount of passes per radius\n(more passes, " "more mesh precision)", default=2, @@ -1023,13 +1023,13 @@ class BasReliefsettings(bpy.types.PropertyGroup): max=10, ) widthmm: IntProperty( - name="Desired width in mm", + name="Desired Width in mm", default=200, min=5, max=4000, ) heightmm: IntProperty( - name="Desired height in mm", + name="Desired Height in mm", default=150, min=5, max=4000, @@ -1070,7 +1070,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) depth_exponent: FloatProperty( - name="Depth exponent", + name="Depth Exponent", description="Initial depth map is taken to this power. Higher = " "sharper relief", min=0.5, @@ -1080,7 +1080,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) silhouette_threshold: FloatProperty( - name="Silhouette threshold", + name="Silhouette Threshold", description="Silhouette threshold", min=0.000001, max=1.0, @@ -1088,12 +1088,12 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) recover_silhouettes: BoolProperty( - name="Recover silhouettes", + name="Recover Silhouettes", description="", default=True, ) silhouette_scale: FloatProperty( - name="Silhouette scale", + name="Silhouette Scale", description="Silhouette scale", min=0.000001, max=5.0, @@ -1101,15 +1101,15 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) silhouette_exponent: IntProperty( - name="Silhouette square exponent", - description="If lower, true depht distances between objects will be " + name="Silhouette Square Exponent", + description="If lower, true depth distances between objects will be " "more visibe in the relief", default=3, min=0, max=5, ) attenuation: FloatProperty( - name="Gradient attenuation", + name="Gradient Attenuation", description="Gradient attenuation", min=0.000001, max=100.0, @@ -1117,39 +1117,39 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) min_gridsize: IntProperty( - name="Minimum grid size", + name="Minimum Grid Size", default=16, min=2, max=512, ) smooth_iterations: IntProperty( - name="Smooth iterations", + name="Smooth Iterations", default=1, min=1, max=64, ) vcycle_iterations: IntProperty( - name="V-cycle iterations", - description="set up higher for plananr constraint", + name="V-Cycle Iterations", + description="Set higher for planar constraint", default=2, min=1, max=128, ) linbcg_iterations: IntProperty( - name="Linbcg iterations", - description="set lower for flatter relief, and when using " + name="LINBCG Iterations", + description="Set lower for flatter relief, and when using " "planar constraint", default=5, min=1, max=64, ) use_planar: BoolProperty( - name="Use planar constraint", + name="Use Planar Constraint", description="", default=False, ) gradient_scaling_mask_use: BoolProperty( - name="Scale gradients with mask", + name="Scale Gradients with Mask", description="", default=False, ) @@ -1164,16 +1164,16 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) gradient_scaling_mask_name: StringProperty( - name='Scaling mask name', - description='mask name', + name='Scaling Mask Name', + description='Mask name', ) scale_down_before_use: BoolProperty( - name="Scale down image before processing", + name="Scale Down Image Before Processing", description="", default=False, ) scale_down_before: FloatProperty( - name="Image scale", + name="Image Scale", description="Image scale", min=0.025, max=1.0, @@ -1181,13 +1181,13 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) detail_enhancement_use: BoolProperty( - name="Enhance details ", - description="enhance details by frequency analysis", + name="Enhance Details", + description="Enhance details by frequency analysis", default=False, ) #detail_enhancement_freq=FloatProperty(name="frequency limit", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) detail_enhancement_amount: FloatProperty( - name="amount", + name="Amount", description="Image scale", min=0.025, max=1.0, @@ -1196,15 +1196,15 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) advanced: BoolProperty( - name="Advanced options", - description="show advanced options", + name="Advanced Options", + description="Show advanced options", default=True, ) class BASRELIEF_Panel(bpy.types.Panel): - """Bas relief panel""" - bl_label = "Bas relief" + """Bas Relief Panel""" + bl_label = "Bas Relief" bl_idname = "WORLD_PT_BASRELIEF" bl_space_type = "PROPERTIES" @@ -1229,7 +1229,7 @@ def draw(self, context): # if br: # cutter preset - layout.operator("scene.calculate_bas_relief", text="Calculate relief") + layout.operator("scene.calculate_bas_relief", text="Calculate Relief") layout.prop(br, 'advanced') layout.prop(br, 'use_image_source') if br.use_image_source: @@ -1238,7 +1238,7 @@ def draw(self, context): layout.prop_search(br, 'view_layer_name', bpy.context.scene, "view_layers") layout.prop(br, 'depth_exponent') - layout.label(text="Project parameters") + layout.label(text="Project Parameters") layout.prop(br, 'bit_diameter') layout.prop(br, 'pass_per_radius') layout.prop(br, 'widthmm') @@ -1293,9 +1293,9 @@ class ReliefError(Exception): class DoBasRelief(bpy.types.Operator): - """calculate Bas relief""" + """Calculate Bas Relief""" bl_idname = "scene.calculate_bas_relief" - bl_label = "calculate Bas relief" + bl_label = "Calculate Bas Relief" bl_options = {'REGISTER', 'UNDO'} processes = [] @@ -1322,9 +1322,9 @@ def execute(self, context): class ProblemAreas(bpy.types.Operator): - """find Bas relief Problem areas""" + """Find Bas Relief Problem Areas""" bl_idname = "scene.problemareas_bas_relief" - bl_label = "problem areas Bas relief" + bl_label = "Problem Areas Bas Relief" bl_options = {'REGISTER', 'UNDO'} processes = [] diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index f45f729bc..17a513ae7 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -58,7 +58,7 @@ def addBridge(x, y, rot, sizex, sizey): def addAutoBridges(o): - """attempt to add auto bridges as set of curves""" + """Attempt to Add Auto Bridges as Set of Curves""" utils.getOperationSources(o) bridgecollectionname = o.bridges_collection_name if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is None: @@ -124,7 +124,7 @@ def getBridgesPoly(o): def useBridges(ch, o): - """this adds bridges to chunks, takes the bridge-objects collection and uses the curves inside it as bridges.""" + """This Adds Bridges to Chunks, Takes the Bridge-objects Collection and Uses the Curves Inside It as Bridges.""" bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] if len(bridgecollection.objects) > 0: diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py index 16e0a85df..8730ac7bb 100644 --- a/scripts/addons/cam/cam_chunk.py +++ b/scripts/addons/cam/cam_chunk.py @@ -318,7 +318,7 @@ def reverse(self): self.rotations.reverse() def pop(self, index): - print("WARNING: Popping from chunk is slow", self, index) + print("WARNING: Popping from Chunk Is Slow", self, index) self.points = np.concatenate( (self.points[0:index], self.points[index + 1:]), axis=0 ) @@ -391,7 +391,7 @@ def extend( self.rotations[at_index:at_index] = rotations def clip_points(self, minx, maxx, miny, maxy): - """remove any points outside this range""" + """Remove Any Points Outside This Range""" included_values = (self.points[:, 0] >= minx) and ( (self.points[:, 0] <= maxx) and (self.points[:, 1] >= maxy) @@ -700,12 +700,12 @@ def leadContour(self, o): if self.parents: # if it is inside another parent perimeterDirection ^= 1 # toggle with a bitwise XOR - print("has parent") + print("Has Parent") if perimeterDirection == 1: - print("path direction is Clockwise") + print("Path Direction Is Clockwise") else: - print("path direction is counterclockwise") + print("Path Direction Is Counter Clockwise") iradius = o.lead_in oradius = o.lead_out start = self.points[0] @@ -797,7 +797,7 @@ def _dot_pr(v1, v2): return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] def _applyVerticalLimit(v1, v2, cos_limit): - """test path segment on verticality threshold, for protect_vertical option""" + """Test Path Segment on Verticality Threshold, for Protect_vertical Option""" z = abs(v1[2] - v2[2]) if z > 0: # don't use this vector because dot product of 0,0,1 is trivially just v2[2] @@ -1147,7 +1147,7 @@ def meshFromCurveToChunk(object): lastvi = 0 vtotal = len(mesh.vertices) perc = 0 - progress("processing curve - START - Vertices: " + str(vtotal)) + progress("Processing Curve - START - Vertices: " + str(vtotal)) for vi in range(0, len(mesh.vertices) - 1): co = (mesh.vertices[vi].co + object.location).to_tuple() if not dk.isdisjoint([(vi, vi + 1)]) and d[(vi, vi + 1)] == 1: @@ -1173,7 +1173,7 @@ def meshFromCurveToChunk(object): chunks.append(chunk) chunk = camPathChunkBuilder() - progress("processing curve - FINISHED") + progress("Processing Curve - FINISHED") vi = len(mesh.vertices) - 1 chunk.points.append( @@ -1238,7 +1238,7 @@ def meshFromCurve(o, use_modifiers=False): bpy.ops.object.convert(target="CURVE", keep_original=False) elif co.type != "CURVE": # curve must be a curve... bpy.ops.object.delete() # delete temporary object - raise CamException("Source curve object must be of type CURVE") + raise CamException("Source Curve Object Must Be of Type Curve") co.data.dimensions = "3D" co.data.bevel_depth = 0 co.data.extrude = 0 @@ -1305,7 +1305,7 @@ def chunkToShapely(chunk): def chunksRefine(chunks, o): - """add extra points in between for chunks""" + """Add Extra Points in Between for Chunks""" for ch in chunks: # print('before',len(ch)) newchunk = [] @@ -1338,7 +1338,7 @@ def chunksRefine(chunks, o): def chunksRefineThreshold(chunks, distance, limitdistance): - """add extra points in between for chunks. For medial axis strategy only !""" + """Add Extra Points in Between for Chunks. for Medial Axis Strategy only!""" for ch in chunks: newchunk = [] v2 = Vector(ch.points[0]) diff --git a/scripts/addons/cam/cam_operation.py b/scripts/addons/cam/cam_operation.py index 60dcfbde9..b141a726c 100644 --- a/scripts/addons/cam/cam_operation.py +++ b/scripts/addons/cam/cam_operation.py @@ -57,17 +57,17 @@ class camOperation(PropertyGroup): update=updateRest, ) filename: StringProperty( - name="File name", + name="File Name", default="Operation", update=updateRest, ) auto_export: BoolProperty( - name="Auto export", - description="export files immediately after path calculation", + name="Auto Export", + description="Export files immediately after path calculation", default=True, ) remove_redundant_points: BoolProperty( - name="Symplify Gcode", + name="Simplify G-code", description="Remove redundant points sharing the same angle" " as the start vector", default=False, @@ -80,19 +80,19 @@ class camOperation(PropertyGroup): max=1000, ) hide_all_others: BoolProperty( - name="Hide all others", - description="Hide all other tool pathes except toolpath" - " assotiated with selected CAM operation", + name="Hide All Others", + description="Hide all other tool paths except toolpath" + " associated with selected CAM operation", default=False, ) parent_path_to_object: BoolProperty( - name="Parent path to object", + name="Parent Path to Object", description="Parent generated CAM path to source object", default=False, ) object_name: StringProperty( name='Object', - description='object handled by this operation', + description='Object handled by this operation', update=updateOperationValid, ) collection_name: StringProperty( @@ -101,26 +101,26 @@ class camOperation(PropertyGroup): update=updateOperationValid, ) curve_object: StringProperty( - name='Curve source', - description='curve which will be sampled along the 3d object', + name='Curve Source', + description='Curve which will be sampled along the 3D object', update=operationValid, ) curve_object1: StringProperty( - name='Curve target', - description='curve which will serve as attractor for the ' + name='Curve Target', + description='Curve which will serve as attractor for the ' 'cutter when the cutter follows the curve', update=operationValid, ) source_image_name: StringProperty( - name='image_source', + name='Image Source', description='image source', update=operationValid, ) geometry_source: EnumProperty( - name='Source of data', + name='Data Source', items=( - ('OBJECT', 'object', 'a'), - ('COLLECTION', 'Collection of objects', 'a'), + ('OBJECT', 'Object', 'a'), + ('COLLECTION', 'Collection of Objects', 'a'), ('IMAGE', 'Image', 'a') ), description='Geometry source', @@ -130,30 +130,30 @@ class camOperation(PropertyGroup): cutter_type: EnumProperty( name='Cutter', items=( - ('END', 'End', 'end - flat cutter'), - ('BALLNOSE', 'Ballnose', 'ballnose cutter'), - ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), - ('VCARVE', 'V-carve', 'v carve cutter'), + ('END', 'End', 'End - Flat cutter'), + ('BALLNOSE', 'Ballnose', 'Ballnose cutter'), + ('BULLNOSE', 'Bullnose', 'Bullnose cutter ***placeholder **'), + ('VCARVE', 'V-carve', 'V-carve cutter'), ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), ('CYLCONE', 'Cylinder cone', - 'Cylinder end with a Cone for Parallel - X'), + 'Cylinder End with a Cone for Parallel - X'), ('LASER', 'Laser', 'Laser cutter'), ('PLASMA', 'Plasma', 'Plasma cutter'), ('CUSTOM', 'Custom-EXPERIMENTAL', - 'modelled cutter - not well tested yet.') + 'Modelled cutter - not well tested yet.') ), description='Type of cutter used', default='END', update=updateZbufferImage, ) cutter_object_name: StringProperty( - name='Cutter object', - description='object used as custom cutter for this operation', + name='Cutter Object', + description='Object used as custom cutter for this operation', update=updateZbufferImage, ) machine_axes: EnumProperty( - name='Number of axes', + name='Number of Axes', items=( ('3', '3 axis', 'a'), ('4', '#4 axis - EXPERIMENTAL', 'a'), @@ -171,7 +171,7 @@ class camOperation(PropertyGroup): ) strategy4axis: EnumProperty( - name='4 axis Strategy', + name='4 Axis Strategy', items=( ('PARALLELR', 'Parallel around 1st rotary axis', 'Parallel lines around first rotary axis'), @@ -191,7 +191,7 @@ class camOperation(PropertyGroup): name='Strategy', items=( ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just rotated by 4+5th axes'), + 'All 3 axis strategies, just rotated by 4+5th axes'), ), description='5 axis Strategy', default='INDEXED', @@ -199,7 +199,7 @@ class camOperation(PropertyGroup): ) rotary_axis_1: EnumProperty( - name='Rotary axis', + name='Rotary Axis', items=( ('X', 'X', ''), ('Y', 'Y', ''), @@ -210,7 +210,7 @@ class camOperation(PropertyGroup): update=updateStrategy, ) rotary_axis_2: EnumProperty( - name='Rotary axis 2', + name='Rotary Axis 2', items=( ('X', 'X', ''), ('Y', 'Y', ''), @@ -232,20 +232,20 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) inverse: BoolProperty( - name="Inverse milling", + name="Inverse Milling", description="Male to female model conversion", default=False, update=updateOffsetImage, ) array: BoolProperty( - name="Use array", + name="Use Array", description="Create a repetitive array for producing the " "same thing many times", default=False, update=updateRest, ) array_x_count: IntProperty( - name="X count", + name="X Count", description="X count", default=1, min=1, @@ -253,7 +253,7 @@ class camOperation(PropertyGroup): update=updateRest, ) array_y_count: IntProperty( - name="Y count", + name="Y Count", description="Y count", default=1, min=1, @@ -261,8 +261,8 @@ class camOperation(PropertyGroup): update=updateRest, ) array_x_distance: FloatProperty( - name="X distance", - description="distance between operation origins", + name="X Distance", + description="Distance between operation origins", min=0.00001, max=1.0, default=0.01, @@ -271,8 +271,8 @@ class camOperation(PropertyGroup): update=updateRest, ) array_y_distance: FloatProperty( - name="Y distance", - description="distance between operation origins", + name="Y Distance", + description="Distance between operation origins", min=0.00001, max=1.0, default=0.01, @@ -293,8 +293,8 @@ class camOperation(PropertyGroup): update=updateRest, ) pocketToCurve: BoolProperty( - name="Pocket to curve", - description="generates a curve instead of a path", + name="Pocket to Curve", + description="Generates a curve instead of a path", default=False, update=updateRest, ) @@ -304,14 +304,14 @@ class camOperation(PropertyGroup): items=( ('OUTSIDE', 'Outside', 'a'), ('INSIDE', 'Inside', 'a'), - ('ONLINE', 'On line', 'a') + ('ONLINE', 'On Line', 'a') ), description='Type of cutter used', default='OUTSIDE', update=updateRest, ) outlines_count: IntProperty( - name="Outlines count", + name="Outlines Count", description="Outlines count", default=1, min=1, @@ -326,7 +326,7 @@ class camOperation(PropertyGroup): ) # cutter cutter_id: IntProperty( - name="Tool number", + name="Tool Number", description="For machines which support tool change based on tool id", min=0, max=10000, @@ -334,7 +334,7 @@ class camOperation(PropertyGroup): update=updateRest, ) cutter_diameter: FloatProperty( - name="Cutter diameter", + name="Cutter Diameter", description="Cutter diameter = 2x cutter radius", min=0.000001, max=10, @@ -354,7 +354,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) cutter_length: FloatProperty( - name="#Cutter length", + name="#Cutter Length", description="#not supported#Cutter length", min=0.0, max=100.0, @@ -364,7 +364,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) cutter_flutes: IntProperty( - name="Cutter flutes", + name="Cutter Flutes", description="Cutter flutes", min=1, max=20, @@ -372,8 +372,8 @@ class camOperation(PropertyGroup): update=updateChipload, ) cutter_tip_angle: FloatProperty( - name="Cutter v-carve angle", - description="Cutter v-carve angle", + name="Cutter V-carve Angle", + description="Cutter V-carve angle", min=0.0, max=180.0, default=60.0, @@ -381,7 +381,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) ball_radius: FloatProperty( - name="Ball radius", + name="Ball Radius", description="Radius of", min=0.0, max=0.035, @@ -410,46 +410,46 @@ class camOperation(PropertyGroup): ) Laser_on: StringProperty( - name="Laser ON string", + name="Laser ON String", default="M68 E0 Q100", ) Laser_off: StringProperty( - name="Laser OFF string", + name="Laser OFF String", default="M68 E0 Q0", ) Laser_cmd: StringProperty( - name="Laser command", + name="Laser Command", default="M68 E0 Q", ) Laser_delay: FloatProperty( name="Laser ON Delay", - description="time after fast move to turn on laser and " + description="Time after fast move to turn on laser and " "let machine stabilize", default=0.2, ) Plasma_on: StringProperty( - name="Plasma ON string", + name="Plasma ON String", default="M03", ) Plasma_off: StringProperty( - name="Plasma OFF string", + name="Plasma OFF String", default="M05", ) Plasma_delay: FloatProperty( name="Plasma ON Delay", - description="time after fast move to turn on Plasma and " + description="Time after fast move to turn on Plasma and " "let machine stabilize", default=0.1, ) Plasma_dwell: FloatProperty( - name="Plasma dwell time", + name="Plasma Dwell Time", description="Time to dwell and warm up the torch", default=0.0, ) # steps dist_between_paths: FloatProperty( - name="Distance between toolpaths", + name="Distance Between Toolpaths", default=0.001, min=0.00001, max=32, @@ -458,7 +458,7 @@ class camOperation(PropertyGroup): update=updateRest, ) dist_along_paths: FloatProperty( - name="Distance along toolpaths", + name="Distance Along Toolpaths", default=0.0002, min=0.00001, max=32, @@ -467,7 +467,7 @@ class camOperation(PropertyGroup): update=updateRest, ) parallel_angle: FloatProperty( - name="Angle of paths", + name="Angle of Paths", default=0, min=-360, max=360, @@ -478,7 +478,7 @@ class camOperation(PropertyGroup): ) old_rotation_A: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="old value of Rotate A axis\nto specified angle", default=0, min=-360, @@ -490,7 +490,7 @@ class camOperation(PropertyGroup): ) old_rotation_B: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="old value of Rotate A axis\nto specified angle", default=0, min=-360, @@ -502,7 +502,7 @@ class camOperation(PropertyGroup): ) rotation_A: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="Rotate A axis\nto specified angle", default=0, min=-360, @@ -513,7 +513,7 @@ class camOperation(PropertyGroup): update=updateRotation, ) enable_A: BoolProperty( - name="Enable A axis", + name="Enable A Axis", description="Rotate A axis", default=False, update=updateRotation, @@ -526,7 +526,7 @@ class camOperation(PropertyGroup): ) rotation_B: FloatProperty( - name="B axis angle", + name="B Axis Angle", description="Rotate B axis\nto specified angle", default=0, min=-360, @@ -537,7 +537,7 @@ class camOperation(PropertyGroup): update=updateRotation, ) enable_B: BoolProperty( - name="Enable B axis", + name="Enable B Axis", description="Rotate B axis", default=False, update=updateRotation, @@ -545,7 +545,7 @@ class camOperation(PropertyGroup): # carve only carve_depth: FloatProperty( - name="Carve depth", + name="Carve Depth", default=0.001, min=-.100, max=32, @@ -556,11 +556,11 @@ class camOperation(PropertyGroup): # drill only drill_type: EnumProperty( - name='Holes on', + name='Holes On', items=( - ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), - ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), - ('ALL_POINTS', 'All points in curve', 'a') + ('MIDDLE_SYMETRIC', 'Middle of Symmetric Curves', 'a'), + ('MIDDLE_ALL', 'Middle of All Curve Parts', 'a'), + ('ALL_POINTS', 'All Points in Curve', 'a') ), description='Strategy to detect holes to drill', default='MIDDLE_SYMETRIC', @@ -568,7 +568,7 @@ class camOperation(PropertyGroup): ) # waterline only slice_detail: FloatProperty( - name="Distance betwen slices", + name="Distance Between Slices", default=0.001, min=0.00001, max=32, @@ -577,13 +577,13 @@ class camOperation(PropertyGroup): update=updateRest, ) waterline_fill: BoolProperty( - name="Fill areas between slices", + name="Fill Areas Between Slices", description="Fill areas between slices in waterline mode", default=True, update=updateRest, ) waterline_project: BoolProperty( - name="Project paths - not recomended", + name="Project Paths - Not Recomended", description="Project paths in areas between slices", default=True, update=updateRest, @@ -607,8 +607,8 @@ class camOperation(PropertyGroup): update=updateRest, ) lead_in: FloatProperty( - name="Lead in radius", - description="Lead out radius for torch or laser to turn off", + name="Lead-in Radius", + description="Lead in radius for torch or laser to turn off", min=0.00, max=1, default=0.0, @@ -616,7 +616,7 @@ class camOperation(PropertyGroup): unit="LENGTH", ) lead_out: FloatProperty( - name="Lead out radius", + name="Lead-out Radius", description="Lead out radius for torch or laser to turn off", min=0.00, max=1, @@ -625,7 +625,7 @@ class camOperation(PropertyGroup): unit="LENGTH", ) profile_start: IntProperty( - name="Start point", + name="Start Point", description="Start point offset", min=0, default=0, @@ -635,7 +635,7 @@ class camOperation(PropertyGroup): # helix_angle: FloatProperty(name="Helix ramp angle", default=3*pi/180, min=0.00001, max=pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) minz: FloatProperty( - name="Operation depth end", + name="Operation Depth End", default=-0.01, min=-3, max=3, @@ -645,7 +645,7 @@ class camOperation(PropertyGroup): ) minz_from: EnumProperty( - name='Set max depth from', + name='Max Depth From', description='Set maximum operation depth', items=( ('OBJECT', 'Object', 'Set max operation depth from Object'), @@ -657,10 +657,10 @@ class camOperation(PropertyGroup): ) start_type: EnumProperty( - name='Start type', + name='Start Type', items=( ('ZLEVEL', 'Z level', 'Starts on a given Z level'), - ('OPERATIONRESULT', 'Rest milling', + ('OPERATIONRESULT', 'Rest Milling', 'For rest milling, operations have to be ' 'put in chain for this to work well.'), ), @@ -670,7 +670,7 @@ class camOperation(PropertyGroup): ) maxz: FloatProperty( - name="Operation depth start", + name="Operation Depth Start", description='operation starting depth', default=0, min=-3, @@ -681,7 +681,7 @@ class camOperation(PropertyGroup): ) # EXPERIMENTAL first_down: BoolProperty( - name="First down", + name="First Down", description="First go down on a contour, then go to the next one", default=False, update=update_operation, @@ -692,7 +692,7 @@ class camOperation(PropertyGroup): #################################################### source_image_scale_z: FloatProperty( - name="Image source depth scale", + name="Image Source Depth Scale", default=0.01, min=-1, max=1, @@ -701,7 +701,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_size_x: FloatProperty( - name="Image source x size", + name="Image Source X Size", default=0.1, min=-10, max=10, @@ -710,7 +710,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_offset: FloatVectorProperty( - name='Image offset', + name='Image Offset', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -719,7 +719,7 @@ class camOperation(PropertyGroup): ) source_image_crop: BoolProperty( - name="Crop source image", + name="Crop Source Image", description="Crop source image - the position of the sub-rectangle " "is relative to the whole image, so it can be used for e.g. " "finishing just a part of an image", @@ -727,7 +727,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_start_x: FloatProperty( - name='crop start x', + name='Crop Start X', default=0, min=0, max=100, @@ -736,7 +736,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_start_y: FloatProperty( - name='crop start y', + name='Crop Start Y', default=0, min=0, max=100, @@ -745,7 +745,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_end_x: FloatProperty( - name='crop end x', + name='Crop End X', default=100, min=0, max=100, @@ -754,7 +754,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_end_y: FloatProperty( - name='crop end y', + name='Crop End Y', default=100, min=0, max=100, @@ -770,13 +770,13 @@ class camOperation(PropertyGroup): ambient_behaviour: EnumProperty( name='Ambient', items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), - description='handling ambient surfaces', + description='Handling ambient surfaces', default='ALL', update=updateZbufferImage, ) ambient_radius: FloatProperty( - name="Ambient radius", + name="Ambient Radius", description="Radius around the part which will be milled if " "ambient is set to Around", min=0.0, @@ -788,21 +788,21 @@ class camOperation(PropertyGroup): ) # ambient_cutter = EnumProperty(name='Borders',items=(('EXTRAFORCUTTER', 'Extra for cutter', "Extra space for cutter is cut around the segment"),('ONBORDER', "Cutter on edge", "Cutter goes exactly on edge of ambient with it's middle") ,('INSIDE', "Inside segment", 'Cutter stays within segment') ),description='handling of ambient and cutter size',default='INSIDE') use_limit_curve: BoolProperty( - name="Use limit curve", + name="Use Limit Curve", description="A curve limits the operation area", default=False, update=updateRest, ) ambient_cutter_restrict: BoolProperty( - name="Cutter stays in ambient limits", + name="Cutter Stays in Ambient Limits", description="Cutter doesn't get out from ambient limits otherwise " "goes on the border exactly", default=True, update=updateRest, ) # restricts cutter inside ambient only limit_curve: StringProperty( - name='Limit curve', - description='curve used to limit the area of the operation', + name='Limit Curve', + description='Curve used to limit the area of the operation', update=updateRest, ) @@ -818,7 +818,7 @@ class camOperation(PropertyGroup): update=updateChipload, ) plunge_feedrate: FloatProperty( - name="Plunge speed ", + name="Plunge Speed", description="% of feedrate", min=0.1, max=100.0, @@ -828,8 +828,8 @@ class camOperation(PropertyGroup): update=updateRest, ) plunge_angle: FloatProperty( - name="Plunge angle", - description="What angle is allready considered to plunge", + name="Plunge Angle", + description="What angle is already considered to plunge", default=pi / 6, min=0, max=pi * 0.5, @@ -839,7 +839,7 @@ class camOperation(PropertyGroup): update=updateRest, ) spindle_rpm: FloatProperty( - name="Spindle rpm", + name="Spindle RPM", description="Spindle speed ", min=0, max=60000, @@ -850,21 +850,21 @@ class camOperation(PropertyGroup): # optimization and performance do_simulation_feedrate: BoolProperty( - name="Adjust feedrates with simulation EXPERIMENTAL", + name="Adjust Feedrates with Simulation EXPERIMENTAL", description="Adjust feedrates with simulation", default=False, update=updateRest, ) dont_merge: BoolProperty( - name="Dont merge outlines when cutting", + name="Don't Merge Outlines when Cutting", description="this is usefull when you want to cut around everything", default=False, update=updateRest, ) pencil_threshold: FloatProperty( - name="Pencil threshold", + name="Pencil Threshold", default=0.00002, min=0.00000001, max=1, @@ -874,7 +874,7 @@ class camOperation(PropertyGroup): ) crazy_threshold1: FloatProperty( - name="min engagement", + name="Min Engagement", default=0.02, min=0.00000001, max=100, @@ -882,7 +882,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold5: FloatProperty( - name="optimal engagement", + name="Optimal Engagement", default=0.3, min=0.00000001, max=100, @@ -890,7 +890,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold2: FloatProperty( - name="max engagement", + name="Max Engagement", default=0.5, min=0.00000001, max=100, @@ -898,7 +898,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold3: FloatProperty( - name="max angle", + name="Max Angle", default=2, min=0.00000001, max=100, @@ -906,7 +906,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold4: FloatProperty( - name="test angle step", + name="Test Angle Step", default=0.05, min=0.00000001, max=100, @@ -915,8 +915,8 @@ class camOperation(PropertyGroup): ) # Add pocket operation to medial axis add_pocket_for_medial: BoolProperty( - name="Add pocket operation", - description="clean unremoved material after medial axis", + name="Add Pocket Operation", + description="Clean unremoved material after medial axis", default=True, update=updateRest, ) @@ -930,7 +930,7 @@ class camOperation(PropertyGroup): ) #### medial_axis_threshold: FloatProperty( - name="Long vector threshold", + name="Long Vector Threshold", default=0.001, min=0.00000001, max=100, @@ -939,7 +939,7 @@ class camOperation(PropertyGroup): update=updateRest, ) medial_axis_subdivision: FloatProperty( - name="Fine subdivision", + name="Fine Subdivision", default=0.0002, min=0.00000001, max=100, @@ -951,20 +951,20 @@ class camOperation(PropertyGroup): # bridges use_bridges: BoolProperty( - name="Use bridges", - description="use bridges in cutout", + name="Use Bridges / Tabs", + description="Use bridges in cutout", default=False, update=updateBridges, ) bridges_width: FloatProperty( - name='width of bridges', + name='Bridge / Tab Width', default=0.002, unit='LENGTH', precision=constants.PRECISION, update=updateBridges, ) bridges_height: FloatProperty( - name='height of bridges', + name='Bridge / Tab Height', description="Height from the bottom of the cutting operation", default=0.0005, unit='LENGTH', @@ -972,13 +972,13 @@ class camOperation(PropertyGroup): update=updateBridges, ) bridges_collection_name: StringProperty( - name='Bridges Collection', + name='Bridges / Tabs Collection', description='Collection of curves used as bridges', update=operationValid, ) use_bridge_modifiers: BoolProperty( - name="use bridge modifiers", - description="include bridge curve modifiers using render level when " + name="Use Bridge / Tab Modifiers", + description="Include bridge curve modifiers using render level when " "calculating operation, does not effect original bridge data", default=True, update=updateBridges, @@ -998,8 +998,8 @@ class camOperation(PropertyGroup): # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=constants.PRECISION, update = updateBridges) use_modifiers: BoolProperty( - name="use mesh modifiers", - description="include mesh modifiers using render level when " + name="Use Mesh Modifiers", + description="Include mesh modifiers using render level when " "calculating operation, does not effect original mesh", default=True, update=operationValid, @@ -1013,14 +1013,14 @@ class camOperation(PropertyGroup): # MATERIAL SETTINGS min: FloatVectorProperty( - name='Operation minimum', + name='Operation Minimum', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, subtype="XYZ", ) max: FloatVectorProperty( - name='Operation maximum', + name='Operation Maximum', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -1029,54 +1029,54 @@ class camOperation(PropertyGroup): # g-code options for operation output_header: BoolProperty( - name="output g-code header", - description="output user defined g-code command header" + name="Output G-code Header", + description="Output user defined G-code command header" " at start of operation", default=False, ) gcode_header: StringProperty( - name="g-code header", - description="g-code commands at start of operation." + name="G-code Header", + description="G-code commands at start of operation." " Use ; for line breaks", default="G53 G0", ) enable_dust: BoolProperty( - name="Dust collector", - description="output user defined g-code command header" + name="Dust Collector", + description="Output user defined g-code command header" " at start of operation", default=False, ) gcode_start_dust_cmd: StringProperty( - name="Start dust collector", - description="commands to start dust collection. Use ; for line breaks", + name="Start Dust Collector", + description="Commands to start dust collection. Use ; for line breaks", default="M100", ) gcode_stop_dust_cmd: StringProperty( - name="Stop dust collector", - description="command to stop dust collection. Use ; for line breaks", + name="Stop Dust Collector", + description="Command to stop dust collection. Use ; for line breaks", default="M101", ) enable_hold: BoolProperty( - name="Hold down", - description="output hold down command at start of operation", + name="Hold Down", + description="Output hold down command at start of operation", default=False, ) gcode_start_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." + name="G-code Header", + description="G-code commands at start of operation." " Use ; for line breaks", default="M102", ) gcode_stop_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", + name="G-code Header", + description="G-code commands at end operation. Use ; for line breaks", default="M103", ) @@ -1087,28 +1087,27 @@ class camOperation(PropertyGroup): ) gcode_start_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." - " Use ; for line breaks", + name="Start Mist", + description="Command to start mist. Use ; for line breaks", default="M104", ) gcode_stop_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", + name="Stop Mist", + description="Command to stop mist. Use ; for line breaks", default="M105", ) output_trailer: BoolProperty( - name="output g-code trailer", - description="output user defined g-code command trailer" + name="Output G-code Trailer", + description="Output user defined g-code command trailer" " at end of operation", default=False, ) gcode_trailer: StringProperty( - name="g-code trailer", - description="g-code commands at end of operation." + name="G-code Trailer", + description="G-code commands at end of operation." " Use ; for line breaks", default="M02", ) @@ -1126,40 +1125,40 @@ class camOperation(PropertyGroup): borderwidth = 50 object = None path_object_name: StringProperty( - name='Path object', - description='actual cnc path' + name='Path Object', + description='Actual CNC path' ) # update and tags and related changed: BoolProperty( - name="True if any of the operation settings has changed", - description="mark for update", + name="True if any of the Operation Settings has Changed", + description="Mark for update", default=False, ) update_zbufferimage_tag: BoolProperty( - name="mark zbuffer image for update", - description="mark for update", + name="Mark Z-Buffer Image for Update", + description="Mark for update", default=True, ) update_offsetimage_tag: BoolProperty( - name="mark offset image for update", - description="mark for update", + name="Mark Offset Image for Update", + description="Mark for update", default=True, ) update_silhouete_tag: BoolProperty( - name="mark silhouete image for update", - description="mark for update", + name="Mark Silhouette Image for Update", + description="Mark for update", default=True, ) update_ambient_tag: BoolProperty( - name="mark ambient polygon for update", - description="mark for update", + name="Mark Ambient Polygon for Update", + description="Mark for update", default=True, ) update_bullet_collision_tag: BoolProperty( - name="mark bullet collisionworld for update", - description="mark for update", + name="Mark Bullet Collision World for Update", + description="Mark for update", default=True, ) @@ -1169,24 +1168,24 @@ class camOperation(PropertyGroup): default=True, ) changedata: StringProperty( - name='changedata', + name='Changedata', description='change data for checking if stuff changed.', ) # process related data computing: BoolProperty( - name="Computing right now", + name="Computing Right Now", description="", default=False, ) pid: IntProperty( - name="process id", + name="Process Id", description="Background process id", default=-1, ) outtext: StringProperty( - name='outtext', + name='Outtext', description='outtext', default='', ) diff --git a/scripts/addons/cam/chain.py b/scripts/addons/cam/chain.py index 44b60b213..bd793ad9f 100644 --- a/scripts/addons/cam/chain.py +++ b/scripts/addons/cam/chain.py @@ -10,7 +10,7 @@ # this type is defined just to hold reference to operations for chains class opReference(PropertyGroup): name: StringProperty( - name="Operation name", + name="Operation Name", default="Operation", ) computing = False # for UiList display @@ -19,13 +19,13 @@ class opReference(PropertyGroup): # chain is just a set of operations which get connected on export into 1 file. class camChain(PropertyGroup): index: IntProperty( - name="index", - description="index in the hard-defined camChains", + name="Index", + description="Index in the hard-defined camChains", default=-1, ) active_operation: IntProperty( - name="active operation", - description="active operation in chain", + name="Active Operation", + description="Active operation in chain", default=-1, ) name: StringProperty( @@ -33,7 +33,7 @@ class camChain(PropertyGroup): default="Chain", ) filename: StringProperty( - name="File name", + name="File Name", default="Chain", ) # filename of valid: BoolProperty( @@ -42,7 +42,7 @@ class camChain(PropertyGroup): default=True, ) computing: BoolProperty( - name="Computing right now", + name="Computing Right Now", description="", default=False, ) diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 8c7200fa9..074fcfe19 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -45,8 +45,8 @@ def getCutterBullet(o): - """cutter for rigidbody simulation collisions - note that everything is 100x bigger for simulation precision.""" + """Cutter for Rigidbody Simulation Collisions + Note that Everything Is 100x Bigger for Simulation Precision.""" s = bpy.context.scene if s.objects.get('cutter') is not None: @@ -176,7 +176,7 @@ def getCutterBullet(o): def subdivideLongEdges(ob, threshold): - print('subdividing long edges') + print('Subdividing Long Edges') m = ob.data scale = (ob.scale.x + ob.scale.y + ob.scale.z) / 3 subdivides = [] @@ -220,8 +220,8 @@ def subdivideLongEdges(ob, threshold): # def prepareBulletCollision(o): - """prepares all objects needed for sampling with bullet collision""" - progress('preparing collisions') + """Prepares All Objects Needed for Sampling with Bullet Collision""" + progress('Preparing Collisions') print(o.name) active_collection = bpy.context.view_layer.active_layer_collection.collection @@ -332,7 +332,7 @@ def getSampleBullet(cutter, x, y, radius, startz, endz): def getSampleBulletNAxis(cutter, startpoint, endpoint, rotation, cutter_compensation): - """fully 3d collision test for NAxis milling""" + """Fully 3D Collision Test for N-Axis Milling""" cutterVec = Vector((0, 0, 1)) * cutter_compensation # cutter compensation vector - cutter physics object has center in the middle, while cam needs the tip position. cutterVec.rotate(Euler(rotation)) diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index 0387caadc..805709276 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -49,13 +49,13 @@ class CamCurveHatch(Operator): - """perform hatch operation on single or multiple curves""" # by Alain Pelletier September 2021 + """Perform Hatch Operation on Single or Multiple Curves""" # by Alain Pelletier September 2021 bl_idname = "object.curve_hatch" - bl_label = "CrossHatch curve" + bl_label = "CrossHatch Curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} angle: FloatProperty( - name="angle", + name="Angle", default=0, min=-pi/2, max=pi/2, @@ -63,7 +63,7 @@ class CamCurveHatch(Operator): subtype="ANGLE", ) distance: FloatProperty( - name="spacing", + name="Spacing", default=0.015, min=0, max=3.0, @@ -87,7 +87,7 @@ class CamCurveHatch(Operator): unit="LENGTH", ) amount: IntProperty( - name="amount", + name="Amount", default=10, min=1, max=10000, @@ -101,14 +101,14 @@ class CamCurveHatch(Operator): default=False, ) contour_separate: BoolProperty( - name="Contour separate", + name="Contour Separate", default=False, ) pocket_type: EnumProperty( - name='Type pocket', + name='Type Pocket', items=( - ('BOUNDS', 'makes a bounds rectangle', 'makes a bounding square'), - ('POCKET', 'Pocket', 'makes a pocket inside a closed loop') + ('BOUNDS', 'Makes a bounds rectangle', 'Makes a bounding square'), + ('POCKET', 'Pocket', 'Makes a pocket inside a closed loop') ), description='Type of pocket', default='BOUNDS', @@ -217,9 +217,9 @@ def execute(self, context): class CamCurvePlate(Operator): - """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 + """Perform Generates Rounded Plate with Mounting Holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_plate" - bl_label = "Sign plate" + bl_label = "Sign Plate" bl_options = {'REGISTER', 'UNDO', 'PRESET'} radius: FloatProperty( @@ -231,7 +231,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) width: FloatProperty( - name="Width of plate", + name="Width of Plate", default=0.3048, min=0, max=3.0, @@ -239,7 +239,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of plate", + name="Height of Plate", default=0.457, min=0, max=3.0, @@ -247,7 +247,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_diameter: FloatProperty( - name="Hole diameter", + name="Hole Diameter", default=0.01, min=0, max=3.0, @@ -263,7 +263,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_vdist: FloatProperty( - name="Hole Vert distance", + name="Hole Vert Distance", default=0.400, min=0, max=3.0, @@ -271,7 +271,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_hdist: FloatProperty( - name="Hole horiz distance", + name="Hole Horiz Distance", default=0, min=0, max=3.0, @@ -279,19 +279,19 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_hamount: IntProperty( - name="Hole horiz amount", + name="Hole Horiz Amount", default=1, min=0, max=50, ) resolution: IntProperty( - name="Spline resolution", + name="Spline Resolution", default=50, min=3, max=150, ) plate_type: EnumProperty( - name='Type plate', + name='Type Plate', items=( ('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), ('COVE', 'Cove corner', @@ -492,13 +492,13 @@ def execute(self, context): class CamCurveFlatCone(Operator): - """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 + """Perform Generates Rounded Plate with Mounting Holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_flat_cone" - bl_label = "Cone flat calculator" + bl_label = "Cone Flat Calculator" bl_options = {'REGISTER', 'UNDO', 'PRESET'} small_d: FloatProperty( - name="small diameter", + name="Small Diameter", default=.025, min=0, max=0.1, @@ -506,7 +506,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) large_d: FloatProperty( - name="large diameter", + name="Large Diameter", default=0.3048, min=0, max=3.0, @@ -514,7 +514,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of cone", + name="Height of Cone", default=0.457, min=0, max=3.0, @@ -522,7 +522,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) tab: FloatProperty( - name="tab witdh", + name="Tab Witdh", default=0.01, min=0, max=0.100, @@ -530,7 +530,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) intake: FloatProperty( - name="intake diameter", + name="Intake Diameter", default=0, min=0, max=0.200, @@ -538,7 +538,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) intake_skew: FloatProperty( - name="intake_skew", + name="Intake Skew", default=1, min=0.1, max=4, @@ -589,13 +589,13 @@ def execute(self, context): class CamCurveMortise(Operator): - """Generates mortise along a curve""" # by Alain Pelletier December 2021 + """Generates Mortise Along a Curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_mortise" bl_label = "Mortise" bl_options = {'REGISTER', 'UNDO', 'PRESET'} finger_size: BoolProperty( - name="kurf bending only", + name="Kurf Bending only", default=False, ) finger_size: FloatProperty( @@ -615,7 +615,7 @@ class CamCurveMortise(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -623,28 +623,28 @@ class CamCurveMortise(Operator): unit="LENGTH", ) plate_thickness: FloatProperty( - name="Drawer plate thickness", + name="Drawer Plate Thickness", default=0.00477, min=0.001, max=3.0, unit="LENGTH", ) side_height: FloatProperty( - name="side height", + name="Side Height", default=0.05, min=0.001, max=3.0, unit="LENGTH", ) flex_pocket: FloatProperty( - name="Flex pocket", + name="Flex Pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH", ) top_bottom: BoolProperty( - name="Side Top & bottom fingers", + name="Side Top & Bottom Fingers", default=True, ) opencurve: BoolProperty( @@ -652,7 +652,7 @@ class CamCurveMortise(Operator): default=False, ) adaptive: FloatProperty( - name="Adaptive angle threshold", + name="Adaptive Angle Threshold", default=0.0, min=0.000, max=2, @@ -660,7 +660,7 @@ class CamCurveMortise(Operator): unit="ROTATION", ) double_adaptive: BoolProperty( - name="Double adaptive Pockets", + name="Double Adaptive Pockets", default=False, ) @@ -732,7 +732,7 @@ def execute(self, context): class CamCurveInterlock(Operator): - """Generates interlock along a curve""" # by Alain Pelletier December 2021 + """Generates Interlock Along a Curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_interlock" bl_label = "Interlock" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -746,7 +746,7 @@ class CamCurveInterlock(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -754,7 +754,7 @@ class CamCurveInterlock(Operator): unit="LENGTH", ) plate_thickness: FloatProperty( - name="Plate thickness", + name="Plate Thickness", default=0.00477, min=0.001, max=3.0, @@ -765,11 +765,11 @@ class CamCurveInterlock(Operator): default=False, ) interlock_type: EnumProperty( - name='Type of interlock', + name='Type of Interlock', items=( - ('TWIST', 'Twist', 'Iterlock requires 1/4 turn twist'), + ('TWIST', 'Twist', 'Interlock requires 1/4 turn twist'), ('GROOVE', 'Groove', 'Simple sliding groove'), - ('PUZZLE', 'Puzzle interlock', 'puzzle good for flat joints') + ('PUZZLE', 'Puzzle Interlock', 'Puzzle good for flat joints') ), description='Type of interlock', default='GROOVE', @@ -781,7 +781,7 @@ class CamCurveInterlock(Operator): max=100, ) tangent_angle: FloatProperty( - name="Tangent deviation", + name="Tangent Deviation", default=0.0, min=0.000, max=2, @@ -789,7 +789,7 @@ class CamCurveInterlock(Operator): unit="ROTATION", ) fixed_angle: FloatProperty( - name="fixed angle", + name="Fixed Angle", default=0.0, min=0.000, max=2, @@ -852,7 +852,7 @@ def execute(self, context): class CamCurveDrawer(Operator): - """Generates drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator + """Generates Drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator bl_idname = "object.curve_drawer" bl_label = "Drawer" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -866,7 +866,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) width: FloatProperty( - name="Width of Drawer", + name="Drawer Width", default=0.125, min=0, max=3.0, @@ -874,7 +874,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of drawer", + name="Drawer Height", default=0.07, min=0, max=3.0, @@ -890,7 +890,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -898,7 +898,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) finger_inset: FloatProperty( - name="Finger inset", + name="Finger Inset", default=0.0, min=0.0, max=0.01, @@ -906,7 +906,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_plate_thickness: FloatProperty( - name="Drawer plate thickness", + name="Drawer Plate Thickness", default=0.00477, min=0.001, max=3.0, @@ -914,7 +914,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_hole_diameter: FloatProperty( - name="Drawer hole diameter", + name="Drawer Hole Diameter", default=0.02, min=0.00001, max=0.5, @@ -922,7 +922,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_hole_offset: FloatProperty( - name="Drawer hole offset", + name="Drawer Hole Offset", default=0.0, min=-0.5, max=0.5, @@ -930,11 +930,11 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) overcut: BoolProperty( - name="Add overcut", + name="Add Overcut", default=False, ) overcut_diameter: FloatProperty( - name="Overcut toool Diameter", + name="Overcut Tool Diameter", default=0.003175, min=-0.001, max=0.5, @@ -1066,13 +1066,13 @@ def execute(self, context): class CamCurvePuzzle(Operator): - """Generates Puzzle joints and interlocks""" # by Alain Pelletier December 2021 + """Generates Puzzle Joints and Interlocks""" # by Alain Pelletier December 2021 bl_idname = "object.curve_puzzle" - bl_label = "Puzzle joints" + bl_label = "Puzzle Joints" bl_options = {'REGISTER', 'UNDO', 'PRESET'} diameter: FloatProperty( - name="tool diameter", + name="Tool Diameter", default=0.003175, min=0.001, max=3.0, @@ -1080,7 +1080,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.00005, min=0, max=0.003, @@ -1094,7 +1094,7 @@ class CamCurvePuzzle(Operator): max=100, ) stem_size: IntProperty( - name="size of the stem", + name="Size of the Stem", default=2, min=1, max=200, @@ -1108,7 +1108,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) height: FloatProperty( - name="height or thickness", + name="Height or Thickness", default=0.025, min=0.005, max=3.0, @@ -1117,7 +1117,7 @@ class CamCurvePuzzle(Operator): ) angle: FloatProperty( - name="angle A", + name="Angle A", default=pi/4, min=-10, max=10, @@ -1125,7 +1125,7 @@ class CamCurvePuzzle(Operator): unit="ROTATION", ) angleb: FloatProperty( - name="angle B", + name="Angle B", default=pi/4, min=-10, max=10, @@ -1143,7 +1143,7 @@ class CamCurvePuzzle(Operator): ) interlock_type: EnumProperty( - name='Type of shape', + name='Type of Shape', items=( ('JOINT', 'Joint', 'Puzzle Joint interlock'), ('BAR', 'Bar', 'Bar interlock'), @@ -1161,7 +1161,7 @@ class CamCurvePuzzle(Operator): default='CURVET', ) gender: EnumProperty( - name='Type gender', + name='Type Gender', items=( ('MF', 'Male-Receptacle', 'Male and receptacle'), ('F', 'Receptacle only', 'Receptacle'), @@ -1171,7 +1171,7 @@ class CamCurvePuzzle(Operator): default='MF', ) base_gender: EnumProperty( - name='Base gender', + name='Base Gender', items=( ('MF', 'Male - Receptacle', 'Male - Receptacle'), ('F', 'Receptacle', 'Receptacle'), @@ -1181,7 +1181,7 @@ class CamCurvePuzzle(Operator): default='M', ) multiangle_gender: EnumProperty( - name='Multiangle gender', + name='Multiangle Gender', items=( ('MMF', 'Male Male Receptacle', 'M M F'), ('MFF', 'Male Receptacle Receptacle', 'M F F') @@ -1208,38 +1208,38 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) twist_percent: FloatProperty( - name="Twist neck", + name="Twist Neck", default=0.3, min=0.1, max=0.9, precision=4, ) twist_keep: BoolProperty( - name="keep Twist holes", + name="Keep Twist Holes", default=False, ) twist_line: BoolProperty( - name="Add Twist to bar", + name="Add Twist to Bar", default=False, ) twist_line_amount: IntProperty( - name="amount of separators", + name="Amount of Separators", default=2, min=1, max=600, ) twist_separator: BoolProperty( - name="Add Twist separator", + name="Add Twist Separator", default=False, ) twist_separator_amount: IntProperty( - name="amount of separators", + name="Amount of Separators", default=2, min=2, max=600, ) twist_separator_spacing: FloatProperty( - name="Separator spacing", + name="Separator Spacing", default=0.025, min=-0.004, max=1.0, @@ -1247,7 +1247,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) twist_separator_edge_distance: FloatProperty( - name="Separator edge distance", + name="Separator Edge Distance", default=0.01, min=0.0005, max=0.1, @@ -1255,29 +1255,29 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) tile_x_amount: IntProperty( - name="amount of x fingers", + name="Amount of X Fingers", default=2, min=1, max=600, ) tile_y_amount: IntProperty( - name="amount of y fingers", + name="Amount of Y Fingers", default=2, min=1, max=600, ) interlock_amount: IntProperty( - name="Interlock amount on curve", + name="Interlock Amount on Curve", default=2, min=0, max=200, ) overcut: BoolProperty( - name="Add overcut", + name="Add Overcut", default=False, ) overcut_diameter: FloatProperty( - name="Overcut toool Diameter", + name="Overcut Tool Diameter", default=0.003175, min=-0.001, max=0.5, @@ -1459,7 +1459,7 @@ def execute(self, context): class CamCurveGear(Operator): - """Generates involute Gears // version 1.1 by Leemon Baird, 2011, Leemon@Leemon.com + """Generates Involute Gears // version 1.1 by Leemon Baird, 2011, Leemon@Leemon.com http://www.thingiverse.com/thing:5505""" # ported by Alain Pelletier January 2022 bl_idname = "object.curve_gear" @@ -1467,7 +1467,7 @@ class CamCurveGear(Operator): bl_options = {'REGISTER', 'UNDO', 'PRESET'} tooth_spacing: FloatProperty( - name="distance per tooth", + name="Distance per Tooth", default=0.010, min=0.001, max=1.0, @@ -1475,19 +1475,19 @@ class CamCurveGear(Operator): unit="LENGTH", ) tooth_amount: IntProperty( - name="Amount of teeth", + name="Amount of Teeth", default=7, min=4, ) spoke_amount: IntProperty( - name="Amount of spokes", + name="Amount of Spokes", default=4, min=0, ) hole_diameter: FloatProperty( - name="Hole diameter", + name="Hole Diameter", default=0.003175, min=0, max=3.0, @@ -1495,7 +1495,7 @@ class CamCurveGear(Operator): unit="LENGTH", ) rim_size: FloatProperty( - name="Rim size", + name="Rim Size", default=0.003175, min=0, max=3.0, @@ -1503,7 +1503,7 @@ class CamCurveGear(Operator): unit="LENGTH", ) hub_diameter: FloatProperty( - name="Hub diameter", + name="Hub Diameter", default=0.005, min=0, max=3.0, @@ -1544,14 +1544,14 @@ class CamCurveGear(Operator): unit="LENGTH", ) rack_tooth_per_hole: IntProperty( - name="teeth per mounting hole", + name="Teeth per Mounting Hole", default=7, min=2, ) gear_type: EnumProperty( - name='Type of gear', + name='Type of Gear', items=( - ('PINION', 'Pinion', 'circular gear'), + ('PINION', 'Pinion', 'Circular Gear'), ('RACK', 'Rack', 'Straight Rack') ), description='Type of gear', diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index bba928244..d8ddb20d6 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -35,14 +35,14 @@ class CamSineCurve(bpy.types.Operator): - """Object sine """ # by Alain Pelletier april 2021 + """Object Sine """ # by Alain Pelletier april 2021 bl_idname = "object.sine" - bl_label = "Create Periodic wave" + bl_label = "Create Periodic Wave" bl_options = {'REGISTER', 'UNDO', 'PRESET'} # zstring: StringProperty(name="Z equation", description="Equation for z=F(u,v)", default="0.05*sin(2*pi*4*t)" ) axis: EnumProperty( - name="displacement axis", + name="Displacement Axis", items=( ('XY', 'Y to displace X axis', 'Y constant; X sine displacement'), ('YX', 'X to displace Y axis', 'X constant; Y sine displacement'), @@ -78,7 +78,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) beatperiod: FloatProperty( - name="Beat Period offset", + name="Beat Period Offset", default=0.0, min=0.0, max=100, @@ -86,7 +86,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) shift: FloatProperty( - name="phase shift", + name="Phase Shift", default=0, min=-360, max=360, @@ -94,7 +94,7 @@ class CamSineCurve(bpy.types.Operator): unit="ROTATION", ) offset: FloatProperty( - name="offset", + name="Offset", default=0, min=- 1.0, @@ -103,13 +103,13 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) iteration: IntProperty( - name="iteration", + name="Iteration", default=100, min=50, max=2000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=0.5, min=-3.0, max=3, @@ -117,7 +117,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-3.0, max=3, @@ -125,7 +125,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) wave_distance: FloatProperty( - name="distance between multiple waves", + name="Distance Between Multiple Waves", default=0.0, min=0.0, max=100, @@ -133,7 +133,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) wave_angle_offset: FloatProperty( - name="angle offset for multiple waves", + name="Angle Offset for Multiple Waves", default=pi/2, min=-200*pi, max=200*pi, @@ -141,7 +141,7 @@ class CamSineCurve(bpy.types.Operator): unit="ROTATION", ) wave_amount: IntProperty( - name="amount of multiple waves", + name="Amount of Multiple Waves", default=1, min=1, max=2000, @@ -195,7 +195,7 @@ def f(t, offset: float = 0.0, angle_offset: float = 0.0): class CamLissajousCurve(bpy.types.Operator): """Lissajous """ # by Alain Pelletier april 2021 bl_idname = "object.lissajous" - bl_label = "Create Lissajous figure" + bl_label = "Create Lissajous Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} amplitude_A: FloatProperty( @@ -264,7 +264,7 @@ class CamLissajousCurve(bpy.types.Operator): unit="LENGTH", ) shift: FloatProperty( - name="phase shift", + name="Phase Shift", default=0, min=-360, max=360, @@ -273,13 +273,13 @@ class CamLissajousCurve(bpy.types.Operator): ) iteration: IntProperty( - name="iteration", + name="Iteration", default=500, min=50, max=10000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=11, min=-3.0, max=1000000, @@ -287,7 +287,7 @@ class CamLissajousCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-10.0, max=3, @@ -330,20 +330,20 @@ def f(t, offset: float = 0.0): class CamHypotrochoidCurve(bpy.types.Operator): - """hypotrochoid """ # by Alain Pelletier april 2021 + """Hypotrochoid """ # by Alain Pelletier april 2021 bl_idname = "object.hypotrochoid" - bl_label = "Create Spirograph type figure" + bl_label = "Create Spirograph Type Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} typecurve: EnumProperty( - name="type of curve", + name="Type of Curve", items=( - ('hypo', 'Hypotrochoid', 'inside ring'), - ('epi', 'Epicycloid', 'outside inner ring') + ('hypo', 'Hypotrochoid', 'Inside ring'), + ('epi', 'Epicycloid', 'Outside inner ring') ), ) R: FloatProperty( - name="Big circle radius", + name="Big Circle Radius", default=0.25, min=0.001, max=100, @@ -351,7 +351,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) r: FloatProperty( - name="Small circle radius", + name="Small Circle Radius", default=0.18, min=0.0001, max=100, @@ -359,7 +359,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) d: FloatProperty( - name="distance from center of interior circle", + name="Distance from Center of Interior Circle", default=0.050, min=0, max=100, @@ -367,7 +367,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) dip: FloatProperty( - name="variable depth from center", + name="Variable Depth from Center", default=0.00, min=-100, max=100, @@ -424,35 +424,35 @@ def f(t, offset: float = 0.0): class CamCustomCurve(bpy.types.Operator): - """Object customCurve """ # by Alain Pelletier april 2021 + """Object Custom Curve """ # by Alain Pelletier april 2021 bl_idname = "object.customcurve" - bl_label = "Create custom curve" + bl_label = "Create Custom Curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} xstring: StringProperty( - name="X equation", + name="X Equation", description="Equation x=F(t)", default="t", ) ystring: StringProperty( - name="Y equation", + name="Y Equation", description="Equation y=F(t)", default="0", ) zstring: StringProperty( - name="Z equation", + name="Z Equation", description="Equation z=F(t)", default="0.05*sin(2*pi*4*t)", ) iteration: IntProperty( - name="iteration", + name="Iteration", default=100, min=50, max=2000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=0.5, min=-3.0, max=10, @@ -460,7 +460,7 @@ class CamCustomCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-3.0, max=3, diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index dae598fcc..ac3e94731 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -46,19 +46,19 @@ # boolean operations for curve objects class CamCurveBoolean(Operator): - """perform Boolean operation on two or more curves""" + """Perform Boolean Operation on Two or More Curves""" bl_idname = "object.curve_boolean" bl_label = "Curve Boolean" bl_options = {'REGISTER', 'UNDO'} boolean_type: EnumProperty( - name='type', + name='Type', items=( ('UNION', 'Union', ''), ('DIFFERENCE', 'Difference', ''), ('INTERSECT', 'Intersect', '') ), - description='boolean type', + description='Boolean type', default='UNION' ) @@ -76,7 +76,7 @@ def execute(self, context): class CamCurveConvexHull(Operator): - """perform hull operation on single or multiple curves""" # by Alain Pelletier april 2021 + """Perform Hull Operation on Single or Multiple Curves""" # by Alain Pelletier april 2021 bl_idname = "object.convex_hull" bl_label = "Convex Hull" bl_options = {'REGISTER', 'UNDO'} @@ -92,13 +92,13 @@ def execute(self, context): # intarsion or joints class CamCurveIntarsion(Operator): - """makes curve cuttable both inside and outside, for intarsion and joints""" + """Makes Curve Cuttable Both Inside and Outside, for Intarsion and Joints""" bl_idname = "object.curve_intarsion" bl_label = "Intarsion" bl_options = {'REGISTER', 'UNDO', 'PRESET'} diameter: FloatProperty( - name="cutter diameter", + name="Cutter Diameter", default=.001, min=0, max=0.025, @@ -106,7 +106,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) tolerance: FloatProperty( - name="cutout Tolerance", + name="Cutout Tolerance", default=.0001, min=0, max=0.005, @@ -114,7 +114,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) backlight: FloatProperty( - name="Backlight seat", + name="Backlight Seat", default=0.000, min=0, max=0.010, @@ -122,7 +122,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) perimeter_cut: FloatProperty( - name="Perimeter cut offset", + name="Perimeter Cut Offset", default=0.000, min=0, max=0.100, @@ -130,7 +130,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) base_thickness: FloatProperty( - name="Base material thickness", + name="Base Material Thickness", default=0.000, min=0, max=0.100, @@ -138,7 +138,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) intarsion_thickness: FloatProperty( - name="Intarsion material thickness", + name="Intarsion Material Thickness", default=0.000, min=0, max=0.100, @@ -146,7 +146,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) backlight_depth_from_top: FloatProperty( - name="Backlight well depth", + name="Backlight Well Depth", default=0.000, min=0, max=0.100, @@ -214,13 +214,13 @@ def execute(self, context): # intarsion or joints class CamCurveOvercuts(Operator): - """Adds overcuts for slots""" + """Adds Overcuts for Slots""" bl_idname = "object.curve_overcuts" - bl_label = "Add Overcuts" + bl_label = "Add Overcuts - A" bl_options = {'REGISTER', 'UNDO'} diameter: FloatProperty( - name="diameter", + name="Diameter", default=.003175, min=0, max=100, @@ -228,7 +228,7 @@ class CamCurveOvercuts(Operator): unit="LENGTH", ) threshold: FloatProperty( - name="threshold", + name="Threshold", default=pi / 2 * .99, min=-3.14, max=3.14, @@ -237,7 +237,7 @@ class CamCurveOvercuts(Operator): unit="ROTATION", ) do_outer: BoolProperty( - name="Outer polygons", + name="Outer Polygons", default=True, ) invert: BoolProperty( @@ -322,13 +322,13 @@ def execute(self, context): # Overcut type B class CamCurveOvercutsB(Operator): - """Adds overcuts for slots""" + """Adds Overcuts for Slots""" bl_idname = "object.curve_overcuts_b" - bl_label = "Add Overcuts-B" + bl_label = "Add Overcuts - B" bl_options = {'REGISTER', 'UNDO'} diameter: FloatProperty( - name="Tool diameter", + name="Tool Diameter", default=.003175, description='Tool bit diameter used in cut operation', min=0, @@ -337,7 +337,7 @@ class CamCurveOvercutsB(Operator): unit="LENGTH", ) style: EnumProperty( - name="style", + name="Style", items=( ('OPEDGE', 'opposite edge', 'place corner overcuts on opposite edges'), @@ -359,7 +359,7 @@ class CamCurveOvercutsB(Operator): unit="ROTATION", ) do_outer: BoolProperty( - name="Include outer curve", + name="Include Outer Curve", description='Include the outer curve if there are curves inside', default=True, ) @@ -369,7 +369,7 @@ class CamCurveOvercutsB(Operator): default=True, ) otherEdge: BoolProperty( - name="other edge", + name="Other Edge", description='change to the other edge for the overcut to be on', default=False, ) @@ -592,9 +592,9 @@ def getCornerDelta(curidx, nextidx): class CamCurveRemoveDoubles(Operator): - """curve remove doubles""" + """Curve Remove Doubles""" bl_idname = "object.curve_remove_doubles" - bl_label = "C-Remove doubles" + bl_label = "Remove Curve Doubles" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -604,11 +604,11 @@ def poll(cls, context): def execute(self, context): obs = bpy.context.selected_objects #bpy.context.object.data.dimensions = '3D' - bpy.context.object.data.resolution_u = 32 + #bpy.context.object.data.resolution_u = 32 for ob in obs: bpy.context.view_layer.objects.active = ob if bpy.context.mode == 'OBJECT': - bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() bpy.ops.curve.select_all() bpy.ops.curve.decimate(ratio=1) bpy.ops.curve.remove_double(distance=0.0001) @@ -618,13 +618,13 @@ def execute(self, context): class CamMeshGetPockets(Operator): - """Detect pockets in a mesh and extract them as curves""" + """Detect Pockets in a Mesh and Extract Them as Curves""" bl_idname = "object.mesh_get_pockets" - bl_label = "Get pocket surfaces" + bl_label = "Get Pocket Surfaces" bl_options = {'REGISTER', 'UNDO'} threshold: FloatProperty( - name="horizontal threshold", + name="Horizontal Threshold", description="How horizontal the surface must be for a pocket: " "1.0 perfectly flat, 0.0 is any orientation", default=.99, @@ -633,8 +633,8 @@ class CamMeshGetPockets(Operator): precision=4, ) zlimit: FloatProperty( - name="z limit", - description="maximum z height considered for pocket operation, " + name="Z Limit", + description="Maximum z height considered for pocket operation, " "default is 0.0", default=0.0, min=-1000.0, @@ -729,13 +729,13 @@ def execute(self, context): # this operator finds the silhouette of objects(meshes, curves just get converted) and offsets it. class CamOffsetSilhouete(Operator): - """Curve offset operation """ + """Curve Offset Operation """ bl_idname = "object.silhouete_offset" - bl_label = "Silhouete offset" + bl_label = "Silhouette Offset" bl_options = {'REGISTER', 'UNDO', 'PRESET'} offset: FloatProperty( - name="offset", + name="Offset", default=.003, min=-100, max=100, @@ -751,7 +751,7 @@ class CamOffsetSilhouete(Operator): unit="LENGTH", ) style: EnumProperty( - name="type of curve", + name="Type of Curve", items=( ('1', 'Round', ''), ('2', 'Mitre', ''), @@ -759,7 +759,7 @@ class CamOffsetSilhouete(Operator): ), ) opencurve: BoolProperty( - name="Dialate open curve", + name="Dialate Open Curve", default=False, ) @@ -804,9 +804,9 @@ def execute(self, context): # Finds object silhouette, usefull for meshes, since with curves it's not needed. class CamObjectSilhouete(Operator): - """Object silhouete """ + """Object Silhouette""" bl_idname = "object.silhouete" - bl_label = "Object silhouete" + bl_label = "Object Silhouette" bl_options = {'REGISTER', 'UNDO'} @classmethod diff --git a/scripts/addons/cam/engine.py b/scripts/addons/cam/engine.py index 8e426006b..5316857d4 100644 --- a/scripts/addons/cam/engine.py +++ b/scripts/addons/cam/engine.py @@ -25,7 +25,7 @@ class BLENDERCAM_ENGINE(RenderEngine): bl_idname = "BLENDERCAM_RENDER" - bl_label = "Cam" + bl_label = "BlenderCAM" bl_use_eevee_viewport = True diff --git a/scripts/addons/cam/gcodeimportparser.py b/scripts/addons/cam/gcodeimportparser.py index ee09d06ac..3d5143ec2 100644 --- a/scripts/addons/cam/gcodeimportparser.py +++ b/scripts/addons/cam/gcodeimportparser.py @@ -46,7 +46,7 @@ def import_gcode(context, filepath): model.draw(split_layers=False) now = time.time() - print("importing Gcode took ", round(now - then, 1), "seconds") + print("Importing Gcode Took ", round(now - then, 1), "Seconds") return {'FINISHED'} diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index 19b0f0c9d..dc68d4a4c 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -91,9 +91,9 @@ def pointonline(a, b, c, tolerence): def exportGcodePath(filename, vertslist, operations): - """exports gcode with the heeks nc adopted library.""" + """Exports G-code with the Heeks NC Adopted Library.""" print("EXPORT") - progress('exporting gcode file') + progress('Exporting G-code File') t = time.time() s = bpy.context.scene m = s.cam_machine @@ -113,7 +113,7 @@ def exportGcodePath(filename, vertslist, operations): if totops > m.split_limit: split = True filesnum = ceil(totops / m.split_limit) - print('file will be separated into %i files' % filesnum) + print('File Will Be Separated Into %i Files' % filesnum) print('1') basefilename = bpy.data.filepath[:- @@ -203,7 +203,7 @@ def startNewFile(): # start program c.program_begin(0, filename) c.flush_nc() - c.comment('G-code generated with BlenderCAM and NC library') + c.comment('G-code Generated with BlenderCAM and NC library') # absolute coordinates c.absolute() @@ -571,7 +571,7 @@ async def getPath(context, operation): # should do all path calculations. pr.enable() await getPath3axis(context, operation) pr.disable() - pr.dump_stats(time.strftime("blendercam_%Y%m%d_%H%M.prof")) + pr.dump_stats(time.strftime("BlenderCAM_%Y%m%d_%H%M.prof")) else: await getPath3axis(context, operation) @@ -600,8 +600,8 @@ async def getPath(context, operation): # should do all path calculations. def getChangeData(o): - """this is a function to check if object props have changed, - to see if image updates are needed in the image based method""" + """This Is a Function to Check if Object Props Have Changed, + to See if Image Updates Are Needed in the Image Based Method""" changedata = '' obs = [] if o.geometry_source == 'OBJECT': @@ -628,8 +628,8 @@ def checkMemoryLimit(o): if res > limit: ratio = (res / limit) o.optimisation.pixsize = o.optimisation.pixsize * sqrt(ratio) - o.info.warnings += f"Memory limit: sampling resolution reduced to {o.optimisation.pixsize:.2e}\n" - print('changing sampling resolution to %f' % o.optimisation.pixsize) + o.info.warnings += f"Memory limit: Sampling Resolution Reduced to {o.optimisation.pixsize:.2e}\n" + print('Changing Sampling Resolution to %f' % o.optimisation.pixsize) # this is the main function. diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 074f1f5d0..c007b4c27 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -160,7 +160,7 @@ def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, heigh async def offsetArea(o, samples): - """ offsets the whole image with the cutter + skin offsets """ + """ Offsets the Whole Image with the Cutter + Skin Offsets """ if o.update_offsetimage_tag: minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z @@ -192,7 +192,7 @@ async def offsetArea(o, samples): await progress_async('offset depth image', int((y2 * 100) / comparearea.shape[1])) o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea - print('\nOffset image time ' + str(time.time() - t)) + print('\nOffset Image Time ' + str(time.time() - t)) o.update_offsetimage_tag = False return o.offset_image @@ -205,9 +205,9 @@ def dilateAr(ar, cycles): def getOffsetImageCavities(o, i): # for pencil operation mainly - """detects areas in the offset image which are 'cavities' - the curvature changes.""" + """Detects Areas in the Offset Image Which Are 'cavities' - the Curvature Changes.""" # i=numpy.logical_xor(lastislice , islice) - progress('detect corners in the offset image') + progress('Detect Corners in the Offset Image') vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] # if bpy.app.debug_value==2: @@ -267,7 +267,7 @@ def imageEdgeSearch_online(o, ar, zimage): if perc != int(100 - 100 * totpix / startpix): perc = int(100 - 100 * totpix / startpix) - progress('pencil path searching', perc) + progress('Pencil Path Searching', perc) # progress('simulation ',int(100*i/l)) success = False testangulardistance = 0 # distance from initial direction in the list of direction @@ -286,7 +286,7 @@ def imageEdgeSearch_online(o, ar, zimage): last_direction = test_direction ar[xs, ys] = False if 0: - print('success') + print('Success') print(xs, ys, testlength, testangle) print(lastvect) print(testvect) @@ -1100,7 +1100,7 @@ def _restore_render_settings(pairs, properties): def renderSampleImage(o): t = time.time() - progress('getting zbuffer') + progress('Getting Z-Buffer') # print(o.zbuffer_image) o.update_offsetimage_tag = True if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION': @@ -1221,7 +1221,7 @@ def renderSampleImage(o): if backup_settings is not None: _restore_render_settings(SETTINGS_TO_BACKUP, backup_settings) else: - print("Failed to backup scene settings") + print("Failed to Backup Scene Settings") i = bpy.data.images.load(iname) bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' @@ -1248,7 +1248,7 @@ def renderSampleImage(o): #o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth) o.optimisation.pixsize = o.source_image_size_x / i.size[0] - progress('pixel size in the image source', o.optimisation.pixsize) + progress('Pixel Size in the Image Source', o.optimisation.pixsize) rawimage = imagetonumpy(i) maxa = numpy.max(rawimage) @@ -1304,7 +1304,7 @@ async def prepareArea(o): iname = getCachePath(o) + '_off.exr' if not o.update_offsetimage_tag: - progress('loading offset image') + progress('Loading Offset Image') try: o.offset_image = imagetonumpy(bpy.data.images.load(iname)) @@ -1390,7 +1390,7 @@ def getCutterArray(operation, pixsize): # print(cutob.scale) vstart = Vector((0, 0, -10)) vend = Vector((0, 0, 10)) - print('sampling custom cutter') + print('Sampling Custom Cutter') maxz = -1 for a in range(0, res): vstart.x = (a + 0.5 - m) * ps * scale diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 2077461aa..9857e99c8 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -311,7 +311,7 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, old_mortise_angle = 0 distance = finger_size / 2 j = 0 - print("joinery loop length", round(loop_length * 1000), "mm") + print("Joinery Loop Length", round(loop_length * 1000), "mm") for i, p in enumerate(coords): if i == 0: p_start = p @@ -527,8 +527,8 @@ def distributed_interlock(loop, loop_length, finger_depth, finger_thick, finger_ end_distance = loop_length j = 0 - print("joinery loop length", round(loop_length * 1000), "mm") - print("distance between joints", round(spacing * 1000), "mm") + print("Joinery Loop Length", round(loop_length * 1000), "mm") + print("Distance Between Joints", round(spacing * 1000), "mm") for i, p in enumerate(coords): if i == 0: diff --git a/scripts/addons/cam/machine_settings.py b/scripts/addons/cam/machine_settings.py index b466dfc20..58178bace 100644 --- a/scripts/addons/cam/machine_settings.py +++ b/scripts/addons/cam/machine_settings.py @@ -15,17 +15,17 @@ class machineSettings(PropertyGroup): """stores all data for machines""" # name = StringProperty(name="Machine Name", default="Machine") post_processor: EnumProperty( - name='Post processor', + name='Post Processor', items=( - ('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), - ('MACH3', 'Mach3', 'default mach3'), + ('ISO', 'Iso', 'Exports standardized gcode ISO 6983 (RS-274)'), + ('MACH3', 'Mach3', 'Default mach3'), ('EMC', 'LinuxCNC - EMC2', 'Linux based CNC control software - formally EMC2'), ('FADAL', 'Fadal', 'Fadal VMC'), ('GRBL', 'grbl', - 'optimized gcode for grbl firmware on Arduino with cnc shield'), - ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), - ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), + 'Optimized gcode for grbl firmware on Arduino with cnc shield'), + ('HEIDENHAIN', 'Heidenhain', 'Heidenhain'), + ('HEIDENHAIN530', 'Heidenhain530', 'Heidenhain530'), ('TNC151', 'Heidenhain TNC151', 'Post Processor for the Heidenhain TNC151 machine'), ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), @@ -37,19 +37,19 @@ class machineSettings(PropertyGroup): ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o') ), - description='Post processor', + description='Post Processor', default='MACH3', ) # units = EnumProperty(name='Units', items = (('IMPERIAL', '')) # position definitions: use_position_definitions: BoolProperty( - name="Use position definitions", + name="Use Position Definitions", description="Define own positions for op start, " "toolchange, ending position", default=False, ) starting_position: FloatVectorProperty( - name='Start position', + name='Start Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -57,7 +57,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) mtc_position: FloatVectorProperty( - name='MTC position', + name='MTC Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -65,7 +65,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) ending_position: FloatVectorProperty( - name='End position', + name='End Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -82,7 +82,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) feedrate_min: FloatProperty( - name="Feedrate minimum /min", + name="Feedrate Minimum /min", default=0.0, min=0.00001, max=320000, @@ -90,7 +90,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) feedrate_max: FloatProperty( - name="Feedrate maximum /min", + name="Feedrate Maximum /min", default=2, min=0.00001, max=320000, @@ -98,7 +98,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) feedrate_default: FloatProperty( - name="Feedrate default /min", + name="Feedrate Default /min", default=1.5, min=0.00001, max=320000, @@ -106,7 +106,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) hourly_rate: FloatProperty( - name="Price per hour", + name="Price per Hour", default=100, min=0.005, precision=2, @@ -115,28 +115,28 @@ class machineSettings(PropertyGroup): # UNSUPPORTED: spindle_min: FloatProperty( - name="Spindle speed minimum RPM", + name="Spindle Speed Minimum RPM", default=5000, min=0.00001, max=320000, precision=1, ) spindle_max: FloatProperty( - name="Spindle speed maximum RPM", + name="Spindle Speed Maximum RPM", default=30000, min=0.00001, max=320000, precision=1, ) spindle_default: FloatProperty( - name="Spindle speed default RPM", + name="Spindle Speed Default RPM", default=15000, min=0.00001, max=320000, precision=1, ) spindle_start_time: FloatProperty( - name="Spindle start delay seconds", + name="Spindle Start Delay Seconds", description='Wait for the spindle to start spinning before starting ' 'the feeds , in seconds', default=0, @@ -146,23 +146,23 @@ class machineSettings(PropertyGroup): ) axis4: BoolProperty( - name="#4th axis", + name="#4th Axis", description="Machine has 4th axis", default=0, ) axis5: BoolProperty( - name="#5th axis", + name="#5th Axis", description="Machine has 5th axis", default=0, ) eval_splitting: BoolProperty( - name="Split files", - description="split gcode file with large number of operations", + name="Split Files", + description="Split gcode file with large number of operations", default=True, ) # split large files split_limit: IntProperty( - name="Operations per file", + name="Operations per File", description="Split files with larger number of operations than this", min=1000, max=20000000, @@ -178,7 +178,7 @@ class machineSettings(PropertyGroup): # default='X', update = updateOffsetImage) collet_size: FloatProperty( - name="#Collet size", + name="#Collet Size", description="Collet size for collision detection", default=33, min=0.00001, @@ -191,38 +191,38 @@ class machineSettings(PropertyGroup): # post processor options output_block_numbers: BoolProperty( - name="output block numbers", - description="output block numbers ie N10 at start of line", + name="Output Block Numbers", + description="Output block numbers ie N10 at start of line", default=False, ) start_block_number: IntProperty( - name="start block number", - description="the starting block number ie 10", + name="Start Block Number", + description="The starting block number ie 10", default=10, ) block_number_increment: IntProperty( - name="block number increment", - description="how much the block number should " + name="Block Number Increment", + description="How much the block number should " "increment for the next line", default=10, ) output_tool_definitions: BoolProperty( - name="output tool definitions", - description="output tool definitions", + name="Output Tool Definitions", + description="Output tool definitions", default=True, ) output_tool_change: BoolProperty( - name="output tool change commands", - description="output tool change commands ie: Tn M06", + name="Output Tool Change Commands", + description="Output tool change commands ie: Tn M06", default=True, ) output_g43_on_tool_change: BoolProperty( - name="output G43 on tool change", - description="output G43 on tool change line", + name="Output G43 on Tool Change", + description="Output G43 on tool change line", default=False, ) diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index f63740ac5..f96132a99 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -68,7 +68,7 @@ def __init__(self, o, proc): def threadread(tcom): - """reads stdout of background process, done this way to have it non-blocking""" + """Reads Stdout of Background Process, Done This Way to Have It Non-blocking""" inline = tcom.proc.stdout.readline() inline = str(inline) s = inline.find('progress{') @@ -79,7 +79,7 @@ def threadread(tcom): @bpy.app.handlers.persistent def timer_update(context): - """monitoring of background processes""" + """Monitoring of Background Processes""" text = '' s = bpy.context.scene if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'): @@ -114,9 +114,9 @@ def timer_update(context): class PathsBackground(Operator): - """calculate CAM paths in background. File has to be saved before.""" + """Calculate CAM Paths in Background. File Has to Be Saved Before.""" bl_idname = "object.calculate_cam_paths_background" - bl_label = "Calculate CAM paths in background" + bl_label = "Calculate CAM Paths in Background" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -149,9 +149,9 @@ def execute(self, context): class KillPathsBackground(Operator): - """Remove CAM path processes in background.""" + """Remove CAM Path Processes in Background.""" bl_idname = "object.kill_calculate_cam_paths_background" - bl_label = "Kill background computation of an operation" + bl_label = "Kill Background Computation of an Operation" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -201,8 +201,8 @@ async def _calc_path(operator, context): # check for free movement height < maxz and return with error if(o.movement.free_height < o.maxz): operator.report({'ERROR_INVALID_INPUT'}, - "Free movement height is less than Operation depth start \n correct and try again.") - progress_async("Operation can't be performed, see warnings for info") + "Free Movement Height Is Less than Operation Depth Start \n Correct and Try Again.") + progress_async("Operation Can't Be Performed, See Warnings for Info") return {'FINISHED', False} if o.computing: @@ -214,7 +214,7 @@ async def _calc_path(operator, context): o.movement.parallel_step_back = False try: await gcodepath.getPath(context, o) - print("Got path okay") + print("Got Path Okay") except CamException as e: traceback.print_tb(e.__traceback__) error_str = "\n".join(textwrap.wrap(str(e), width=80)) @@ -235,9 +235,9 @@ async def _calc_path(operator, context): class CalculatePath(Operator, AsyncOperatorMixin): - """calculate CAM paths""" + """Calculate CAM Paths""" bl_idname = "object.calculate_cam_path" - bl_label = "Calculate CAM paths" + bl_label = "Calculate CAM Paths" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -256,16 +256,16 @@ async def execute_async(self, context): class PathsAll(Operator): - """calculate all CAM paths""" + """Calculate All CAM Paths""" bl_idname = "object.calculate_cam_paths_all" - bl_label = "Calculate all CAM paths" + bl_label = "Calculate All CAM Paths" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): i = 0 for o in bpy.context.scene.cam_operations: bpy.context.scene.cam_active_operation = i - print('\nCalculating path :' + o.name) + print('\nCalculating Path :' + o.name) print('\n') bpy.ops.object.calculate_cam_paths_background() i += 1 @@ -279,9 +279,9 @@ def draw(self, context): class CamPackObjects(Operator): - """calculate all CAM paths""" + """Calculate All CAM Paths""" bl_idname = "object.cam_pack_objects" - bl_label = "Pack curves on sheet" + bl_label = "Pack Curves on Sheet" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -296,10 +296,10 @@ def draw(self, context): class CamSliceObjects(Operator): - """Slice a mesh object horizontally""" + """Slice a Mesh Object Horizontally""" # warning, this is a separate and neglected feature, it's a mess - by now it just slices up the object. bl_idname = "object.cam_slice_objects" - bl_label = "Slice object - usefull for lasercut puzzles e.t.c." + bl_label = "Slice Object - Useful for Lasercut Puzzles etc" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -313,7 +313,7 @@ def draw(self, context): def getChainOperations(chain): - """return chain operations, currently chain object can't store operations directly due to blender limitations""" + """Return Chain Operations, Currently Chain Object Can't Store Operations Directly Due to Blender Limitations""" chop = [] for cho in chain.operations: for so in bpy.context.scene.cam_operations: @@ -323,9 +323,9 @@ def getChainOperations(chain): class PathsChain(Operator, AsyncOperatorMixin): - """calculate a chain and export the gcode alltogether. """ + """Calculate a Chain and Export the G-code Alltogether. """ bl_idname = "object.calculate_cam_paths_chain" - bl_label = "Calculate CAM paths in current chain and export chain gcode" + bl_label = "Calculate CAM Paths in Current Chain and Export Chain G-code" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -344,11 +344,11 @@ async def execute_async(self, context): for i in range(0, len(chainops)): s.cam_active_operation = s.cam_operations.find( chainops[i].name) - self.report({'INFO'}, f"Calculating path: {chainops[i].name}") + self.report({'INFO'}, f"Calculating Path: {chainops[i].name}") result, success = await _calc_path(self, context) if not success and 'FINISHED' in result: self.report( - {'ERROR'}, f"Couldn't calculate path: {chainops[i].name}") + {'ERROR'}, f"Couldn't Calculate Path: {chainops[i].name}") except Exception as e: print("FAIL", e) traceback.print_tb(e.__traceback__) @@ -362,9 +362,9 @@ async def execute_async(self, context): class PathExportChain(Operator): - """calculate a chain and export the gcode alltogether. """ + """Calculate a Chain and Export the G-code Together.""" bl_idname = "object.cam_export_paths_chain" - bl_label = "Export CAM paths in current chain as gcode" + bl_label = "Export CAM Paths in Current Chain as G-code" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -390,9 +390,9 @@ def execute(self, context): class PathExport(Operator): - """Export gcode. Can be used only when the path object is present""" + """Export G-code. Can Be Used only when the Path Object Is Present""" bl_idname = "object.cam_export" - bl_label = "Export operation gcode" + bl_label = "Export Operation G-code" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -409,11 +409,11 @@ def execute(self, context): class CAMSimulate(Operator, AsyncOperatorMixin): - """simulate CAM operation - this is performed by: creating an image, painting Z depth of the brush substractively. - Works only for some operations, can not be used for 4-5 axis.""" + """Simulate CAM Operation + This Is Performed by: Creating an Image, Painting Z Depth of the Brush Subtractively. + Works only for Some Operations, Can Not Be Used for 4-5 Axis.""" bl_idname = "object.cam_simulate" - bl_label = "CAM simulation" + bl_label = "CAM Simulation" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} operation: StringProperty( @@ -434,7 +434,7 @@ async def execute_async(self, context): except AsyncCancelledException as e: return {'CANCELLED'} else: - self.report({'ERROR'}, 'no computed path to simulate') + self.report({'ERROR'}, 'No Computed Path to Simulate') return {'FINISHED'} return {'FINISHED'} @@ -445,10 +445,10 @@ def draw(self, context): class CAMSimulateChain(Operator, AsyncOperatorMixin): - """simulate CAM chain, compared to single op simulation just writes into one image and thus enables - to see how ops work together.""" + """Simulate CAM Chain, Compared to Single Op Simulation Just Writes Into One Image and Thus Enables + to See how Ops Work Together.""" bl_idname = "object.cam_simulate_chain" - bl_label = "CAM simulation" + bl_label = "CAM Simulation" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -490,9 +490,9 @@ def draw(self, context): class CamChainAdd(Operator): - """Add new CAM chain""" + """Add New CAM Chain""" bl_idname = "scene.cam_chain_add" - bl_label = "Add new CAM chain" + bl_label = "Add New CAM Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -513,9 +513,9 @@ def execute(self, context): class CamChainRemove(Operator): - """Remove CAM chain""" + """Remove CAM Chain""" bl_idname = "scene.cam_chain_remove" - bl_label = "Remove CAM chain" + bl_label = "Remove CAM Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -531,9 +531,9 @@ def execute(self, context): class CamChainOperationAdd(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_add" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -551,9 +551,9 @@ def execute(self, context): class CamChainOperationUp(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_up" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -571,9 +571,9 @@ def execute(self, context): class CamChainOperationDown(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_down" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -591,9 +591,9 @@ def execute(self, context): class CamChainOperationRemove(Operator): - """Remove operation from chain""" + """Remove Operation from Chain""" bl_idname = "scene.cam_chain_operation_remove" - bl_label = "Remove operation from chain" + bl_label = "Remove Operation from Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -611,7 +611,7 @@ def execute(self, context): def fixUnits(): - """Sets up units for blender CAM""" + """Sets up Units for BlenderCAM""" s = bpy.context.scene s.unit_settings.system_rotation = 'DEGREES' @@ -621,9 +621,9 @@ def fixUnits(): class CamOperationAdd(Operator): - """Add new CAM operation""" + """Add New CAM Operation""" bl_idname = "scene.cam_operation_add" - bl_label = "Add new CAM operation" + bl_label = "Add New CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -637,7 +637,7 @@ def execute(self, context): ob = bpy.context.active_object if ob is None: self.report({'ERROR_INVALID_INPUT'}, - "Please add an object to base the operation on.") + "Please Add an Object to Base the Operation on.") return {'CANCELLED'} minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace([ob]) @@ -658,9 +658,9 @@ def execute(self, context): class CamOperationCopy(Operator): - """Copy CAM operation""" + """Copy CAM Operation""" bl_idname = "scene.cam_operation_copy" - bl_label = "Copy active CAM operation" + bl_label = "Copy Active CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -709,9 +709,9 @@ def execute(self, context): class CamOperationRemove(Operator): - """Remove CAM operation""" + """Remove CAM Operation""" bl_idname = "scene.cam_operation_remove" - bl_label = "Remove CAM operation" + bl_label = "Remove CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -744,18 +744,18 @@ def execute(self, context): # move cam operation in the list up or down class CamOperationMove(Operator): - """Move CAM operation""" + """Move CAM Operation""" bl_idname = "scene.cam_operation_move" - bl_label = "Move CAM operation in list" + bl_label = "Move CAM Operation in List" bl_options = {'REGISTER', 'UNDO'} direction: EnumProperty( - name='direction', + name='Direction', items=( ('UP', 'Up', ''), ('DOWN', 'Down', '') ), - description='direction', + description='Direction', default='DOWN', ) @@ -781,9 +781,9 @@ def execute(self, context): class CamOrientationAdd(Operator): - """Add orientation to cam operation, for multiaxis operations""" + """Add Orientation to CAM Operation, for Multiaxis Operations""" bl_idname = "scene.cam_orientation_add" - bl_label = "Add orientation" + bl_label = "Add Orientation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -808,9 +808,9 @@ def execute(self, context): class CamBridgesAdd(Operator): - """Add bridge objects to curve""" + """Add Bridge Objects to Curve""" bl_idname = "scene.cam_bridges_add" - bl_label = "Add bridges" + bl_label = "Add Bridges / Tabs" bl_options = {'REGISTER', 'UNDO'} @classmethod diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index 3e99dbe3c..28114681e 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -218,7 +218,7 @@ class PackObjectsSettings(PropertyGroup): """stores all data for machines""" sheet_fill_direction: EnumProperty( - name="Fill direction", + name="Fill Direction", items=( ("X", "X", "Fills sheet in X axis direction"), ("Y", "Y", "Fills sheet in Y axis direction"), @@ -227,7 +227,7 @@ class PackObjectsSettings(PropertyGroup): default="Y", ) sheet_x: FloatProperty( - name="X size", + name="X Size", description="Sheet size", min=0.001, max=10, @@ -236,7 +236,7 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) sheet_y: FloatProperty( - name="Y size", + name="Y Size", description="Sheet size", min=0.001, max=10, @@ -245,8 +245,8 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) distance: FloatProperty( - name="Minimum distance", - description="minimum distance between objects(should be " + name="Minimum Distance", + description="Minimum distance between objects(should be " "at least cutter diameter!)", min=0.001, max=10, @@ -264,13 +264,13 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) rotate: BoolProperty( - name="enable rotation", + name="Enable Rotation", description="Enable rotation of elements", default=True, ) rotate_angle: FloatProperty( - name="Placement Angle rotation step", - description="bigger rotation angle,faster placemant", + name="Placement Angle Rotation Step", + description="Bigger rotation angle, faster placemant", default=0.19635 * 4, min=pi / 180, max=pi, diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index 3e84a0049..b3f5e37e8 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -150,7 +150,7 @@ def getPathPatternParallel(o, angle): def getPathPattern(operation): o = operation t = time.time() - progress('building path pattern') + progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] @@ -408,7 +408,7 @@ def getPathPattern(operation): def getPathPattern4axis(operation): o = operation t = time.time() - progress('building path pattern') + progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] zlevel = 1 # minz#this should do layers... diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index acd31bc46..06ebc53ac 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -73,7 +73,7 @@ def shapelyToMultipolygon(anydata): else: return sgeometry.MultiPolygon() else: - print(anydata.type, 'shapely conversion aborted') + print(anydata.type, 'Shapely Conversion Aborted') return sgeometry.MultiPolygon() diff --git a/scripts/addons/cam/preferences.py b/scripts/addons/cam/preferences.py index 71d83514f..b2a67c797 100644 --- a/scripts/addons/cam/preferences.py +++ b/scripts/addons/cam/preferences.py @@ -15,17 +15,17 @@ class CamAddonPreferences(AddonPreferences): bl_idname = __package__ op_preset_update: BoolProperty( - name="Have the Operation Presets been Updated", + name="Have the Operation Presets Been Updated", default=False, ) experimental: BoolProperty( - name="Show experimental features", + name="Show Experimental Features", default=False, ) update_source: StringProperty( - name="Source of updates for the addon", + name="Source of Updates for the Addon", description="This can be either a github repo link in which case " "it will download the latest release on there, " "or an api link like " @@ -35,39 +35,39 @@ class CamAddonPreferences(AddonPreferences): ) last_update_check: IntProperty( - name="Last update time", + name="Last Update Time", default=0, ) last_commit_hash: StringProperty( - name="Hash of last commit from updater", + name="Hash of Last Commit from Updater", default="", ) just_updated: BoolProperty( - name="Set to true on update or initial install", + name="Set to True on Update or Initial Install", default=True, ) new_version_available: StringProperty( - name="Set to new version name if one is found", + name="Set to New Version Name if One Is Found", default="", ) default_interface_level: EnumProperty( - name="Interface level in new file", + name="Interface Level in New File", description="Choose visible options", items=[ - ("0", "Basic", "Only show essential options"), - ("1", "Advanced", "Show advanced options"), - ("2", "Complete", "Show all options"), - ("3", "Experimental", "Show experimental options"), + ("0", "Basic", "Only show Essential Options"), + ("1", "Advanced", "Show Advanced Options"), + ("2", "Complete", "Show All Options"), + ("3", "Experimental", "Show Experimental Options"), ], default="3", ) default_machine_preset: StringProperty( - name="Machine preset in new file", + name="Machine Preset in New File", description="So that machine preset choice persists between files", default="", ) @@ -75,11 +75,11 @@ class CamAddonPreferences(AddonPreferences): def draw(self, context): layout = self.layout layout.label( - text="Use experimental features when you want to help development of Blender CAM:" + text="Use Experimental Features when you want to help development of BlenderCAM:" ) layout.prop(self, "experimental") layout.prop(self, "update_source") - layout.label(text="Choose a preset update source") + layout.label(text="Choose a Preset Update Source") UPDATE_SOURCES = [ ( diff --git a/scripts/addons/cam/preset_managers.py b/scripts/addons/cam/preset_managers.py index 16c4986a3..751925ddf 100644 --- a/scripts/addons/cam/preset_managers.py +++ b/scripts/addons/cam/preset_managers.py @@ -7,14 +7,14 @@ class CAM_CUTTER_MT_presets(Menu): - bl_label = "Cutter presets" + bl_label = "Cutter Presets" preset_subdir = "cam_cutters" preset_operator = "script.execute_preset" draw = Menu.draw_preset class CAM_MACHINE_MT_presets(Menu): - bl_label = "Machine presets" + bl_label = "Machine Presets" preset_subdir = "cam_machines" preset_operator = "script.execute_preset" draw = Menu.draw_preset @@ -54,7 +54,7 @@ class AddPresetCamCutter(AddPresetBase, Operator): class CAM_OPERATION_MT_presets(Menu): - bl_label = "Operation presets" + bl_label = "Operation Presets" preset_subdir = "cam_operations" preset_operator = "script.execute_preset" draw = Menu.draw_preset diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index 8d52cfe52..b75460839 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -71,7 +71,7 @@ def timingprint(tinf): def progress(text, n=None): - """function for reporting during the script, works for background operations in the header.""" + """Function for Reporting During the Script, Works for Background Operations in the Header.""" text = str(text) if n is None: n = '' @@ -82,7 +82,7 @@ def progress(text, n=None): def activate(o): - """makes an object active, used many times in blender""" + """Makes an Object Active, Used Many Times in Blender""" s = bpy.context.scene bpy.ops.object.select_all(action='DESELECT') o.select_set(state=True) @@ -91,18 +91,18 @@ def activate(o): def dist2d(v1, v2): - """distance between two points in 2d""" + """Distance Between Two Points in 2D""" return hypot((v1[0] - v2[0]), (v1[1] - v2[1])) def delob(ob): - """object deletion for multiple uses""" + """Object Deletion for Multiple Uses""" activate(ob) bpy.ops.object.delete(use_global=False) def dupliob(o, pos): - """helper function for visualising cutter positions in bullet simulation""" + """Helper Function for Visualising Cutter Positions in Bullet Simulation""" activate(o) bpy.ops.object.duplicate() s = 1.0 / BULLET_SCALE @@ -123,7 +123,7 @@ def addToGroup(ob, groupname): def compare(v1, v2, vmiddle, e): - """comparison for optimisation of paths""" + """Comparison for Optimisation of Paths""" # e=0.0001 v1 = Vector(v1) v2 = Vector(v2) @@ -139,7 +139,7 @@ def compare(v1, v2, vmiddle, e): def isVerticalLimit(v1, v2, limit): - """test path segment on verticality threshold, for protect_vertical option""" + """Test Path Segment on Verticality Threshold, for protect_vertical Option""" z = abs(v1[2] - v2[2]) # verticality=0.05 # this will be better. diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 398c13498..dbdba25d1 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -98,7 +98,7 @@ def createSimulationObject(name, operations, i): async def doSimulation(name, operations): - """perform simulation of operations. Currently only for 3 axis""" + """Perform Simulation of Operations. Currently only for 3 Axis""" for o in operations: getOperationSources(o) limits = getBoundsMultiple( @@ -296,8 +296,8 @@ async def generateSimulationImage(operations, limits): def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False): - """simulates a cutter cutting into stock, taking away the volume, - and optionally returning the volume that has been milled. This is now used for feedrate tweaking.""" + """Simulates a Cutter Cutting Into Stock, Taking Away the Volume, + and Optionally Returning the Volume that Has Been Milled. This Is Now Used for Feedrate Tweaking.""" m = int(cutterArray.shape[0] / 2) size = cutterArray.shape[0] # whole cutter in image there diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index 8dcbeaaf8..1cc9493fc 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -145,11 +145,11 @@ def sliceObject(ob): # April 2020 Alain Pelletier class SliceObjectsSettings(PropertyGroup): - """stores all data for machines""" + """Stores All Data for Machines""" slice_distance: FloatProperty( - name="Slicing distance", - description="slices distance in z, should be most often " + name="Slicing Distance", + description="Slices distance in z, should be most often " "thickness of plywood sheet.", min=0.001, max=10, @@ -158,17 +158,17 @@ class SliceObjectsSettings(PropertyGroup): unit="LENGTH", ) slice_above0: BoolProperty( - name="Slice above 0", + name="Slice Above 0", description="only slice model above 0", default=False, ) slice_3d: BoolProperty( - name="3d slice", - description="for 3d carving", + name="3D Slice", + description="For 3D carving", default=False, ) indexes: BoolProperty( - name="add indexes", - description="adds index text of layer + index", + name="Add Indexes", + description="Adds index text of layer + index", default=True, ) diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 98ebc0781..ccadaffcb 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -110,7 +110,7 @@ async def cutout(o): join = 2 else: join = 1 - print('operation: cutout') + print('Operation: Cutout') offset = True if o.cut_type == 'ONLINE' and o.onlycurves: # is separate to allow open curves :) print('separate') @@ -196,9 +196,9 @@ async def cutout(o): chunks = [] if o.use_bridges: # add bridges to chunks - print('using bridges') + print('Using Bridges') remove_multiple(o.name+'_cut_bridges') - print("old briddge cut removed") + print("Old Briddge Cut Removed") bridgeheight = min(o.max.z, o.min.z + abs(o.bridges_height)) @@ -209,7 +209,7 @@ async def cutout(o): useBridges(chunk, o) if o.profile_start > 0: - print("cutout change profile start") + print("Cutout Change Profile Start") for chl in extendorder: chunk = chl[0] if chunk.closed: @@ -217,7 +217,7 @@ async def cutout(o): # Lead in if o.lead_in > 0.0 or o.lead_out > 0: - print("cutout leadin") + print("Cutout Lead-in") for chl in extendorder: chunk = chl[0] if chunk.closed: @@ -242,11 +242,11 @@ async def cutout(o): async def curve(o): - print('operation: curve') + print('Operation: Curve') pathSamples = [] getOperationSources(o) if not o.onlycurves: - raise CamException("All objects must be curves for this operation.") + raise CamException("All Objects Must Be Curves for This Operation.") for ob in o.objects: # make the chunks from curve here @@ -289,7 +289,7 @@ async def curve(o): async def proj_curve(s, o): - print('operation: projected curve') + print('Operation: Projected Curve') pathSamples = [] chunks = [] ob = bpy.data.objects[o.curve_object] @@ -299,7 +299,7 @@ async def proj_curve(s, o): from cam import cam_chunk if targetCurve.type != 'CURVE': - raise CamException('Projection target and source have to be curve objects!') + raise CamException('Projection Target and Source Have to Be Curve Objects!') if 1: extend_up = 0.1 @@ -340,7 +340,7 @@ async def proj_curve(s, o): async def pocket(o): - print('operation: pocket') + print('Operation: Pocket') scene = bpy.context.scene remove_multiple("3D_poc") @@ -358,11 +358,11 @@ async def pocket(o): c_offset = o.cutter_diameter / 2 c_offset += o.skin # add skin - print("cutter offset", c_offset) + print("Cutter Offset", c_offset) p = getObjectOutline(c_offset, o, False) approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 - print("approximative:" + str(approxn)) + print("Approximative:" + str(approxn)) print(o) i = 0 @@ -394,7 +394,7 @@ async def pocket(o): lastchunks = nchunks percent = int(i / approxn * 100) - progress('outlining polygons ', percent) + progress('Outlining Polygons ', percent) p = pnew i += 1 @@ -537,7 +537,7 @@ async def pocket(o): async def drill(o): - print('operation: Drill') + print('Operation: Drill') chunks = [] for ob in o.objects: activate(ob) @@ -631,7 +631,7 @@ async def drill(o): async def medial_axis(o): - print('operation: Medial Axis') + print('Operation: Medial Axis') remove_multiple("medialMesh") @@ -660,7 +660,7 @@ async def medial_axis(o): elif o.cutter_type == 'BALLNOSE': maxdepth = - new_cutter_diameter / 2 - o.skin else: - raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.") + raise CamException("Only Ballnose and V-carve Cutters Are Supported for Medial Axis.") # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] @@ -681,7 +681,7 @@ async def medial_axis(o): # just a multipolygon mpoly = polys else: - raise CamException("Failed getting object silhouette. Is input curve closed?") + raise CamException("Failed Getting Object Silhouette. Is Input Curve Closed?") mpoly_boundary = mpoly.boundary ipol = 0 @@ -700,19 +700,19 @@ async def medial_axis(o): # verts= points#[[vert.x, vert.y, vert.z] for vert in vertsPts] nDupli, nZcolinear = unique(verts) nVerts = len(verts) - print(str(nDupli) + " duplicates points ignored") - print(str(nZcolinear) + " z colinear points excluded") + print(str(nDupli) + " Duplicates Points Ignored") + print(str(nZcolinear) + " Z Colinear Points Excluded") if nVerts < 3: - print("Not enough points") + print("Not Enough Points") return {'FINISHED'} # Check colinear xValues = [pt[0] for pt in verts] yValues = [pt[1] for pt in verts] if checkEqual(xValues) or checkEqual(yValues): - print("Points are colinear") + print("Points Are Colinear") return {'FINISHED'} # Create diagram - print("Tesselation... (" + str(nVerts) + " points)") + print("Tesselation... (" + str(nVerts) + " Points)") xbuff, ybuff = 5, 5 # % zPosition = 0 vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] @@ -725,14 +725,14 @@ async def medial_axis(o): newIdx = 0 vertr = [] filteredPts = [] - print('filter points') + print('Filter Points') ipts = 0 for p in pts: ipts = ipts + 1 if ipts % 500 == 0: sys.stdout.write('\r') # the exact output you're looking for: - prog_message = "points: " + str(ipts) + " / " + str(len(pts)) + " " + str( + prog_message = "Points: " + str(ipts) + " / " + str(len(pts)) + " " + str( round(100 * ipts / len(pts))) + "%" sys.stdout.write(prog_message) sys.stdout.flush() @@ -761,7 +761,7 @@ async def medial_axis(o): filteredPts.append((p[0], p[1], z)) newIdx += 1 - print('filter edges') + print('Filter Edges') filteredEdgs = [] ledges = [] for e in edgesIdx: @@ -829,17 +829,17 @@ async def medial_axis(o): def getLayers(operation, startdepth, enddepth): - """returns a list of layers bounded by startdepth and enddepth - uses operation.stepdown to determine number of layers. + """Returns a List of Layers Bounded by Startdepth and Enddepth + Uses Operation.stepdown to Determine Number of Layers. """ if startdepth < enddepth: - raise CamException("Start depth is lower than end depth. " - "If you have set a custom depth end, it must be lower than depth start, " - "and should usually be negative. Set this in the CAM Operation Area panel.") + raise CamException("Start Depth Is Lower than End Depth. " + "if You Have Set a Custom Depth End, It Must Be Lower than Depth Start, " + "and Should Usually Be Negative. Set This in the CAM Operation Area Panel.") if operation.use_layers: layers = [] n = ceil((startdepth - enddepth) / operation.stepdown) - print("start " + str(startdepth) + " end " + str(enddepth) + " n " + str(n)) + print("Start " + str(startdepth) + " End " + str(enddepth) + " n " + str(n)) layerstart = operation.maxz for x in range(0, n): @@ -856,7 +856,7 @@ def getLayers(operation, startdepth, enddepth): def chunksToMesh(chunks, o): - """convert sampled chunks to path, optimization of paths""" + """Convert Sampled Chunks to Path, Optimization of Paths""" t = time.time() s = bpy.context.scene m = s.cam_machine @@ -888,7 +888,7 @@ def chunksToMesh(chunks, o): nchunks.append(ch) chunks = nchunks - progress('building paths from chunks') + progress('Building Paths from Chunks') e = 0.0001 lifted = True diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 4decdd0c7..b5ebcfd37 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -141,7 +141,7 @@ def testOperation(i): newresult = bpy.data.objects[o.path_object_name] origname = "test_cam_path_" + o.name if origname not in s.objects: - report += 'operation test has nothing to compare with, making the new result as comparable result.\n\n' + report += 'Operation Test Has Nothing to Compare with, Making the New Result as Comparable Result.\n\n' newresult.name = origname else: testresult = bpy.data.objects[origname] @@ -149,7 +149,7 @@ def testOperation(i): m2 = newresult.data test_ok = True if len(m1.vertices) != len(m2.vertices): - report += "vertex counts don't match\n\n" + report += "Vertex Counts Don't Match\n\n" test_ok = False else: different_co_count = 0 @@ -159,12 +159,12 @@ def testOperation(i): if v1.co != v2.co: different_co_count += 1 if different_co_count > 0: - report += 'vertex position is different on %i vertices \n\n' % (different_co_count) + report += 'Vertex Position Is Different on %i Vertices \n\n' % (different_co_count) test_ok = False if test_ok: - report += 'test ok\n\n' + report += 'Test Ok\n\n' else: - report += 'test result is different\n \n ' + report += 'Test Result Is Different\n \n ' print(report) return report diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index fe56733ae..8f8cc786a 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -98,7 +98,7 @@ class CustomPanel(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" - bl_label = "Import Gcode" + bl_label = "Import G-code" bl_idname = "OBJECT_PT_importgcode" bl_options = {'DEFAULT_CLOSED'} @@ -119,7 +119,7 @@ def draw(self, context): col = layout.column(align=True) col = col.row(align=True) col.split() - col.label(text="Segment length") + col.label(text="Segment Length") col.prop(isettings, "max_segment_size") col.enabled = isettings.subdivide @@ -131,9 +131,9 @@ def draw(self, context): class WM_OT_gcode_import(Operator, ImportHelper): - """Import Gcode, travel lines don't get drawn""" + """Import G-code, Travel Lines Don't Get Drawn""" bl_idname = "wm.gcode_import" # important since its how bpy.ops.import_test.some_data is constructed - bl_label = "Import Gcode" + bl_label = "Import G-code" # ImportHelper mixin class uses this filename_ext = ".txt" @@ -162,7 +162,7 @@ class import_settings(PropertyGroup): default=False, ) output: EnumProperty( - name="output type", + name="Output Type", items=( ("mesh", "Mesh", "Make a mesh output"), ("curve", "Curve", "Make curve output"), @@ -171,7 +171,7 @@ class import_settings(PropertyGroup): ) max_segment_size: FloatProperty( name="", - description="Only Segments bigger then this value get subdivided", + description="Only Segments bigger than this value get subdivided", default=0.001, min=0.0001, max=1.0, diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index c07a0b68f..af6cf76f2 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -6,8 +6,8 @@ class CAM_AREA_Panel(CAMButtonsPanel, Panel): - """CAM operation area panel""" - bl_label = "CAM operation area " + """CAM Operation Area Panel""" + bl_label = "CAM Operation Area" bl_idname = "WORLD_PT_CAM_OPERATION_AREA" panel_interface_level = 0 @@ -45,7 +45,7 @@ def draw_maxz(self): self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: self.layout.label(text='!ERROR! COLLISION!') - self.layout.label(text='Depth start > Free movement height') + self.layout.label(text='Depth Start > Free Movement Height') self.layout.label(text='!ERROR! COLLISION!') def draw_minz(self): @@ -53,10 +53,10 @@ def draw_minz(self): return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: if self.op.strategy == 'CURVE': - self.layout.label(text="cannot use depth from object using CURVES") + self.layout.label(text="Cannot Use Depth from Object Using Curves") row = self.layout.row(align=True) - row.label(text='Set max depth from') + row.label(text='Set Max Depth from') row.prop(self.op, 'minz_from', text='') if self.op.minz_from == 'CUSTOM': self.layout.prop(self.op, 'minz') @@ -68,16 +68,16 @@ def draw_minz(self): i = bpy.data.images[self.op.source_image_name] if i is not None: sy = int((self.op.source_image_size_x / i.size[0]) * i.size[1] * 1000000) / 1000 - self.layout.label(text='image size on y axis: ' + strInUnits(sy, 8)) + self.layout.label(text='Image Size on Y Axis: ' + strInUnits(sy, 8)) self.layout.separator() self.layout.prop(self.op, 'source_image_offset') col = self.layout.column(align=True) - col.prop(self.op, 'source_image_crop', text='Crop source image') + col.prop(self.op, 'source_image_crop', text='Crop Source Image') if self.op.source_image_crop: - col.prop(self.op, 'source_image_crop_start_x', text='start x') - col.prop(self.op, 'source_image_crop_start_y', text='start y') - col.prop(self.op, 'source_image_crop_end_x', text='end x') - col.prop(self.op, 'source_image_crop_end_y', text='end y') + col.prop(self.op, 'source_image_crop_start_x', text='Start X') + col.prop(self.op, 'source_image_crop_start_y', text='Start Y') + col.prop(self.op, 'source_image_crop_end_x', text='End X') + col.prop(self.op, 'source_image_crop_end_y', text='End Y') def draw_ambient(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 15c112d1b..4c42aa36e 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -34,8 +34,8 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn class CAM_CHAINS_Panel(CAMButtonsPanel, Panel): - """CAM chains panel""" - bl_label = "CAM chains" + """CAM Chains Panel""" + bl_label = "CAM Chains" bl_idname = "WORLD_PT_CAM_CHAINS" panel_interface_level = 1 always_show_panel = True @@ -67,16 +67,16 @@ def draw(self, context): if not chain.computing: layout.operator("object.calculate_cam_paths_chain", - text="Calculate chain paths & Export Gcode") - layout.operator("object.cam_export_paths_chain", text="Export chain gcode") - layout.operator("object.cam_simulate_chain", text="Simulate this chain") + text="Calculate Chain Paths & Export Gcode") + layout.operator("object.cam_export_paths_chain", text="Export Chain G-code") + layout.operator("object.cam_simulate_chain", text="Simulate This Chain") valid, reason = isChainValid(chain, context) if not valid: - layout.label(icon="ERROR", text=f"Can't compute chain - reason:\n") + layout.label(icon="ERROR", text=f"Can't Compute Chain - Reason:\n") layout.label(text=reason) else: - layout.label(text='chain is currently computing') + layout.label(text='Chain Is Currently Computing') layout.prop(chain, 'name') layout.prop(chain, 'filename') diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index 31973e170..1a2ddc207 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -5,7 +5,7 @@ class CAM_CUTTER_Panel(CAMButtonsPanel, Panel): - """CAM cutter panel""" + """CAM Cutter Panel""" bl_label = "CAM Cutter" bl_idname = "WORLD_PT_CAM_CUTTER" panel_interface_level = 0 @@ -95,9 +95,9 @@ def draw_custom(self): if self.op.cutter_type in ['CUSTOM']: if self.op.optimisation.use_exact: self.layout.label( - text='Warning - only convex shapes are supported. ', icon='COLOR_RED') - self.layout.label(text='If your custom cutter is concave,') - self.layout.label(text='switch exact mode off.') + text='Warning - only Convex Shapes Are Supported. ', icon='COLOR_RED') + self.layout.label(text='If Your Custom Cutter Is Concave,') + self.layout.label(text='Switch Exact Mode Off.') self.layout.prop_search(self.op, "cutter_object_name", bpy.data, "objects") def draw_cutter_diameter(self): @@ -129,7 +129,7 @@ def draw_engagement(self): else: engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) - self.layout.label(text=f"Cutter engagement: {engagement}%") + self.layout.label(text=f"Cutter Engagement: {engagement}%") if engagement > 50: self.layout.label(text="WARNING: CUTTER ENGAGEMENT > 50%") diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index e66f041d9..84d1a3877 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -5,8 +5,8 @@ class CAM_FEEDRATE_Panel(CAMButtonsPanel, Panel): - """CAM feedrate panel""" - bl_label = "CAM feedrate" + """CAM Feedrate Panel""" + bl_label = "CAM Feedrate" bl_idname = "WORLD_PT_CAM_FEEDRATE" panel_interface_level = 0 diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index 934eaefba..95991cbf9 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -5,8 +5,8 @@ class CAM_GCODE_Panel(CAMButtonsPanel, Panel): - """CAM operation g-code options panel""" - bl_label = "CAM g-code options " + """CAM Operation G-code Options Panel""" + bl_label = "CAM G-code Options" bl_idname = "WORLD_PT_CAM_GCODE" panel_interface_level = 1 diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index 4acbd0578..cbab7838d 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -28,20 +28,23 @@ class CAM_INFO_Properties(PropertyGroup): warnings: StringProperty( - name='warnings', - description='warnings', + name='Warnings', + description='Warnings', default='', update=update_operation, ) chipload: FloatProperty( - name="chipload", description="Calculated chipload", + name="Chipload", + description="Calculated chipload", default=0.0, unit='LENGTH', precision=CHIPLOAD_PRECISION, ) duration: FloatProperty( - name="Estimated time", default=0.01, min=0.0000, + name="Estimated Time", + default=0.01, + min=0.0000, max=MAX_OPERATION_TIME, precision=PRECISION, unit="TIME", @@ -49,7 +52,7 @@ class CAM_INFO_Properties(PropertyGroup): class CAM_INFO_Panel(CAMButtonsPanel, Panel): - bl_label = "CAM info & warnings" + bl_label = "CAM Info & Warnings" bl_idname = "WORLD_PT_CAM_INFO" panel_interface_level = 0 always_show_panel = True @@ -68,9 +71,9 @@ def draw_blendercam_version(self): if not self.has_correct_level(): return self.layout.label( - text=f'Blendercam version: {".".join([str(x) for x in cam_version])}') + text=f'BlenderCAM v{".".join([str(x) for x in cam_version])}') if len(bpy.context.preferences.addons['cam'].preferences.new_version_available) > 0: - self.layout.label(text=f"New version available:") + self.layout.label(text=f"New Version Available:") self.layout.label( text=f" {bpy.context.preferences.addons['cam'].preferences.new_version_available}") self.layout.operator("render.cam_update_now") @@ -81,10 +84,10 @@ def draw_opencamlib_version(self): return ocl_version = opencamlib_version() if ocl_version is None: - self.layout.label(text="Opencamlib is not installed") + self.layout.label(text="OpenCAMLib is not Installed") else: self.layout.label( - text=f"Opencamlib v{ocl_version} installed") + text=f"OpenCAMLib v{ocl_version}") # Display warnings related to the current operation def draw_op_warnings(self): @@ -101,7 +104,7 @@ def draw_op_time(self): if not int(self.op.info.duration * 60) > 0: return - time_estimate = f"Operation duration: {int(self.op.info.duration*60)}s " + time_estimate = f"Operation Duration: {int(self.op.info.duration*60)}s " if self.op.info.duration > 60: time_estimate += f" ({int(self.op.info.duration / 60)}h" time_estimate += f" {round(self.op.info.duration % 60)}min)" @@ -136,7 +139,7 @@ def draw_op_money_cost(self): cost_per_second = bpy.context.scene.cam_machine.hourly_rate / 3600 total_cost = self.op.info.duration * 60 * cost_per_second - op_cost = f"Operation cost: ${total_cost:.2f} (${cost_per_second:.2f}/s)" + op_cost = f"Operation Cost: ${total_cost:.2f} (${cost_per_second:.2f}/s)" self.layout.label(text=op_cost) # Display the Info Panel diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index 8e2b18689..b34c97c68 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -5,7 +5,7 @@ class CAM_MACHINE_Panel(CAMButtonsPanel, Panel): - """CAM machine panel""" + """CAM Machine Panel""" bl_label = "CAM Machine" bl_idname = "WORLD_PT_CAM_MACHINE" always_show_panel = True diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 28ee78cfd..dd3075508 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -22,14 +22,14 @@ class CAM_MATERIAL_Properties(PropertyGroup): estimate_from_model: BoolProperty( - name="Estimate cut area from model", + name="Estimate Cut Area from Model", description="Estimate cut area based on model geometry", default=True, update=update_material, ) radius_around_model: FloatProperty( - name='Radius around model', + name='Radius Around Model', description="Increase cut area around the model on X and " "Y by this amount", default=0.0, @@ -39,21 +39,21 @@ class CAM_MATERIAL_Properties(PropertyGroup): ) center_x: BoolProperty( - name="Center on X axis", + name="Center on X Axis", description="Position model centered on X", default=False, update=update_material, ) center_y: BoolProperty( - name="Center on Y axis", + name="Center on Y Axis", description="Position model centered on Y", default=False, update=update_material, ) z_position: EnumProperty( - name="Z placement", + name="Z Placement", items=( ('ABOVE', 'Above', 'Place object vertically above the XY plane'), ('BELOW', 'Below', 'Place object vertically below the XY plane'), @@ -67,7 +67,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): # material_origin origin: FloatVectorProperty( - name='Material origin', + name='Material Origin', default=(0, 0, 0), unit='LENGTH', precision=PRECISION, @@ -77,7 +77,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): # material_size size: FloatVectorProperty( - name='Material size', + name='Material Size', default=(0.200, 0.200, 0.100), min=0, unit='LENGTH', @@ -92,7 +92,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): class CAM_MATERIAL_PositionObject(Operator): bl_idname = "object.material_cam_position" - bl_label = "position object for CAM operation" + bl_label = "Position Object for CAM Operation" bl_options = {'REGISTER', 'UNDO'} interface_level = 0 @@ -102,7 +102,7 @@ def execute(self, context): if operation.object_name in bpy.data.objects: positionObject(operation) else: - print('no object assigned') + print('No Object Assigned') return {'FINISHED'} def draw(self, context): @@ -113,7 +113,7 @@ def draw(self, context): class CAM_MATERIAL_Panel(CAMButtonsPanel, Panel): - bl_label = "CAM Material size and position" + bl_label = "CAM Material Size and Position" bl_idname = "WORLD_PT_CAM_MATERIAL" panel_interface_level = 0 @@ -127,7 +127,7 @@ def draw_estimate_from_image(self): if not self.has_correct_level(): return if self.op.geometry_source not in ['OBJECT', 'COLLECTION']: - self.layout.label(text='Estimated from image') + self.layout.label(text='Estimated from Image') def draw_estimate_from_object(self): if not self.has_correct_level(): @@ -136,7 +136,7 @@ def draw_estimate_from_object(self): self.layout.prop(self.op.material, 'estimate_from_model') if self.op.material.estimate_from_model: row_radius = self.layout.row() - row_radius.label(text="Additional radius") + row_radius.label(text="Additional Radius") row_radius.prop(self.op.material, 'radius_around_model', text='') else: @@ -153,7 +153,7 @@ def draw_axis_alignment(self): row_axis.prop(self.op.material, 'center_y') self.layout.prop(self.op.material, 'z_position') self.layout.operator( - "object.material_cam_position", text="Position object") + "object.material_cam_position", text="Position Object") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index a29885528..3dc111c26 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -22,14 +22,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): # movement parallel_step_back type: EnumProperty( - name='Movement type', + name='Movement Type', items=( ('CONVENTIONAL', 'Conventional / Up milling', - 'cutter rotates against the direction of the feed'), + 'Cutter rotates against the direction of the feed'), ('CLIMB', 'Climb / Down milling', - 'cutter rotates with the direction of the feed'), + 'Cutter rotates with the direction of the feed'), ('MEANDER', 'Meander / Zig Zag', - 'cutting is done both with and against the ' + 'Cutting is done both with and against the ' 'rotation of the spindle') ), description='movement type', @@ -43,16 +43,16 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ('INSIDEOUT', 'Inside out', 'a'), ('OUTSIDEIN', 'Outside in', 'a') ), - description='approach to the piece', + description='Approach to the piece', default='INSIDEOUT', update=update_operation, ) spindle_rotation: EnumProperty( - name='Spindle rotation', + name='Spindle Rotation', items=( - ('CW', 'Clock wise', 'a'), - ('CCW', 'Counter clock wise', 'a') + ('CW', 'Clockwise', 'a'), + ('CCW', 'Counter clockwise', 'a') ), description='Spindle rotation direction', default='CW', @@ -60,7 +60,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) free_height: FloatProperty( - name="Free movement height", + name="Free Movement Height", default=0.01, min=0.0000, max=32, @@ -70,7 +70,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) useG64: BoolProperty( - name="G64 trajectory", + name="G64 Trajectory", description='Use only if your machine supports ' 'G64 code. LinuxCNC and Mach3 do', default=False, @@ -88,7 +88,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) parallel_step_back: BoolProperty( - name="Parallel step back", + name="Parallel Step Back", description='For roughing and finishing in one pass: mills ' 'material in climb mode, then steps back and goes ' 'between 2 last chunks back', @@ -97,14 +97,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) helix_enter: BoolProperty( - name="Helix enter - EXPERIMENTAL", + name="Helix Enter - EXPERIMENTAL", description="Enter material in helix", default=False, update=update_operation, ) ramp_in_angle: FloatProperty( - name="Ramp in angle", + name="Ramp-in Angle", default=pi / 6, min=0, max=pi * 0.4999, @@ -115,7 +115,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) helix_diameter: FloatProperty( - name='Helix diameter - % of cutter diameter', + name='Helix Diameter - % of Cutter Diameter', default=90, min=10, max=100, @@ -125,7 +125,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) ramp: BoolProperty( - name="Ramp in - EXPERIMENTAL", + name="Ramp-in - EXPERIMENTAL", description="Ramps down the whole contour, so the cutline looks " "like helix", default=False, @@ -133,14 +133,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) ramp_out: BoolProperty( - name="Ramp out - EXPERIMENTAL", + name="Ramp-out - EXPERIMENTAL", description="Ramp out to not leave mark on surface", default=False, update=update_operation, ) ramp_out_angle: FloatProperty( - name="Ramp out angle", + name="Ramp-out Angle", default=pi / 6, min=0, max=pi * 0.4999, @@ -151,14 +151,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) retract_tangential: BoolProperty( - name="Retract tangential - EXPERIMENTAL", + name="Retract Tangential - EXPERIMENTAL", description="Retract from material in circular motion", default=False, update=update_operation, ) retract_radius: FloatProperty( - name='Retract arc radius', + name='Retract Arc Radius', default=0.001, min=0.000001, max=100, @@ -168,7 +168,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) retract_height: FloatProperty( - name='Retract arc height', + name='Retract Arc Height', default=0.001, min=0.00000, max=100, @@ -178,13 +178,13 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) stay_low: BoolProperty( - name="Stay low if possible", + name="Stay Low if Possible", default=True, update=update_operation, ) merge_dist: FloatProperty( - name="Merge distance - EXPERIMENTAL", + name="Merge Distance - EXPERIMENTAL", default=0.0, min=0.0000, max=0.1, @@ -194,15 +194,15 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) protect_vertical: BoolProperty( - name="Protect vertical", + name="Protect Vertical", description="The path goes only vertically next to steep areas", default=True, update=update_operation, ) protect_vertical_limit: FloatProperty( - name="Verticality limit", - description="What angle is allready considered vertical", + name="Verticality Limit", + description="What angle is already considered vertical", default=pi / 45, min=0, max=pi * 0.5, @@ -214,8 +214,8 @@ class CAM_MOVEMENT_Properties(PropertyGroup): class CAM_MOVEMENT_Panel(CAMButtonsPanel, Panel): - """CAM movement panel""" - bl_label = "CAM movement" + """CAM Movement Panel""" + bl_label = "CAM Movement" bl_idname = "WORLD_PT_CAM_MOVEMENT" panel_interface_level = 0 @@ -249,7 +249,7 @@ def draw_free_height(self): return self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: - self.layout.label(text='Depth start > Free movement') + self.layout.label(text='Depth Start > Free Movement') self.layout.label(text='POSSIBLE COLLISION') def draw_use_g64(self): diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index dc52da053..ef1e2f066 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -5,8 +5,8 @@ class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, Panel): - """CAM operation properties panel""" - bl_label = "CAM operation setup" + """CAM Operation Properties Panel""" + bl_label = "CAM Operation Setup" bl_idname = "WORLD_PT_CAM_OPERATION" panel_interface_level = 0 @@ -44,9 +44,9 @@ def draw_cutter_engagement(self): engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) if engagement > 50: - self.layout.label(text="Warning: High cutter engagement") + self.layout.label(text="Warning: High Cutter Engagement") - self.layout.label(text=f"Cutter engagement: {engagement}%") + self.layout.label(text=f"Cutter Engagement: {engagement}%") def draw_machine_axis(self): if not self.has_correct_level(): @@ -139,14 +139,14 @@ def draw_waterline_options(self): if not self.has_correct_level(): return if self.op.strategy in ['WATERLINE']: - self.layout.label(text="OCL doesn't support fill areas") + self.layout.label(text="Ocl Doesn't Support Fill Areas") if not self.op.optimisation.use_opencamlib: self.layout.prop(self.op, 'slice_detail') self.layout.prop(self.op, 'waterline_fill') if self.op.waterline_fill: self.layout.prop(self.op, 'dist_between_paths') self.layout.prop(self.op, 'waterline_project') - self.layout.label(text="Waterline needs a skin margin") + self.layout.label(text="Waterline Needs a Skin Margin") def draw_carve_options(self): if not self.has_correct_level(): @@ -203,7 +203,7 @@ def draw_bridges_options(self): self.layout.prop(self.op, 'bridges_height') self.layout.prop_search(self.op, "bridges_collection_name", bpy.data, "collections") self.layout.prop(self.op, 'use_bridge_modifiers') - self.layout.operator("scene.cam_bridges_add", text="Autogenerate bridges") + self.layout.operator("scene.cam_bridges_add", text="Autogenerate Bridges / Tabs") def draw_skin(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index 7fa38ef27..d5d8ca5c9 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -14,8 +14,8 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, Panel): - """CAM operations panel""" - bl_label = "CAM operations" + """CAM Operations Panel""" + bl_label = "CAM Operations" bl_idname = "WORLD_PT_CAM_OPERATIONS" always_show_panel = True panel_interface_level = 0 @@ -60,14 +60,14 @@ def draw_calculate_path(self): return if self.op.maxz > self.op.movement.free_height: self.layout.label(text='!ERROR! COLLISION!') - self.layout.label(text='Depth start > Free movement height') + self.layout.label(text='Depth Start > Free Movement Height') self.layout.label(text='!ERROR! COLLISION!') self.layout.prop(self.op.movement, 'free_height') if not self.op.valid: - self.layout.label(text="Select a valid object to calculate the path.") + self.layout.label(text="Select a Valid Object to Calculate the Path.") # will be disable if not valid - self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") + self.layout.operator("object.calculate_cam_path", text="Calculate Path & Export Gcode") def draw_export_gcode(self): if not self.has_correct_level(): @@ -82,7 +82,7 @@ def draw_simulate_op(self): if not self.has_correct_level(): return if self.op.valid: - self.layout.operator("object.cam_simulate", text="Simulate this operation") + self.layout.operator("object.cam_simulate", text="Simulate This Operation") def draw_op_name(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index 4b6490119..39bcdd416 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -23,14 +23,14 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): optimize: BoolProperty( - name="Reduce path points", + name="Reduce Path Points", description="Reduce path points", default=True, update=update_operation, ) optimize_threshold: FloatProperty( - name="Reduction threshold in μm", + name="Reduction Threshold in μm", default=.2, min=0.000000001, max=1000, @@ -39,7 +39,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) use_exact: BoolProperty( - name="Use exact mode", + name="Use Exact Mode", description="Exact mode allows greater precision, but is slower " "with complex meshes", default=True, @@ -47,7 +47,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) imgres_limit: IntProperty( - name="Maximum resolution in megapixels", + name="Maximum Resolution in Megapixels", default=16, min=1, max=512, @@ -57,7 +57,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) pixsize: FloatProperty( - name="sampling raster detail", + name="Sampling Raster Detail", default=0.0001, min=0.00001, max=0.1, @@ -74,7 +74,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) exact_subdivide_edges: BoolProperty( - name="Auto subdivide long edges", + name="Auto Subdivide Long Edges", description="This can avoid some collision issues when " "importing CAD models", default=False, @@ -82,7 +82,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) circle_detail: IntProperty( - name="Detail of circles used for curve offsets", + name="Detail of Circles Used for Curve Offsets", default=64, min=12, max=512, @@ -90,7 +90,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) simulation_detail: FloatProperty( - name="Simulation sampling raster detail", + name="Simulation Sampling Raster Detail", default=0.0002, min=0.00001, max=0.01, @@ -101,8 +101,8 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): class CAM_OPTIMISATION_Panel(CAMButtonsPanel, Panel): - """CAM optimisation panel""" - bl_label = "CAM optimisation" + """CAM Optimisation Panel""" + bl_label = "CAM Optimisation" bl_idname = "WORLD_PT_CAM_OPTIMISATION" panel_interface_level = 2 @@ -150,7 +150,7 @@ def draw_use_opencamlib(self): ocl_version = opencamlib_version() if ocl_version is None: - self.layout.label(text="Opencamlib is not available ") + self.layout.label(text="OpenCAMLib is not Available ") self.layout.prop(self.op.optimisation, 'exact_subdivide_edges') else: self.layout.prop(self.op.optimisation, 'use_opencamlib') diff --git a/scripts/addons/cam/ui_panels/pack.py b/scripts/addons/cam/ui_panels/pack.py index 6d2f3767e..105c30923 100644 --- a/scripts/addons/cam/ui_panels/pack.py +++ b/scripts/addons/cam/ui_panels/pack.py @@ -5,8 +5,8 @@ class CAM_PACK_Panel(CAMButtonsPanel, Panel): - """CAM material panel""" - bl_label = "Pack curves on sheet" + """CAM Pack Panel""" + bl_label = "Pack Curves on Sheet" bl_idname = "WORLD_PT_CAM_PACK" panel_interface_level = 2 @@ -16,8 +16,8 @@ def draw(self, context): layout = self.layout scene = bpy.context.scene settings = scene.cam_pack - layout.label(text='warning - algorithm is slow.') - layout.label(text='only for curves now.') + layout.label(text='Warning - Algorithm Is Slow.') + layout.label(text='Only for Curves Now.') layout.operator("object.cam_pack_objects") layout.prop(settings, 'sheet_fill_direction') diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index 10ef67029..8a1daebb7 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -5,8 +5,8 @@ class CAM_SLICE_Panel(CAMButtonsPanel, Panel): - """CAM slicer panel""" - bl_label = "Slice model to plywood sheets" + """CAM Slicer Panel""" + bl_label = "Slice Model to Plywood Sheets" bl_idname = "WORLD_PT_CAM_SLICE" panel_interface_level = 2 diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 476e7ad16..750573de9 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -68,7 +68,7 @@ oclSample, oclResampleChunks, ) -from .polygon_utils_cam import shapelyToCurve +from .polygon_utils_cam import shapelyToCurve, shapelyToMultipolygon from .simple import ( activate, progress, @@ -287,21 +287,21 @@ def getOperationSources(o): def getBounds(o): # print('kolikrat sem rpijde') if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION' or o.geometry_source == 'CURVE': - print("valid geometry") + print("Valid Geometry") minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace(o.objects, o.use_modifiers) if o.minz_from == 'OBJECT': if minz == 10000000: minz = 0 - print("minz from object:" + str(minz)) + print("Minz from Object:" + str(minz)) o.min.z = minz o.minz = o.min.z else: o.min.z = o.minz # max(bb[0][2]+l.z,o.minz)# - print("not minz from object") + print("Not Minz from Object") if o.material.estimate_from_model: - print("Estimate material from model") + print("Estimate Material from Model") o.min.x = minx - o.material.radius_around_model o.min.y = miny - o.material.radius_around_model @@ -310,7 +310,7 @@ def getBounds(o): o.max.x = maxx + o.material.radius_around_model o.max.y = maxy + o.material.radius_around_model else: - print("not material from model") + print("Not Material from Model") o.min.x = o.material.origin.x o.min.y = o.material.origin.y o.min.z = o.material.origin.z - o.material.size.z @@ -342,14 +342,14 @@ def getBounds(o): s = bpy.context.scene m = s.cam_machine # make sure this message only shows once and goes away once fixed - o.info.warnings.replace('Operation exceeds your machine limits\n', '') + o.info.warnings.replace('Operation Exceeds Your Machine Limits\n', '') if o.max.x - o.min.x > m.working_area.x or o.max.y - o.min.y > m.working_area.y \ or o.max.z - o.min.z > m.working_area.z: - o.info.warnings += 'Operation exceeds your machine limits\n' + o.info.warnings += 'Operation Exceeds Your Machine Limits\n' def getBoundsMultiple(operations): - """gets bounds of multiple operations, mainly for purpose of simulations or rest milling. highly suboptimal.""" + """Gets Bounds of Multiple Operations, Mainly for Purpose of Simulations or Rest Milling. Highly Suboptimal.""" maxx = maxy = maxz = -10000000 minx = miny = minz = 10000000 for o in operations: @@ -458,7 +458,7 @@ async def sampleChunks(o, pathSamples, layers): ob = bpy.data.objects[o.object_name] zinvert = ob.location.z + maxz # ob.bound_box[6][2] - print(f"Total sample points {totlen}") + print(f"Total Sample Points {totlen}") n = 0 last_percent = -1 @@ -616,7 +616,7 @@ async def sampleChunks(o, pathSamples, layers): lastrunchunks = thisrunchunks # print(len(layerchunks[i])) - progress('checking relations between paths') + progress('Checking Relations Between Paths') timingstart(sortingtime) if o.strategy == 'PARALLEL' or o.strategy == 'CROSS' or o.strategy == 'OUTLINEFILL': @@ -665,7 +665,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): cutterdepth = cutter.dimensions.z / 2 t = time.time() - print('sampling paths') + print('Sampling Paths') totlen = 0 # total length of all chunks, to estimate sampling time. for chs in pathSamples: @@ -868,7 +868,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): # print(len(layerchunks[i])) - progress('checking relations between paths') + progress('Checking Relations Between Paths') """#this algorithm should also work for n-axis, but now is "sleeping" if (o.strategy=='PARALLEL' or o.strategy=='CROSS'): if len(layers)>1:# sorting help so that upper layers go first always @@ -1025,7 +1025,7 @@ def overlaps(bb1, bb2): # true if bb1 is child of bb2 async def connectChunksLow(chunks, o): - """ connects chunks that are close to each other without lifting, sampling them 'low' """ + """ Connects Chunks that Are Close to Each Other without Lifting, Sampling Them 'low' """ if not o.movement.stay_low or (o.strategy == 'CARVE' and o.carve_depth > 0): return chunks @@ -1183,7 +1183,7 @@ def getVectorRight(lastv, verts): def cleanUpDict(ndict): # now it should delete all junk first, iterate over lonely verts. - print('removing lonely points') + print('Removing Lonely Points') # found_solitaires=True # while found_solitaires: found_solitaires = False @@ -1234,8 +1234,8 @@ def cutloops(csource, parentloop, loops): def getOperationSilhouete(operation): - """gets silhouete for the operation - uses image thresholding for everything except curves. + """Gets Silhouette for the Operation + Uses Image Thresholding for Everything Except Curves. """ if operation.update_silhouete_tag: image = None @@ -1255,7 +1255,7 @@ def getOperationSilhouete(operation): totfaces += len(ob.data.polygons) if (stype == 'OBJECTS' and totfaces > 200000) or stype == 'IMAGE': - print('image method') + print('Image Method') samples = renderSampleImage(operation) if stype == 'OBJECTS': i = samples > operation.minz - 0.0000001 @@ -1296,7 +1296,7 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if totfaces < 20000000: # boolean polygons method originaly was 20 000 poly limit, now limitless, # it might become teribly slow, but who cares? t = time.time() - print('shapely getting silhouette') + print('Shapely Getting Silhouette') polys = [] for ob in objects: if use_modifiers: @@ -1334,7 +1334,7 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if totfaces < 20000: p = sops.unary_union(polys) else: - print('computing in parts') + print('Computing in Parts') bigshapes = [] i = 1 part = 20000 @@ -1346,13 +1346,13 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if (i - 1) * part < totfaces: last_ar = polys[(i - 1) * part:] bigshapes.append(sops.unary_union(last_ar)) - print('joining') + print('Joining') p = sops.unary_union(bigshapes) print(time.time() - t) t = time.time() - silhouete = [p] # [polygon_utils_cam.Shapely2Polygon(p)] + silhouete = shapelyToMultipolygon(p) # [polygon_utils_cam.Shapely2Polygon(p)] return silhouete @@ -1427,7 +1427,7 @@ def getObjectOutline(radius, o, Offset): # FIXME: make this one operation indep def addOrientationObject(o): - """the orientation object should be used to set up orientations of the object for 4 and 5 axis milling.""" + """The Orientation Object Should Be Used to Set up Orientations of the Object for 4 and 5 Axis Milling.""" name = o.name + ' orientation' s = bpy.context.scene if s.objects.find(name) == -1: @@ -1588,7 +1588,7 @@ def __init__(self, x, y, z): def unique(L): - """Return a list of unhashable elements in s, but without duplicates. + """Return a List of Unhashable Elements in S, but without Duplicates. [[1, 2], [2, 3], [1, 2]] >>> [[1, 2], [2, 3]]""" # For unhashable objects, you can sort the sequence and then scan from the end of the list, # deleting duplicates as you go @@ -1684,9 +1684,9 @@ def cleanupIndexed(operation): def rotTo2axes(e, axescombination): - """converts an orientation object rotation to rotation defined by 2 rotational axes on the machine - - for indexed machining. - attempting to do this for all axes combinations. + """Converts an Orientation Object Rotation to Rotation Defined by 2 Rotational Axes on the Machine - + for Indexed Machining. + Attempting to Do This for All Axes Combinations. """ v = Vector((0, 0, 1)) v.rotate(e) @@ -1813,13 +1813,13 @@ def reload_paths(o): def updateMachine(self, context): - print('update machine ') + print('Update Machine') if not _IS_LOADING_DEFAULTS: addMachineAreaObject() def updateMaterial(self, context): - print('update material') + print('Update Material') addMaterialAreaObject() @@ -1885,7 +1885,7 @@ def operationValid(self, context): o = scene.cam_operations[scene.cam_active_operation] o.changed = True o.valid = isValid(o, context) - invalidmsg = "Invalid source object for operation.\n" + invalidmsg = "Invalid Source Object for Operation.\n" if o.valid: o.info.warnings = "" else: @@ -1908,9 +1908,9 @@ def isChainValid(chain, context): if so.name == cho.name: found_op = so if found_op == None: - return (False, f"Couldn't find operation {cho.name}") + return (False, f"Couldn't Find Operation {cho.name}") if isValid(found_op, context) is False: - return (False, f"Operation {found_op.name} is not valid") + return (False, f"Operation {found_op.name} Is Not Valid") return (True, "") @@ -1920,8 +1920,8 @@ def updateOperationValid(self, context): # Update functions start here def updateChipload(self, context): - """this is very simple computation of chip size, could be very much improved""" - print('update chipload ') + """This Is Very Simple Computation of Chip Size, Could Be Very Much Improved""" + print('Update Chipload ') o = self # Old chipload o.info.chipload = (o.feedrate / (o.spindle_rpm * o.cutter_flutes)) @@ -1942,15 +1942,15 @@ def updateChipload(self, context): def updateOffsetImage(self, context): - """refresh offset image tag for rerendering""" + """Refresh Offset Image Tag for Rerendering""" updateChipload(self, context) - print('update offset') + print('Update Offset') self.changed = True self.update_offsetimage_tag = True def updateZbufferImage(self, context): - """changes tags so offset and zbuffer images get updated on calculation time.""" + """Changes Tags so Offset and Zbuffer Images Get Updated on Calculation Time.""" # print('updatezbuf') # print(self,context) self.changed = True @@ -1962,7 +1962,7 @@ def updateZbufferImage(self, context): def updateStrategy(o, context): """""" o.changed = True - print('update strategy') + print('Update Strategy') if o.machine_axes == '5' or ( o.machine_axes == '4' and o.strategy4axis == 'INDEXED'): # INDEXED 4 AXIS DOESN'T EXIST NOW... addOrientationObject(o) @@ -1976,35 +1976,35 @@ def updateCutout(o, context): def updateExact(o, context): - print('update exact ') + print('Update Exact ') o.changed = True o.update_zbufferimage_tag = True o.update_offsetimage_tag = True if o.optimisation.use_exact: if o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS' or o.inverse: o.optimisation.use_opencamlib = False - print('Current operation cannot use exact mode') + print('Current Operation Cannot Use Exact Mode') else: o.optimisation.use_opencamlib = False def updateOpencamlib(o, context): - print('update opencamlib ') + print('Update OpenCAMLib ') o.changed = True if o.optimisation.use_opencamlib and ( o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS'): o.optimisation.use_exact = False o.optimisation.use_opencamlib = False - print('Current operation cannot use opencamlib') + print('Current Operation Cannot Use OpenCAMLib') def updateBridges(o, context): - print('update bridges ') + print('Update Bridges ') o.changed = True def updateRotation(o, context): - print('update rotation') + print('Update Rotation') if o.enable_B or o.enable_A: print(o, o.rotation_A) ob = bpy.data.objects[o.object_name] @@ -2029,7 +2029,7 @@ def updateRotation(o, context): # o.changed = True def updateRest(o, context): - print('update rest ') + print('Update Rest ') o.changed = True @@ -2101,7 +2101,7 @@ def update_zbuffer_image(self, context): @bpy.app.handlers.persistent def check_operations_on_load(context): - """checks any broken computations on load and reset them.""" + """Checks Any Broken Computations on Load and Reset Them.""" s = bpy.context.scene for o in s.cam_operations: if o.computing: @@ -2113,7 +2113,7 @@ def check_operations_on_load(context): machine_preset = bpy.context.preferences.addons[ "cam"].preferences.machine_preset = bpy.context.preferences.addons["cam"].preferences.default_machine_preset if len(machine_preset) > 0: - print("Loading preset:", machine_preset) + print("Loading Preset:", machine_preset) # load last used machine preset bpy.ops.script.execute_preset( filepath=machine_preset, menu_idname="CAM_MACHINE_MT_presets" diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index eb9ecdf17..32f985d18 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -581,16 +581,16 @@ def __init__(self, edge=None, pm=Edge.LE): def dump(self): print("Halfedge--------------------------") - print("left: ", self.left) - print("right: ", self.right) - print("edge: ", self.edge) - print("pm: ", self.pm) - print("vertex: "), + print("Left: ", self.left) + print("Right: ", self.right) + print("Edge: ", self.edge) + print("PM: ", self.pm) + print("Vertex: "), if self.vertex: self.vertex.dump() else: print("None") - print("ystar: ", self.ystar) + print("Ystar: ", self.ystar) def __lt__(self, other): if self.ystar < other.ystar: From bd4fe87b50ad56c0f5e30678451a618912e2b212 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 11 Apr 2024 14:55:34 -0400 Subject: [PATCH 069/100] TItle Case for Names, Labels Enforced title case and consistency in names, labels, print and progress strings, fixed typos. --- scripts/addons/cam/opencamlib/oclSample.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index 6be08d42d..e99f5425f 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -43,7 +43,7 @@ def get_oclSTL(operation): # FIXME needs to work with collections if not found_mesh: raise CamException( - "This operation requires a mesh or curve object or equivalent (e.g. text, volume).") + "This Operation Requires a Mesh or Curve Object or Equivalent (e.g. Text, Volume).") return oclSTL @@ -78,7 +78,7 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) * 1000, operation.bull_corner_radius*1000, cutter_length) else: - print("Cutter unsupported: {0}\n".format(op_cutter_type)) + print("Cutter Unsupported: {0}\n".format(op_cutter_type)) quit() bdc = ocl.BatchDropCutter() @@ -93,7 +93,7 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): for chunk in chunks: for coord in chunk.get_points_np(): bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) - await progress_async("OpenCAMLib sampling") + await progress_async("OpenCAMLib Sampling") bdc.run() cl_points = bdc.getCLPoints() From dee93d20ebd533711f0ac837a66a8bc21099ceaf Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 11 Apr 2024 15:01:02 -0400 Subject: [PATCH 070/100] Update README.md Fixed Files Organization pipe inconsistency. Updated documentation comment to reflect naming used elsewhere in the README. --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a0870ef5f..3c4599ece 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ BlenderCAM works on Windows or Linux. ```graphql . ├── config - # 'startup' and 'userpref' blend files -├── documentation - # Markdown guides and images +├── documentation - # How to Use (Wiki) - files ├── Examples - # Bas Relief & Intarsion operation demo files and images ├── scripts │   └── addons @@ -108,12 +108,12 @@ BlenderCAM works on Windows or Linux. │         ├── nc - # Post-Processors │         ├── opencamlib - # OpenCAMLib functions │    ├── presets - # Quick access to pre-defined cutting tools, machines and operations -│    | ├── cam_cutters -│    | ├── cam_machines -│    | └── cam_operations -| ├── tests - # Developer Tests -| | └── test_data - # Test output -| └── ui_panels - # User Interface +│    │ ├── cam_cutters +│    │ ├── cam_machines +│    │ └── cam_operations +│ ├── tests - # Developer Tests +│ │ └── test_data - # Test output +│ └── ui_panels - # User Interface └── static - # Logo ``` From 4e87fb399e6f05616dc06cd55f9f9479b323b905 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 11 Apr 2024 15:02:08 -0400 Subject: [PATCH 071/100] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a0870ef5f..3c4599ece 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ BlenderCAM works on Windows or Linux. ```graphql . ├── config - # 'startup' and 'userpref' blend files -├── documentation - # Markdown guides and images +├── documentation - # How to Use (Wiki) - files ├── Examples - # Bas Relief & Intarsion operation demo files and images ├── scripts │   └── addons @@ -108,12 +108,12 @@ BlenderCAM works on Windows or Linux. │         ├── nc - # Post-Processors │         ├── opencamlib - # OpenCAMLib functions │    ├── presets - # Quick access to pre-defined cutting tools, machines and operations -│    | ├── cam_cutters -│    | ├── cam_machines -│    | └── cam_operations -| ├── tests - # Developer Tests -| | └── test_data - # Test output -| └── ui_panels - # User Interface +│    │ ├── cam_cutters +│    │ ├── cam_machines +│    │ └── cam_operations +│ ├── tests - # Developer Tests +│ │ └── test_data - # Test output +│ └── ui_panels - # User Interface └── static - # Logo ``` From a56bd333578e914e635066454cd2c8ae8be9e1bf Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 11 Apr 2024 15:06:21 -0400 Subject: [PATCH 072/100] Title Case, Mesh Bridges Fix, README Update Enforced title case and naming consistency across names, labels, print and progress strings. Fixed Bridge Generation for Mesh-based objects, though path generation does not account for bridges unless they are made into meshes and joined with the main object - will need to be patched separately. Updated the README to fix an inconsistency with the pipes in the Files Organization diagram, and to change the comment in 'documentation' to reflect the naming used elsewhere - 'How to Use (Wiki)' --- scripts/addons/cam/async_op.py | 10 +- scripts/addons/cam/autoupdate.py | 18 +- scripts/addons/cam/basrelief.py | 104 +++--- scripts/addons/cam/bridges.py | 4 +- scripts/addons/cam/cam_chunk.py | 22 +- scripts/addons/cam/cam_operation.py | 335 +++++++++--------- scripts/addons/cam/chain.py | 14 +- scripts/addons/cam/collision.py | 12 +- scripts/addons/cam/curvecamcreate.py | 178 +++++----- scripts/addons/cam/curvecamequation.py | 68 ++-- scripts/addons/cam/curvecamtools.py | 78 ++-- scripts/addons/cam/engine.py | 2 +- scripts/addons/cam/gcodeimportparser.py | 2 +- scripts/addons/cam/gcodepath.py | 18 +- scripts/addons/cam/image_utils.py | 22 +- scripts/addons/cam/joinery.py | 6 +- scripts/addons/cam/machine_settings.py | 74 ++-- scripts/addons/cam/opencamlib/oclSample.py | 6 +- scripts/addons/cam/ops.py | 126 +++---- scripts/addons/cam/pack.py | 16 +- scripts/addons/cam/pattern.py | 4 +- scripts/addons/cam/polygon_utils_cam.py | 2 +- scripts/addons/cam/preferences.py | 30 +- scripts/addons/cam/preset_managers.py | 6 +- scripts/addons/cam/simple.py | 14 +- scripts/addons/cam/simulation.py | 6 +- scripts/addons/cam/slice.py | 16 +- scripts/addons/cam/strategy.py | 66 ++-- scripts/addons/cam/testing.py | 10 +- scripts/addons/cam/ui.py | 12 +- scripts/addons/cam/ui_panels/area.py | 22 +- scripts/addons/cam/ui_panels/chains.py | 14 +- scripts/addons/cam/ui_panels/cutter.py | 10 +- scripts/addons/cam/ui_panels/feedrate.py | 4 +- scripts/addons/cam/ui_panels/gcode.py | 4 +- scripts/addons/cam/ui_panels/info.py | 25 +- scripts/addons/cam/ui_panels/machine.py | 2 +- scripts/addons/cam/ui_panels/material.py | 26 +- scripts/addons/cam/ui_panels/movement.py | 56 +-- scripts/addons/cam/ui_panels/op_properties.py | 14 +- scripts/addons/cam/ui_panels/operations.py | 12 +- scripts/addons/cam/ui_panels/optimisation.py | 22 +- scripts/addons/cam/ui_panels/pack.py | 8 +- scripts/addons/cam/ui_panels/slice.py | 4 +- scripts/addons/cam/utils.py | 94 ++--- scripts/addons/cam/voronoi.py | 12 +- 46 files changed, 806 insertions(+), 804 deletions(-) diff --git a/scripts/addons/cam/async_op.py b/scripts/addons/cam/async_op.py index bb298a626..5cc115662 100644 --- a/scripts/addons/cam/async_op.py +++ b/scripts/addons/cam/async_op.py @@ -6,7 +6,7 @@ @types.coroutine def progress_async(text, n=None, value_type='%'): - """function for reporting during the script, works for background operations in the header.""" + """Function for Reporting During the Script, Works for Background Operations in the Header.""" throw_exception = yield ('progress', {'text': text, 'n': n, "value_type": value_type}) if throw_exception is not None: raise throw_exception @@ -65,7 +65,7 @@ def tick(self, context): self.coroutine = self.execute_async(context) try: if self._is_cancelled: - (msg, args) = self.coroutine.send(AsyncCancelledException("Cancelled with ESC key")) + (msg, args) = self.coroutine.send(AsyncCancelledException("Cancelled with ESC Key")) raise StopIteration else: (msg, args) = self.coroutine.send(None) @@ -77,7 +77,7 @@ def tick(self, context): except StopIteration: return False except Exception as e: - print("Exception thrown in tick:", e) + print("Exception Thrown in Tick:", e) def execute(self, context): if bpy.app.background: @@ -93,9 +93,9 @@ def execute(self, context): class AsyncTestOperator(bpy.types.Operator, AsyncOperatorMixin): - """test async operator""" + """Test Async Operator""" bl_idname = "object.cam_async_test_operator" - bl_label = "Test operator for async stuff" + bl_label = "Test Operator for Async Stuff" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} async def execute_async(self, context): diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 70856e9c4..7ac6a32a9 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -16,9 +16,9 @@ class UpdateChecker(bpy.types.Operator): - """check for updates""" + """Check for Updates""" bl_idname = "render.cam_check_updates" - bl_label = "Check for updates in blendercam plugin" + bl_label = "Check for Updates in BlenderCAM Plugin" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -31,7 +31,7 @@ def execute(self, context): if match: update_source = f"https://api.github.com/repos/{match.group(1)}/releases" - print(f"update check: {update_source}") + print(f"Update Check: {update_source}") if update_source == "None" or len(update_source) == 0: return {'FINISHED'} @@ -46,7 +46,7 @@ def execute(self, context): if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] - print(f"Found release: {tag}") + print(f"Found Release: {tag}") match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: version_num = tuple(map(int, match.groups())) @@ -71,13 +71,13 @@ def execute(self, context): class Updater(bpy.types.Operator): - """update to newer version if possible """ + """Update to Newer Version if Possible""" bl_idname = "render.cam_update_now" bl_label = "Update" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - print("update check") + print("Update Check") last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check today = date.today().toordinal() update_source = bpy.context.preferences.addons['cam'].preferences.update_source @@ -96,7 +96,7 @@ def execute(self, context): if len(release_list) > 0: release = release_list[0] tag = release["tag_name"] - print(f"Found release: {tag}") + print(f"Found Release: {tag}") match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)", tag) if match: version_num = tuple(map(int, match.groups())) @@ -105,7 +105,7 @@ def execute(self, context): bpy.ops.wm.save_userpref() if version_num > current_version: - print("Version is newer, downloading source") + print("Version Is Newer, Downloading Source") zip_url = release["zipball_url"] self.install_zip_from_url(zip_url) return {'FINISHED'} @@ -166,7 +166,7 @@ def install_zip_from_url(self, zip_url): class UpdateSourceOperator(bpy.types.Operator): bl_idname = "render.cam_set_update_source" - bl_label = "Set blendercam update source" + bl_label = "Set BlenderCAM Update Source" new_source: StringProperty( default='', diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index f1c4ad195..08a2aa4ba 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -557,7 +557,7 @@ def numpysave(a, iname): def numpytoimage(a, iname): t = time.time() - print('numpy to image - here') + print('Numpy to Image - Here') t = time.time() print(a.shape[0], a.shape[1]) foundimage = False @@ -625,7 +625,7 @@ def tonemap(i, exponent): def vert(column, row, z, XYscaling, Zscaling): - """ Create a single vert """ + """ Create a Single Vert """ return column * XYscaling, row * XYscaling, z * Zscaling @@ -641,7 +641,7 @@ def buildMesh(mesh_z, br): bpy.data.objects.remove(object) print("old basrelief removed") - print("Building mesh") + print("Building Mesh") numY = mesh_z.shape[1] numX = mesh_z.shape[0] print(numX, numY) @@ -687,24 +687,24 @@ def buildMesh(mesh_z, br): bpy.context.active_object.location = (float( br.justifyx)*br.widthmm/1000, float(br.justifyy)*br.heightmm/1000, float(br.justifyz)*br.thicknessmm/1000) - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) + print("Faces:" + str(len(ob.data.polygons))) + print("Vertices:" + str(len(ob.data.vertices))) if decimateRatio > 0.95: - print("skipping decimate ratio > 0.95") + print("Skipping Decimate Ratio > 0.95") else: m = ob.modifiers.new(name="Foo", type='DECIMATE') m.ratio = decimateRatio - print("decimating with ratio:"+str(decimateRatio)) + print("Decimating with Ratio:"+str(decimateRatio)) bpy.ops.object.modifier_apply(modifier=m.name) - print("decimated") - print("faces:" + str(len(ob.data.polygons))) - print("vertices:" + str(len(ob.data.vertices))) + print("Decimated") + print("Faces:" + str(len(ob.data.polygons))) + print("Vertices:" + str(len(ob.data.vertices))) # Switches to cycles render to CYCLES to render the sceen then switches it back to BLENDERCAM_RENDER for basRelief def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view_layer): - print("rendering scene") + print("Rendering Scene") scene = bpy.context.scene # make sure we're in object mode or else bad things happen if bpy.context.active_object: @@ -745,7 +745,7 @@ def renderScene(width, height, bit_diameter, passes_per_radius, make_nodes, view if our_viewer is not None: nodes.remove(our_viewer) bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' - print("done rendering") + print("Done Rendering") def problemAreas(br): @@ -869,7 +869,7 @@ def relief(br): print("Range:", nar.min(), nar.max()) if nar.min() - nar.max() == 0: raise ReliefError( - "Input image is blank - check you have the correct view layer or input image set.") + "Input Image Is Blank - Check You Have the Correct View Layer or Input Image Set.") gx = nar.copy() gx.fill(0) @@ -994,20 +994,20 @@ def filterwindow(x, y, cx=0, cy=0): # , curve=None): class BasReliefsettings(bpy.types.PropertyGroup): use_image_source: BoolProperty( - name="Use image source", + name="Use Image Source", description="", default=False, ) source_image_name: StringProperty( - name='Image source', + name='Image Source', description='image source', ) view_layer_name: StringProperty( - name='View layer source', + name='View Layer Source', description='Make a bas-relief from whatever is on this view layer', ) bit_diameter: FloatProperty( - name="Diameter of ball end in mm", + name="Diameter of Ball End in mm", description="Diameter of bit which will be used for carving", min=0.01, max=50.0, @@ -1015,7 +1015,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) pass_per_radius: IntProperty( - name="Passes per radius", + name="Passes per Radius", description="Amount of passes per radius\n(more passes, " "more mesh precision)", default=2, @@ -1023,13 +1023,13 @@ class BasReliefsettings(bpy.types.PropertyGroup): max=10, ) widthmm: IntProperty( - name="Desired width in mm", + name="Desired Width in mm", default=200, min=5, max=4000, ) heightmm: IntProperty( - name="Desired height in mm", + name="Desired Height in mm", default=150, min=5, max=4000, @@ -1070,7 +1070,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) depth_exponent: FloatProperty( - name="Depth exponent", + name="Depth Exponent", description="Initial depth map is taken to this power. Higher = " "sharper relief", min=0.5, @@ -1080,7 +1080,7 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) silhouette_threshold: FloatProperty( - name="Silhouette threshold", + name="Silhouette Threshold", description="Silhouette threshold", min=0.000001, max=1.0, @@ -1088,12 +1088,12 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) recover_silhouettes: BoolProperty( - name="Recover silhouettes", + name="Recover Silhouettes", description="", default=True, ) silhouette_scale: FloatProperty( - name="Silhouette scale", + name="Silhouette Scale", description="Silhouette scale", min=0.000001, max=5.0, @@ -1101,15 +1101,15 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) silhouette_exponent: IntProperty( - name="Silhouette square exponent", - description="If lower, true depht distances between objects will be " + name="Silhouette Square Exponent", + description="If lower, true depth distances between objects will be " "more visibe in the relief", default=3, min=0, max=5, ) attenuation: FloatProperty( - name="Gradient attenuation", + name="Gradient Attenuation", description="Gradient attenuation", min=0.000001, max=100.0, @@ -1117,39 +1117,39 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) min_gridsize: IntProperty( - name="Minimum grid size", + name="Minimum Grid Size", default=16, min=2, max=512, ) smooth_iterations: IntProperty( - name="Smooth iterations", + name="Smooth Iterations", default=1, min=1, max=64, ) vcycle_iterations: IntProperty( - name="V-cycle iterations", - description="set up higher for plananr constraint", + name="V-Cycle Iterations", + description="Set higher for planar constraint", default=2, min=1, max=128, ) linbcg_iterations: IntProperty( - name="Linbcg iterations", - description="set lower for flatter relief, and when using " + name="LINBCG Iterations", + description="Set lower for flatter relief, and when using " "planar constraint", default=5, min=1, max=64, ) use_planar: BoolProperty( - name="Use planar constraint", + name="Use Planar Constraint", description="", default=False, ) gradient_scaling_mask_use: BoolProperty( - name="Scale gradients with mask", + name="Scale Gradients with Mask", description="", default=False, ) @@ -1164,16 +1164,16 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) gradient_scaling_mask_name: StringProperty( - name='Scaling mask name', - description='mask name', + name='Scaling Mask Name', + description='Mask name', ) scale_down_before_use: BoolProperty( - name="Scale down image before processing", + name="Scale Down Image Before Processing", description="", default=False, ) scale_down_before: FloatProperty( - name="Image scale", + name="Image Scale", description="Image scale", min=0.025, max=1.0, @@ -1181,13 +1181,13 @@ class BasReliefsettings(bpy.types.PropertyGroup): precision=PRECISION, ) detail_enhancement_use: BoolProperty( - name="Enhance details ", - description="enhance details by frequency analysis", + name="Enhance Details", + description="Enhance details by frequency analysis", default=False, ) #detail_enhancement_freq=FloatProperty(name="frequency limit", description="Image scale", min=0.025, max=1.0, default=.5, precision=PRECISION) detail_enhancement_amount: FloatProperty( - name="amount", + name="Amount", description="Image scale", min=0.025, max=1.0, @@ -1196,15 +1196,15 @@ class BasReliefsettings(bpy.types.PropertyGroup): ) advanced: BoolProperty( - name="Advanced options", - description="show advanced options", + name="Advanced Options", + description="Show advanced options", default=True, ) class BASRELIEF_Panel(bpy.types.Panel): - """Bas relief panel""" - bl_label = "Bas relief" + """Bas Relief Panel""" + bl_label = "Bas Relief" bl_idname = "WORLD_PT_BASRELIEF" bl_space_type = "PROPERTIES" @@ -1229,7 +1229,7 @@ def draw(self, context): # if br: # cutter preset - layout.operator("scene.calculate_bas_relief", text="Calculate relief") + layout.operator("scene.calculate_bas_relief", text="Calculate Relief") layout.prop(br, 'advanced') layout.prop(br, 'use_image_source') if br.use_image_source: @@ -1238,7 +1238,7 @@ def draw(self, context): layout.prop_search(br, 'view_layer_name', bpy.context.scene, "view_layers") layout.prop(br, 'depth_exponent') - layout.label(text="Project parameters") + layout.label(text="Project Parameters") layout.prop(br, 'bit_diameter') layout.prop(br, 'pass_per_radius') layout.prop(br, 'widthmm') @@ -1293,9 +1293,9 @@ class ReliefError(Exception): class DoBasRelief(bpy.types.Operator): - """calculate Bas relief""" + """Calculate Bas Relief""" bl_idname = "scene.calculate_bas_relief" - bl_label = "calculate Bas relief" + bl_label = "Calculate Bas Relief" bl_options = {'REGISTER', 'UNDO'} processes = [] @@ -1322,9 +1322,9 @@ def execute(self, context): class ProblemAreas(bpy.types.Operator): - """find Bas relief Problem areas""" + """Find Bas Relief Problem Areas""" bl_idname = "scene.problemareas_bas_relief" - bl_label = "problem areas Bas relief" + bl_label = "Problem Areas Bas Relief" bl_options = {'REGISTER', 'UNDO'} processes = [] diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index f45f729bc..17a513ae7 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -58,7 +58,7 @@ def addBridge(x, y, rot, sizex, sizey): def addAutoBridges(o): - """attempt to add auto bridges as set of curves""" + """Attempt to Add Auto Bridges as Set of Curves""" utils.getOperationSources(o) bridgecollectionname = o.bridges_collection_name if bridgecollectionname == '' or bpy.data.collections.get(bridgecollectionname) is None: @@ -124,7 +124,7 @@ def getBridgesPoly(o): def useBridges(ch, o): - """this adds bridges to chunks, takes the bridge-objects collection and uses the curves inside it as bridges.""" + """This Adds Bridges to Chunks, Takes the Bridge-objects Collection and Uses the Curves Inside It as Bridges.""" bridgecollectionname = o.bridges_collection_name bridgecollection = bpy.data.collections[bridgecollectionname] if len(bridgecollection.objects) > 0: diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py index 16e0a85df..8730ac7bb 100644 --- a/scripts/addons/cam/cam_chunk.py +++ b/scripts/addons/cam/cam_chunk.py @@ -318,7 +318,7 @@ def reverse(self): self.rotations.reverse() def pop(self, index): - print("WARNING: Popping from chunk is slow", self, index) + print("WARNING: Popping from Chunk Is Slow", self, index) self.points = np.concatenate( (self.points[0:index], self.points[index + 1:]), axis=0 ) @@ -391,7 +391,7 @@ def extend( self.rotations[at_index:at_index] = rotations def clip_points(self, minx, maxx, miny, maxy): - """remove any points outside this range""" + """Remove Any Points Outside This Range""" included_values = (self.points[:, 0] >= minx) and ( (self.points[:, 0] <= maxx) and (self.points[:, 1] >= maxy) @@ -700,12 +700,12 @@ def leadContour(self, o): if self.parents: # if it is inside another parent perimeterDirection ^= 1 # toggle with a bitwise XOR - print("has parent") + print("Has Parent") if perimeterDirection == 1: - print("path direction is Clockwise") + print("Path Direction Is Clockwise") else: - print("path direction is counterclockwise") + print("Path Direction Is Counter Clockwise") iradius = o.lead_in oradius = o.lead_out start = self.points[0] @@ -797,7 +797,7 @@ def _dot_pr(v1, v2): return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2] def _applyVerticalLimit(v1, v2, cos_limit): - """test path segment on verticality threshold, for protect_vertical option""" + """Test Path Segment on Verticality Threshold, for Protect_vertical Option""" z = abs(v1[2] - v2[2]) if z > 0: # don't use this vector because dot product of 0,0,1 is trivially just v2[2] @@ -1147,7 +1147,7 @@ def meshFromCurveToChunk(object): lastvi = 0 vtotal = len(mesh.vertices) perc = 0 - progress("processing curve - START - Vertices: " + str(vtotal)) + progress("Processing Curve - START - Vertices: " + str(vtotal)) for vi in range(0, len(mesh.vertices) - 1): co = (mesh.vertices[vi].co + object.location).to_tuple() if not dk.isdisjoint([(vi, vi + 1)]) and d[(vi, vi + 1)] == 1: @@ -1173,7 +1173,7 @@ def meshFromCurveToChunk(object): chunks.append(chunk) chunk = camPathChunkBuilder() - progress("processing curve - FINISHED") + progress("Processing Curve - FINISHED") vi = len(mesh.vertices) - 1 chunk.points.append( @@ -1238,7 +1238,7 @@ def meshFromCurve(o, use_modifiers=False): bpy.ops.object.convert(target="CURVE", keep_original=False) elif co.type != "CURVE": # curve must be a curve... bpy.ops.object.delete() # delete temporary object - raise CamException("Source curve object must be of type CURVE") + raise CamException("Source Curve Object Must Be of Type Curve") co.data.dimensions = "3D" co.data.bevel_depth = 0 co.data.extrude = 0 @@ -1305,7 +1305,7 @@ def chunkToShapely(chunk): def chunksRefine(chunks, o): - """add extra points in between for chunks""" + """Add Extra Points in Between for Chunks""" for ch in chunks: # print('before',len(ch)) newchunk = [] @@ -1338,7 +1338,7 @@ def chunksRefine(chunks, o): def chunksRefineThreshold(chunks, distance, limitdistance): - """add extra points in between for chunks. For medial axis strategy only !""" + """Add Extra Points in Between for Chunks. for Medial Axis Strategy only!""" for ch in chunks: newchunk = [] v2 = Vector(ch.points[0]) diff --git a/scripts/addons/cam/cam_operation.py b/scripts/addons/cam/cam_operation.py index 60dcfbde9..b141a726c 100644 --- a/scripts/addons/cam/cam_operation.py +++ b/scripts/addons/cam/cam_operation.py @@ -57,17 +57,17 @@ class camOperation(PropertyGroup): update=updateRest, ) filename: StringProperty( - name="File name", + name="File Name", default="Operation", update=updateRest, ) auto_export: BoolProperty( - name="Auto export", - description="export files immediately after path calculation", + name="Auto Export", + description="Export files immediately after path calculation", default=True, ) remove_redundant_points: BoolProperty( - name="Symplify Gcode", + name="Simplify G-code", description="Remove redundant points sharing the same angle" " as the start vector", default=False, @@ -80,19 +80,19 @@ class camOperation(PropertyGroup): max=1000, ) hide_all_others: BoolProperty( - name="Hide all others", - description="Hide all other tool pathes except toolpath" - " assotiated with selected CAM operation", + name="Hide All Others", + description="Hide all other tool paths except toolpath" + " associated with selected CAM operation", default=False, ) parent_path_to_object: BoolProperty( - name="Parent path to object", + name="Parent Path to Object", description="Parent generated CAM path to source object", default=False, ) object_name: StringProperty( name='Object', - description='object handled by this operation', + description='Object handled by this operation', update=updateOperationValid, ) collection_name: StringProperty( @@ -101,26 +101,26 @@ class camOperation(PropertyGroup): update=updateOperationValid, ) curve_object: StringProperty( - name='Curve source', - description='curve which will be sampled along the 3d object', + name='Curve Source', + description='Curve which will be sampled along the 3D object', update=operationValid, ) curve_object1: StringProperty( - name='Curve target', - description='curve which will serve as attractor for the ' + name='Curve Target', + description='Curve which will serve as attractor for the ' 'cutter when the cutter follows the curve', update=operationValid, ) source_image_name: StringProperty( - name='image_source', + name='Image Source', description='image source', update=operationValid, ) geometry_source: EnumProperty( - name='Source of data', + name='Data Source', items=( - ('OBJECT', 'object', 'a'), - ('COLLECTION', 'Collection of objects', 'a'), + ('OBJECT', 'Object', 'a'), + ('COLLECTION', 'Collection of Objects', 'a'), ('IMAGE', 'Image', 'a') ), description='Geometry source', @@ -130,30 +130,30 @@ class camOperation(PropertyGroup): cutter_type: EnumProperty( name='Cutter', items=( - ('END', 'End', 'end - flat cutter'), - ('BALLNOSE', 'Ballnose', 'ballnose cutter'), - ('BULLNOSE', 'Bullnose', 'bullnose cutter ***placeholder **'), - ('VCARVE', 'V-carve', 'v carve cutter'), + ('END', 'End', 'End - Flat cutter'), + ('BALLNOSE', 'Ballnose', 'Ballnose cutter'), + ('BULLNOSE', 'Bullnose', 'Bullnose cutter ***placeholder **'), + ('VCARVE', 'V-carve', 'V-carve cutter'), ('BALLCONE', 'Ballcone', 'Ball with a Cone for Parallel - X'), ('CYLCONE', 'Cylinder cone', - 'Cylinder end with a Cone for Parallel - X'), + 'Cylinder End with a Cone for Parallel - X'), ('LASER', 'Laser', 'Laser cutter'), ('PLASMA', 'Plasma', 'Plasma cutter'), ('CUSTOM', 'Custom-EXPERIMENTAL', - 'modelled cutter - not well tested yet.') + 'Modelled cutter - not well tested yet.') ), description='Type of cutter used', default='END', update=updateZbufferImage, ) cutter_object_name: StringProperty( - name='Cutter object', - description='object used as custom cutter for this operation', + name='Cutter Object', + description='Object used as custom cutter for this operation', update=updateZbufferImage, ) machine_axes: EnumProperty( - name='Number of axes', + name='Number of Axes', items=( ('3', '3 axis', 'a'), ('4', '#4 axis - EXPERIMENTAL', 'a'), @@ -171,7 +171,7 @@ class camOperation(PropertyGroup): ) strategy4axis: EnumProperty( - name='4 axis Strategy', + name='4 Axis Strategy', items=( ('PARALLELR', 'Parallel around 1st rotary axis', 'Parallel lines around first rotary axis'), @@ -191,7 +191,7 @@ class camOperation(PropertyGroup): name='Strategy', items=( ('INDEXED', 'Indexed 3-axis', - 'all 3 axis strategies, just rotated by 4+5th axes'), + 'All 3 axis strategies, just rotated by 4+5th axes'), ), description='5 axis Strategy', default='INDEXED', @@ -199,7 +199,7 @@ class camOperation(PropertyGroup): ) rotary_axis_1: EnumProperty( - name='Rotary axis', + name='Rotary Axis', items=( ('X', 'X', ''), ('Y', 'Y', ''), @@ -210,7 +210,7 @@ class camOperation(PropertyGroup): update=updateStrategy, ) rotary_axis_2: EnumProperty( - name='Rotary axis 2', + name='Rotary Axis 2', items=( ('X', 'X', ''), ('Y', 'Y', ''), @@ -232,20 +232,20 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) inverse: BoolProperty( - name="Inverse milling", + name="Inverse Milling", description="Male to female model conversion", default=False, update=updateOffsetImage, ) array: BoolProperty( - name="Use array", + name="Use Array", description="Create a repetitive array for producing the " "same thing many times", default=False, update=updateRest, ) array_x_count: IntProperty( - name="X count", + name="X Count", description="X count", default=1, min=1, @@ -253,7 +253,7 @@ class camOperation(PropertyGroup): update=updateRest, ) array_y_count: IntProperty( - name="Y count", + name="Y Count", description="Y count", default=1, min=1, @@ -261,8 +261,8 @@ class camOperation(PropertyGroup): update=updateRest, ) array_x_distance: FloatProperty( - name="X distance", - description="distance between operation origins", + name="X Distance", + description="Distance between operation origins", min=0.00001, max=1.0, default=0.01, @@ -271,8 +271,8 @@ class camOperation(PropertyGroup): update=updateRest, ) array_y_distance: FloatProperty( - name="Y distance", - description="distance between operation origins", + name="Y Distance", + description="Distance between operation origins", min=0.00001, max=1.0, default=0.01, @@ -293,8 +293,8 @@ class camOperation(PropertyGroup): update=updateRest, ) pocketToCurve: BoolProperty( - name="Pocket to curve", - description="generates a curve instead of a path", + name="Pocket to Curve", + description="Generates a curve instead of a path", default=False, update=updateRest, ) @@ -304,14 +304,14 @@ class camOperation(PropertyGroup): items=( ('OUTSIDE', 'Outside', 'a'), ('INSIDE', 'Inside', 'a'), - ('ONLINE', 'On line', 'a') + ('ONLINE', 'On Line', 'a') ), description='Type of cutter used', default='OUTSIDE', update=updateRest, ) outlines_count: IntProperty( - name="Outlines count", + name="Outlines Count", description="Outlines count", default=1, min=1, @@ -326,7 +326,7 @@ class camOperation(PropertyGroup): ) # cutter cutter_id: IntProperty( - name="Tool number", + name="Tool Number", description="For machines which support tool change based on tool id", min=0, max=10000, @@ -334,7 +334,7 @@ class camOperation(PropertyGroup): update=updateRest, ) cutter_diameter: FloatProperty( - name="Cutter diameter", + name="Cutter Diameter", description="Cutter diameter = 2x cutter radius", min=0.000001, max=10, @@ -354,7 +354,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) cutter_length: FloatProperty( - name="#Cutter length", + name="#Cutter Length", description="#not supported#Cutter length", min=0.0, max=100.0, @@ -364,7 +364,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) cutter_flutes: IntProperty( - name="Cutter flutes", + name="Cutter Flutes", description="Cutter flutes", min=1, max=20, @@ -372,8 +372,8 @@ class camOperation(PropertyGroup): update=updateChipload, ) cutter_tip_angle: FloatProperty( - name="Cutter v-carve angle", - description="Cutter v-carve angle", + name="Cutter V-carve Angle", + description="Cutter V-carve angle", min=0.0, max=180.0, default=60.0, @@ -381,7 +381,7 @@ class camOperation(PropertyGroup): update=updateOffsetImage, ) ball_radius: FloatProperty( - name="Ball radius", + name="Ball Radius", description="Radius of", min=0.0, max=0.035, @@ -410,46 +410,46 @@ class camOperation(PropertyGroup): ) Laser_on: StringProperty( - name="Laser ON string", + name="Laser ON String", default="M68 E0 Q100", ) Laser_off: StringProperty( - name="Laser OFF string", + name="Laser OFF String", default="M68 E0 Q0", ) Laser_cmd: StringProperty( - name="Laser command", + name="Laser Command", default="M68 E0 Q", ) Laser_delay: FloatProperty( name="Laser ON Delay", - description="time after fast move to turn on laser and " + description="Time after fast move to turn on laser and " "let machine stabilize", default=0.2, ) Plasma_on: StringProperty( - name="Plasma ON string", + name="Plasma ON String", default="M03", ) Plasma_off: StringProperty( - name="Plasma OFF string", + name="Plasma OFF String", default="M05", ) Plasma_delay: FloatProperty( name="Plasma ON Delay", - description="time after fast move to turn on Plasma and " + description="Time after fast move to turn on Plasma and " "let machine stabilize", default=0.1, ) Plasma_dwell: FloatProperty( - name="Plasma dwell time", + name="Plasma Dwell Time", description="Time to dwell and warm up the torch", default=0.0, ) # steps dist_between_paths: FloatProperty( - name="Distance between toolpaths", + name="Distance Between Toolpaths", default=0.001, min=0.00001, max=32, @@ -458,7 +458,7 @@ class camOperation(PropertyGroup): update=updateRest, ) dist_along_paths: FloatProperty( - name="Distance along toolpaths", + name="Distance Along Toolpaths", default=0.0002, min=0.00001, max=32, @@ -467,7 +467,7 @@ class camOperation(PropertyGroup): update=updateRest, ) parallel_angle: FloatProperty( - name="Angle of paths", + name="Angle of Paths", default=0, min=-360, max=360, @@ -478,7 +478,7 @@ class camOperation(PropertyGroup): ) old_rotation_A: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="old value of Rotate A axis\nto specified angle", default=0, min=-360, @@ -490,7 +490,7 @@ class camOperation(PropertyGroup): ) old_rotation_B: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="old value of Rotate A axis\nto specified angle", default=0, min=-360, @@ -502,7 +502,7 @@ class camOperation(PropertyGroup): ) rotation_A: FloatProperty( - name="A axis angle", + name="A Axis Angle", description="Rotate A axis\nto specified angle", default=0, min=-360, @@ -513,7 +513,7 @@ class camOperation(PropertyGroup): update=updateRotation, ) enable_A: BoolProperty( - name="Enable A axis", + name="Enable A Axis", description="Rotate A axis", default=False, update=updateRotation, @@ -526,7 +526,7 @@ class camOperation(PropertyGroup): ) rotation_B: FloatProperty( - name="B axis angle", + name="B Axis Angle", description="Rotate B axis\nto specified angle", default=0, min=-360, @@ -537,7 +537,7 @@ class camOperation(PropertyGroup): update=updateRotation, ) enable_B: BoolProperty( - name="Enable B axis", + name="Enable B Axis", description="Rotate B axis", default=False, update=updateRotation, @@ -545,7 +545,7 @@ class camOperation(PropertyGroup): # carve only carve_depth: FloatProperty( - name="Carve depth", + name="Carve Depth", default=0.001, min=-.100, max=32, @@ -556,11 +556,11 @@ class camOperation(PropertyGroup): # drill only drill_type: EnumProperty( - name='Holes on', + name='Holes On', items=( - ('MIDDLE_SYMETRIC', 'Middle of symetric curves', 'a'), - ('MIDDLE_ALL', 'Middle of all curve parts', 'a'), - ('ALL_POINTS', 'All points in curve', 'a') + ('MIDDLE_SYMETRIC', 'Middle of Symmetric Curves', 'a'), + ('MIDDLE_ALL', 'Middle of All Curve Parts', 'a'), + ('ALL_POINTS', 'All Points in Curve', 'a') ), description='Strategy to detect holes to drill', default='MIDDLE_SYMETRIC', @@ -568,7 +568,7 @@ class camOperation(PropertyGroup): ) # waterline only slice_detail: FloatProperty( - name="Distance betwen slices", + name="Distance Between Slices", default=0.001, min=0.00001, max=32, @@ -577,13 +577,13 @@ class camOperation(PropertyGroup): update=updateRest, ) waterline_fill: BoolProperty( - name="Fill areas between slices", + name="Fill Areas Between Slices", description="Fill areas between slices in waterline mode", default=True, update=updateRest, ) waterline_project: BoolProperty( - name="Project paths - not recomended", + name="Project Paths - Not Recomended", description="Project paths in areas between slices", default=True, update=updateRest, @@ -607,8 +607,8 @@ class camOperation(PropertyGroup): update=updateRest, ) lead_in: FloatProperty( - name="Lead in radius", - description="Lead out radius for torch or laser to turn off", + name="Lead-in Radius", + description="Lead in radius for torch or laser to turn off", min=0.00, max=1, default=0.0, @@ -616,7 +616,7 @@ class camOperation(PropertyGroup): unit="LENGTH", ) lead_out: FloatProperty( - name="Lead out radius", + name="Lead-out Radius", description="Lead out radius for torch or laser to turn off", min=0.00, max=1, @@ -625,7 +625,7 @@ class camOperation(PropertyGroup): unit="LENGTH", ) profile_start: IntProperty( - name="Start point", + name="Start Point", description="Start point offset", min=0, default=0, @@ -635,7 +635,7 @@ class camOperation(PropertyGroup): # helix_angle: FloatProperty(name="Helix ramp angle", default=3*pi/180, min=0.00001, max=pi*0.4999,precision=1, subtype="ANGLE" , unit="ROTATION" , update = updateRest) minz: FloatProperty( - name="Operation depth end", + name="Operation Depth End", default=-0.01, min=-3, max=3, @@ -645,7 +645,7 @@ class camOperation(PropertyGroup): ) minz_from: EnumProperty( - name='Set max depth from', + name='Max Depth From', description='Set maximum operation depth', items=( ('OBJECT', 'Object', 'Set max operation depth from Object'), @@ -657,10 +657,10 @@ class camOperation(PropertyGroup): ) start_type: EnumProperty( - name='Start type', + name='Start Type', items=( ('ZLEVEL', 'Z level', 'Starts on a given Z level'), - ('OPERATIONRESULT', 'Rest milling', + ('OPERATIONRESULT', 'Rest Milling', 'For rest milling, operations have to be ' 'put in chain for this to work well.'), ), @@ -670,7 +670,7 @@ class camOperation(PropertyGroup): ) maxz: FloatProperty( - name="Operation depth start", + name="Operation Depth Start", description='operation starting depth', default=0, min=-3, @@ -681,7 +681,7 @@ class camOperation(PropertyGroup): ) # EXPERIMENTAL first_down: BoolProperty( - name="First down", + name="First Down", description="First go down on a contour, then go to the next one", default=False, update=update_operation, @@ -692,7 +692,7 @@ class camOperation(PropertyGroup): #################################################### source_image_scale_z: FloatProperty( - name="Image source depth scale", + name="Image Source Depth Scale", default=0.01, min=-1, max=1, @@ -701,7 +701,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_size_x: FloatProperty( - name="Image source x size", + name="Image Source X Size", default=0.1, min=-10, max=10, @@ -710,7 +710,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_offset: FloatVectorProperty( - name='Image offset', + name='Image Offset', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -719,7 +719,7 @@ class camOperation(PropertyGroup): ) source_image_crop: BoolProperty( - name="Crop source image", + name="Crop Source Image", description="Crop source image - the position of the sub-rectangle " "is relative to the whole image, so it can be used for e.g. " "finishing just a part of an image", @@ -727,7 +727,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_start_x: FloatProperty( - name='crop start x', + name='Crop Start X', default=0, min=0, max=100, @@ -736,7 +736,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_start_y: FloatProperty( - name='crop start y', + name='Crop Start Y', default=0, min=0, max=100, @@ -745,7 +745,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_end_x: FloatProperty( - name='crop end x', + name='Crop End X', default=100, min=0, max=100, @@ -754,7 +754,7 @@ class camOperation(PropertyGroup): update=updateZbufferImage, ) source_image_crop_end_y: FloatProperty( - name='crop end y', + name='Crop End Y', default=100, min=0, max=100, @@ -770,13 +770,13 @@ class camOperation(PropertyGroup): ambient_behaviour: EnumProperty( name='Ambient', items=(('ALL', 'All', 'a'), ('AROUND', 'Around', 'a')), - description='handling ambient surfaces', + description='Handling ambient surfaces', default='ALL', update=updateZbufferImage, ) ambient_radius: FloatProperty( - name="Ambient radius", + name="Ambient Radius", description="Radius around the part which will be milled if " "ambient is set to Around", min=0.0, @@ -788,21 +788,21 @@ class camOperation(PropertyGroup): ) # ambient_cutter = EnumProperty(name='Borders',items=(('EXTRAFORCUTTER', 'Extra for cutter', "Extra space for cutter is cut around the segment"),('ONBORDER', "Cutter on edge", "Cutter goes exactly on edge of ambient with it's middle") ,('INSIDE', "Inside segment", 'Cutter stays within segment') ),description='handling of ambient and cutter size',default='INSIDE') use_limit_curve: BoolProperty( - name="Use limit curve", + name="Use Limit Curve", description="A curve limits the operation area", default=False, update=updateRest, ) ambient_cutter_restrict: BoolProperty( - name="Cutter stays in ambient limits", + name="Cutter Stays in Ambient Limits", description="Cutter doesn't get out from ambient limits otherwise " "goes on the border exactly", default=True, update=updateRest, ) # restricts cutter inside ambient only limit_curve: StringProperty( - name='Limit curve', - description='curve used to limit the area of the operation', + name='Limit Curve', + description='Curve used to limit the area of the operation', update=updateRest, ) @@ -818,7 +818,7 @@ class camOperation(PropertyGroup): update=updateChipload, ) plunge_feedrate: FloatProperty( - name="Plunge speed ", + name="Plunge Speed", description="% of feedrate", min=0.1, max=100.0, @@ -828,8 +828,8 @@ class camOperation(PropertyGroup): update=updateRest, ) plunge_angle: FloatProperty( - name="Plunge angle", - description="What angle is allready considered to plunge", + name="Plunge Angle", + description="What angle is already considered to plunge", default=pi / 6, min=0, max=pi * 0.5, @@ -839,7 +839,7 @@ class camOperation(PropertyGroup): update=updateRest, ) spindle_rpm: FloatProperty( - name="Spindle rpm", + name="Spindle RPM", description="Spindle speed ", min=0, max=60000, @@ -850,21 +850,21 @@ class camOperation(PropertyGroup): # optimization and performance do_simulation_feedrate: BoolProperty( - name="Adjust feedrates with simulation EXPERIMENTAL", + name="Adjust Feedrates with Simulation EXPERIMENTAL", description="Adjust feedrates with simulation", default=False, update=updateRest, ) dont_merge: BoolProperty( - name="Dont merge outlines when cutting", + name="Don't Merge Outlines when Cutting", description="this is usefull when you want to cut around everything", default=False, update=updateRest, ) pencil_threshold: FloatProperty( - name="Pencil threshold", + name="Pencil Threshold", default=0.00002, min=0.00000001, max=1, @@ -874,7 +874,7 @@ class camOperation(PropertyGroup): ) crazy_threshold1: FloatProperty( - name="min engagement", + name="Min Engagement", default=0.02, min=0.00000001, max=100, @@ -882,7 +882,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold5: FloatProperty( - name="optimal engagement", + name="Optimal Engagement", default=0.3, min=0.00000001, max=100, @@ -890,7 +890,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold2: FloatProperty( - name="max engagement", + name="Max Engagement", default=0.5, min=0.00000001, max=100, @@ -898,7 +898,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold3: FloatProperty( - name="max angle", + name="Max Angle", default=2, min=0.00000001, max=100, @@ -906,7 +906,7 @@ class camOperation(PropertyGroup): update=updateRest, ) crazy_threshold4: FloatProperty( - name="test angle step", + name="Test Angle Step", default=0.05, min=0.00000001, max=100, @@ -915,8 +915,8 @@ class camOperation(PropertyGroup): ) # Add pocket operation to medial axis add_pocket_for_medial: BoolProperty( - name="Add pocket operation", - description="clean unremoved material after medial axis", + name="Add Pocket Operation", + description="Clean unremoved material after medial axis", default=True, update=updateRest, ) @@ -930,7 +930,7 @@ class camOperation(PropertyGroup): ) #### medial_axis_threshold: FloatProperty( - name="Long vector threshold", + name="Long Vector Threshold", default=0.001, min=0.00000001, max=100, @@ -939,7 +939,7 @@ class camOperation(PropertyGroup): update=updateRest, ) medial_axis_subdivision: FloatProperty( - name="Fine subdivision", + name="Fine Subdivision", default=0.0002, min=0.00000001, max=100, @@ -951,20 +951,20 @@ class camOperation(PropertyGroup): # bridges use_bridges: BoolProperty( - name="Use bridges", - description="use bridges in cutout", + name="Use Bridges / Tabs", + description="Use bridges in cutout", default=False, update=updateBridges, ) bridges_width: FloatProperty( - name='width of bridges', + name='Bridge / Tab Width', default=0.002, unit='LENGTH', precision=constants.PRECISION, update=updateBridges, ) bridges_height: FloatProperty( - name='height of bridges', + name='Bridge / Tab Height', description="Height from the bottom of the cutting operation", default=0.0005, unit='LENGTH', @@ -972,13 +972,13 @@ class camOperation(PropertyGroup): update=updateBridges, ) bridges_collection_name: StringProperty( - name='Bridges Collection', + name='Bridges / Tabs Collection', description='Collection of curves used as bridges', update=operationValid, ) use_bridge_modifiers: BoolProperty( - name="use bridge modifiers", - description="include bridge curve modifiers using render level when " + name="Use Bridge / Tab Modifiers", + description="Include bridge curve modifiers using render level when " "calculating operation, does not effect original bridge data", default=True, update=updateBridges, @@ -998,8 +998,8 @@ class camOperation(PropertyGroup): # bridges_max_distance = FloatProperty(name = 'Maximum distance between bridges', default=0.08, unit='LENGTH', precision=constants.PRECISION, update = updateBridges) use_modifiers: BoolProperty( - name="use mesh modifiers", - description="include mesh modifiers using render level when " + name="Use Mesh Modifiers", + description="Include mesh modifiers using render level when " "calculating operation, does not effect original mesh", default=True, update=operationValid, @@ -1013,14 +1013,14 @@ class camOperation(PropertyGroup): # MATERIAL SETTINGS min: FloatVectorProperty( - name='Operation minimum', + name='Operation Minimum', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, subtype="XYZ", ) max: FloatVectorProperty( - name='Operation maximum', + name='Operation Maximum', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -1029,54 +1029,54 @@ class camOperation(PropertyGroup): # g-code options for operation output_header: BoolProperty( - name="output g-code header", - description="output user defined g-code command header" + name="Output G-code Header", + description="Output user defined G-code command header" " at start of operation", default=False, ) gcode_header: StringProperty( - name="g-code header", - description="g-code commands at start of operation." + name="G-code Header", + description="G-code commands at start of operation." " Use ; for line breaks", default="G53 G0", ) enable_dust: BoolProperty( - name="Dust collector", - description="output user defined g-code command header" + name="Dust Collector", + description="Output user defined g-code command header" " at start of operation", default=False, ) gcode_start_dust_cmd: StringProperty( - name="Start dust collector", - description="commands to start dust collection. Use ; for line breaks", + name="Start Dust Collector", + description="Commands to start dust collection. Use ; for line breaks", default="M100", ) gcode_stop_dust_cmd: StringProperty( - name="Stop dust collector", - description="command to stop dust collection. Use ; for line breaks", + name="Stop Dust Collector", + description="Command to stop dust collection. Use ; for line breaks", default="M101", ) enable_hold: BoolProperty( - name="Hold down", - description="output hold down command at start of operation", + name="Hold Down", + description="Output hold down command at start of operation", default=False, ) gcode_start_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." + name="G-code Header", + description="G-code commands at start of operation." " Use ; for line breaks", default="M102", ) gcode_stop_hold_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", + name="G-code Header", + description="G-code commands at end operation. Use ; for line breaks", default="M103", ) @@ -1087,28 +1087,27 @@ class camOperation(PropertyGroup): ) gcode_start_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at start of operation." - " Use ; for line breaks", + name="Start Mist", + description="Command to start mist. Use ; for line breaks", default="M104", ) gcode_stop_mist_cmd: StringProperty( - name="g-code header", - description="g-code commands at end operation. Use ; for line breaks", + name="Stop Mist", + description="Command to stop mist. Use ; for line breaks", default="M105", ) output_trailer: BoolProperty( - name="output g-code trailer", - description="output user defined g-code command trailer" + name="Output G-code Trailer", + description="Output user defined g-code command trailer" " at end of operation", default=False, ) gcode_trailer: StringProperty( - name="g-code trailer", - description="g-code commands at end of operation." + name="G-code Trailer", + description="G-code commands at end of operation." " Use ; for line breaks", default="M02", ) @@ -1126,40 +1125,40 @@ class camOperation(PropertyGroup): borderwidth = 50 object = None path_object_name: StringProperty( - name='Path object', - description='actual cnc path' + name='Path Object', + description='Actual CNC path' ) # update and tags and related changed: BoolProperty( - name="True if any of the operation settings has changed", - description="mark for update", + name="True if any of the Operation Settings has Changed", + description="Mark for update", default=False, ) update_zbufferimage_tag: BoolProperty( - name="mark zbuffer image for update", - description="mark for update", + name="Mark Z-Buffer Image for Update", + description="Mark for update", default=True, ) update_offsetimage_tag: BoolProperty( - name="mark offset image for update", - description="mark for update", + name="Mark Offset Image for Update", + description="Mark for update", default=True, ) update_silhouete_tag: BoolProperty( - name="mark silhouete image for update", - description="mark for update", + name="Mark Silhouette Image for Update", + description="Mark for update", default=True, ) update_ambient_tag: BoolProperty( - name="mark ambient polygon for update", - description="mark for update", + name="Mark Ambient Polygon for Update", + description="Mark for update", default=True, ) update_bullet_collision_tag: BoolProperty( - name="mark bullet collisionworld for update", - description="mark for update", + name="Mark Bullet Collision World for Update", + description="Mark for update", default=True, ) @@ -1169,24 +1168,24 @@ class camOperation(PropertyGroup): default=True, ) changedata: StringProperty( - name='changedata', + name='Changedata', description='change data for checking if stuff changed.', ) # process related data computing: BoolProperty( - name="Computing right now", + name="Computing Right Now", description="", default=False, ) pid: IntProperty( - name="process id", + name="Process Id", description="Background process id", default=-1, ) outtext: StringProperty( - name='outtext', + name='Outtext', description='outtext', default='', ) diff --git a/scripts/addons/cam/chain.py b/scripts/addons/cam/chain.py index 44b60b213..bd793ad9f 100644 --- a/scripts/addons/cam/chain.py +++ b/scripts/addons/cam/chain.py @@ -10,7 +10,7 @@ # this type is defined just to hold reference to operations for chains class opReference(PropertyGroup): name: StringProperty( - name="Operation name", + name="Operation Name", default="Operation", ) computing = False # for UiList display @@ -19,13 +19,13 @@ class opReference(PropertyGroup): # chain is just a set of operations which get connected on export into 1 file. class camChain(PropertyGroup): index: IntProperty( - name="index", - description="index in the hard-defined camChains", + name="Index", + description="Index in the hard-defined camChains", default=-1, ) active_operation: IntProperty( - name="active operation", - description="active operation in chain", + name="Active Operation", + description="Active operation in chain", default=-1, ) name: StringProperty( @@ -33,7 +33,7 @@ class camChain(PropertyGroup): default="Chain", ) filename: StringProperty( - name="File name", + name="File Name", default="Chain", ) # filename of valid: BoolProperty( @@ -42,7 +42,7 @@ class camChain(PropertyGroup): default=True, ) computing: BoolProperty( - name="Computing right now", + name="Computing Right Now", description="", default=False, ) diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 8c7200fa9..074fcfe19 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -45,8 +45,8 @@ def getCutterBullet(o): - """cutter for rigidbody simulation collisions - note that everything is 100x bigger for simulation precision.""" + """Cutter for Rigidbody Simulation Collisions + Note that Everything Is 100x Bigger for Simulation Precision.""" s = bpy.context.scene if s.objects.get('cutter') is not None: @@ -176,7 +176,7 @@ def getCutterBullet(o): def subdivideLongEdges(ob, threshold): - print('subdividing long edges') + print('Subdividing Long Edges') m = ob.data scale = (ob.scale.x + ob.scale.y + ob.scale.z) / 3 subdivides = [] @@ -220,8 +220,8 @@ def subdivideLongEdges(ob, threshold): # def prepareBulletCollision(o): - """prepares all objects needed for sampling with bullet collision""" - progress('preparing collisions') + """Prepares All Objects Needed for Sampling with Bullet Collision""" + progress('Preparing Collisions') print(o.name) active_collection = bpy.context.view_layer.active_layer_collection.collection @@ -332,7 +332,7 @@ def getSampleBullet(cutter, x, y, radius, startz, endz): def getSampleBulletNAxis(cutter, startpoint, endpoint, rotation, cutter_compensation): - """fully 3d collision test for NAxis milling""" + """Fully 3D Collision Test for N-Axis Milling""" cutterVec = Vector((0, 0, 1)) * cutter_compensation # cutter compensation vector - cutter physics object has center in the middle, while cam needs the tip position. cutterVec.rotate(Euler(rotation)) diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index 0387caadc..805709276 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -49,13 +49,13 @@ class CamCurveHatch(Operator): - """perform hatch operation on single or multiple curves""" # by Alain Pelletier September 2021 + """Perform Hatch Operation on Single or Multiple Curves""" # by Alain Pelletier September 2021 bl_idname = "object.curve_hatch" - bl_label = "CrossHatch curve" + bl_label = "CrossHatch Curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} angle: FloatProperty( - name="angle", + name="Angle", default=0, min=-pi/2, max=pi/2, @@ -63,7 +63,7 @@ class CamCurveHatch(Operator): subtype="ANGLE", ) distance: FloatProperty( - name="spacing", + name="Spacing", default=0.015, min=0, max=3.0, @@ -87,7 +87,7 @@ class CamCurveHatch(Operator): unit="LENGTH", ) amount: IntProperty( - name="amount", + name="Amount", default=10, min=1, max=10000, @@ -101,14 +101,14 @@ class CamCurveHatch(Operator): default=False, ) contour_separate: BoolProperty( - name="Contour separate", + name="Contour Separate", default=False, ) pocket_type: EnumProperty( - name='Type pocket', + name='Type Pocket', items=( - ('BOUNDS', 'makes a bounds rectangle', 'makes a bounding square'), - ('POCKET', 'Pocket', 'makes a pocket inside a closed loop') + ('BOUNDS', 'Makes a bounds rectangle', 'Makes a bounding square'), + ('POCKET', 'Pocket', 'Makes a pocket inside a closed loop') ), description='Type of pocket', default='BOUNDS', @@ -217,9 +217,9 @@ def execute(self, context): class CamCurvePlate(Operator): - """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 + """Perform Generates Rounded Plate with Mounting Holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_plate" - bl_label = "Sign plate" + bl_label = "Sign Plate" bl_options = {'REGISTER', 'UNDO', 'PRESET'} radius: FloatProperty( @@ -231,7 +231,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) width: FloatProperty( - name="Width of plate", + name="Width of Plate", default=0.3048, min=0, max=3.0, @@ -239,7 +239,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of plate", + name="Height of Plate", default=0.457, min=0, max=3.0, @@ -247,7 +247,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_diameter: FloatProperty( - name="Hole diameter", + name="Hole Diameter", default=0.01, min=0, max=3.0, @@ -263,7 +263,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_vdist: FloatProperty( - name="Hole Vert distance", + name="Hole Vert Distance", default=0.400, min=0, max=3.0, @@ -271,7 +271,7 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_hdist: FloatProperty( - name="Hole horiz distance", + name="Hole Horiz Distance", default=0, min=0, max=3.0, @@ -279,19 +279,19 @@ class CamCurvePlate(Operator): unit="LENGTH", ) hole_hamount: IntProperty( - name="Hole horiz amount", + name="Hole Horiz Amount", default=1, min=0, max=50, ) resolution: IntProperty( - name="Spline resolution", + name="Spline Resolution", default=50, min=3, max=150, ) plate_type: EnumProperty( - name='Type plate', + name='Type Plate', items=( ('ROUNDED', 'Rounded corner', 'Makes a rounded corner plate'), ('COVE', 'Cove corner', @@ -492,13 +492,13 @@ def execute(self, context): class CamCurveFlatCone(Operator): - """perform generates rounded plate with mounting holes""" # by Alain Pelletier Sept 2021 + """Perform Generates Rounded Plate with Mounting Holes""" # by Alain Pelletier Sept 2021 bl_idname = "object.curve_flat_cone" - bl_label = "Cone flat calculator" + bl_label = "Cone Flat Calculator" bl_options = {'REGISTER', 'UNDO', 'PRESET'} small_d: FloatProperty( - name="small diameter", + name="Small Diameter", default=.025, min=0, max=0.1, @@ -506,7 +506,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) large_d: FloatProperty( - name="large diameter", + name="Large Diameter", default=0.3048, min=0, max=3.0, @@ -514,7 +514,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of cone", + name="Height of Cone", default=0.457, min=0, max=3.0, @@ -522,7 +522,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) tab: FloatProperty( - name="tab witdh", + name="Tab Witdh", default=0.01, min=0, max=0.100, @@ -530,7 +530,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) intake: FloatProperty( - name="intake diameter", + name="Intake Diameter", default=0, min=0, max=0.200, @@ -538,7 +538,7 @@ class CamCurveFlatCone(Operator): unit="LENGTH", ) intake_skew: FloatProperty( - name="intake_skew", + name="Intake Skew", default=1, min=0.1, max=4, @@ -589,13 +589,13 @@ def execute(self, context): class CamCurveMortise(Operator): - """Generates mortise along a curve""" # by Alain Pelletier December 2021 + """Generates Mortise Along a Curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_mortise" bl_label = "Mortise" bl_options = {'REGISTER', 'UNDO', 'PRESET'} finger_size: BoolProperty( - name="kurf bending only", + name="Kurf Bending only", default=False, ) finger_size: FloatProperty( @@ -615,7 +615,7 @@ class CamCurveMortise(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -623,28 +623,28 @@ class CamCurveMortise(Operator): unit="LENGTH", ) plate_thickness: FloatProperty( - name="Drawer plate thickness", + name="Drawer Plate Thickness", default=0.00477, min=0.001, max=3.0, unit="LENGTH", ) side_height: FloatProperty( - name="side height", + name="Side Height", default=0.05, min=0.001, max=3.0, unit="LENGTH", ) flex_pocket: FloatProperty( - name="Flex pocket", + name="Flex Pocket", default=0.004, min=0.000, max=1.0, unit="LENGTH", ) top_bottom: BoolProperty( - name="Side Top & bottom fingers", + name="Side Top & Bottom Fingers", default=True, ) opencurve: BoolProperty( @@ -652,7 +652,7 @@ class CamCurveMortise(Operator): default=False, ) adaptive: FloatProperty( - name="Adaptive angle threshold", + name="Adaptive Angle Threshold", default=0.0, min=0.000, max=2, @@ -660,7 +660,7 @@ class CamCurveMortise(Operator): unit="ROTATION", ) double_adaptive: BoolProperty( - name="Double adaptive Pockets", + name="Double Adaptive Pockets", default=False, ) @@ -732,7 +732,7 @@ def execute(self, context): class CamCurveInterlock(Operator): - """Generates interlock along a curve""" # by Alain Pelletier December 2021 + """Generates Interlock Along a Curve""" # by Alain Pelletier December 2021 bl_idname = "object.curve_interlock" bl_label = "Interlock" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -746,7 +746,7 @@ class CamCurveInterlock(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -754,7 +754,7 @@ class CamCurveInterlock(Operator): unit="LENGTH", ) plate_thickness: FloatProperty( - name="Plate thickness", + name="Plate Thickness", default=0.00477, min=0.001, max=3.0, @@ -765,11 +765,11 @@ class CamCurveInterlock(Operator): default=False, ) interlock_type: EnumProperty( - name='Type of interlock', + name='Type of Interlock', items=( - ('TWIST', 'Twist', 'Iterlock requires 1/4 turn twist'), + ('TWIST', 'Twist', 'Interlock requires 1/4 turn twist'), ('GROOVE', 'Groove', 'Simple sliding groove'), - ('PUZZLE', 'Puzzle interlock', 'puzzle good for flat joints') + ('PUZZLE', 'Puzzle Interlock', 'Puzzle good for flat joints') ), description='Type of interlock', default='GROOVE', @@ -781,7 +781,7 @@ class CamCurveInterlock(Operator): max=100, ) tangent_angle: FloatProperty( - name="Tangent deviation", + name="Tangent Deviation", default=0.0, min=0.000, max=2, @@ -789,7 +789,7 @@ class CamCurveInterlock(Operator): unit="ROTATION", ) fixed_angle: FloatProperty( - name="fixed angle", + name="Fixed Angle", default=0.0, min=0.000, max=2, @@ -852,7 +852,7 @@ def execute(self, context): class CamCurveDrawer(Operator): - """Generates drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator + """Generates Drawers""" # by Alain Pelletier December 2021 inspired by The Drawinator bl_idname = "object.curve_drawer" bl_label = "Drawer" bl_options = {'REGISTER', 'UNDO', 'PRESET'} @@ -866,7 +866,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) width: FloatProperty( - name="Width of Drawer", + name="Drawer Width", default=0.125, min=0, max=3.0, @@ -874,7 +874,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) height: FloatProperty( - name="Height of drawer", + name="Drawer Height", default=0.07, min=0, max=3.0, @@ -890,7 +890,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.000045, min=0, max=0.003, @@ -898,7 +898,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) finger_inset: FloatProperty( - name="Finger inset", + name="Finger Inset", default=0.0, min=0.0, max=0.01, @@ -906,7 +906,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_plate_thickness: FloatProperty( - name="Drawer plate thickness", + name="Drawer Plate Thickness", default=0.00477, min=0.001, max=3.0, @@ -914,7 +914,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_hole_diameter: FloatProperty( - name="Drawer hole diameter", + name="Drawer Hole Diameter", default=0.02, min=0.00001, max=0.5, @@ -922,7 +922,7 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) drawer_hole_offset: FloatProperty( - name="Drawer hole offset", + name="Drawer Hole Offset", default=0.0, min=-0.5, max=0.5, @@ -930,11 +930,11 @@ class CamCurveDrawer(Operator): unit="LENGTH", ) overcut: BoolProperty( - name="Add overcut", + name="Add Overcut", default=False, ) overcut_diameter: FloatProperty( - name="Overcut toool Diameter", + name="Overcut Tool Diameter", default=0.003175, min=-0.001, max=0.5, @@ -1066,13 +1066,13 @@ def execute(self, context): class CamCurvePuzzle(Operator): - """Generates Puzzle joints and interlocks""" # by Alain Pelletier December 2021 + """Generates Puzzle Joints and Interlocks""" # by Alain Pelletier December 2021 bl_idname = "object.curve_puzzle" - bl_label = "Puzzle joints" + bl_label = "Puzzle Joints" bl_options = {'REGISTER', 'UNDO', 'PRESET'} diameter: FloatProperty( - name="tool diameter", + name="Tool Diameter", default=0.003175, min=0.001, max=3.0, @@ -1080,7 +1080,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) finger_tolerance: FloatProperty( - name="Finger play room", + name="Finger Play Room", default=0.00005, min=0, max=0.003, @@ -1094,7 +1094,7 @@ class CamCurvePuzzle(Operator): max=100, ) stem_size: IntProperty( - name="size of the stem", + name="Size of the Stem", default=2, min=1, max=200, @@ -1108,7 +1108,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) height: FloatProperty( - name="height or thickness", + name="Height or Thickness", default=0.025, min=0.005, max=3.0, @@ -1117,7 +1117,7 @@ class CamCurvePuzzle(Operator): ) angle: FloatProperty( - name="angle A", + name="Angle A", default=pi/4, min=-10, max=10, @@ -1125,7 +1125,7 @@ class CamCurvePuzzle(Operator): unit="ROTATION", ) angleb: FloatProperty( - name="angle B", + name="Angle B", default=pi/4, min=-10, max=10, @@ -1143,7 +1143,7 @@ class CamCurvePuzzle(Operator): ) interlock_type: EnumProperty( - name='Type of shape', + name='Type of Shape', items=( ('JOINT', 'Joint', 'Puzzle Joint interlock'), ('BAR', 'Bar', 'Bar interlock'), @@ -1161,7 +1161,7 @@ class CamCurvePuzzle(Operator): default='CURVET', ) gender: EnumProperty( - name='Type gender', + name='Type Gender', items=( ('MF', 'Male-Receptacle', 'Male and receptacle'), ('F', 'Receptacle only', 'Receptacle'), @@ -1171,7 +1171,7 @@ class CamCurvePuzzle(Operator): default='MF', ) base_gender: EnumProperty( - name='Base gender', + name='Base Gender', items=( ('MF', 'Male - Receptacle', 'Male - Receptacle'), ('F', 'Receptacle', 'Receptacle'), @@ -1181,7 +1181,7 @@ class CamCurvePuzzle(Operator): default='M', ) multiangle_gender: EnumProperty( - name='Multiangle gender', + name='Multiangle Gender', items=( ('MMF', 'Male Male Receptacle', 'M M F'), ('MFF', 'Male Receptacle Receptacle', 'M F F') @@ -1208,38 +1208,38 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) twist_percent: FloatProperty( - name="Twist neck", + name="Twist Neck", default=0.3, min=0.1, max=0.9, precision=4, ) twist_keep: BoolProperty( - name="keep Twist holes", + name="Keep Twist Holes", default=False, ) twist_line: BoolProperty( - name="Add Twist to bar", + name="Add Twist to Bar", default=False, ) twist_line_amount: IntProperty( - name="amount of separators", + name="Amount of Separators", default=2, min=1, max=600, ) twist_separator: BoolProperty( - name="Add Twist separator", + name="Add Twist Separator", default=False, ) twist_separator_amount: IntProperty( - name="amount of separators", + name="Amount of Separators", default=2, min=2, max=600, ) twist_separator_spacing: FloatProperty( - name="Separator spacing", + name="Separator Spacing", default=0.025, min=-0.004, max=1.0, @@ -1247,7 +1247,7 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) twist_separator_edge_distance: FloatProperty( - name="Separator edge distance", + name="Separator Edge Distance", default=0.01, min=0.0005, max=0.1, @@ -1255,29 +1255,29 @@ class CamCurvePuzzle(Operator): unit="LENGTH", ) tile_x_amount: IntProperty( - name="amount of x fingers", + name="Amount of X Fingers", default=2, min=1, max=600, ) tile_y_amount: IntProperty( - name="amount of y fingers", + name="Amount of Y Fingers", default=2, min=1, max=600, ) interlock_amount: IntProperty( - name="Interlock amount on curve", + name="Interlock Amount on Curve", default=2, min=0, max=200, ) overcut: BoolProperty( - name="Add overcut", + name="Add Overcut", default=False, ) overcut_diameter: FloatProperty( - name="Overcut toool Diameter", + name="Overcut Tool Diameter", default=0.003175, min=-0.001, max=0.5, @@ -1459,7 +1459,7 @@ def execute(self, context): class CamCurveGear(Operator): - """Generates involute Gears // version 1.1 by Leemon Baird, 2011, Leemon@Leemon.com + """Generates Involute Gears // version 1.1 by Leemon Baird, 2011, Leemon@Leemon.com http://www.thingiverse.com/thing:5505""" # ported by Alain Pelletier January 2022 bl_idname = "object.curve_gear" @@ -1467,7 +1467,7 @@ class CamCurveGear(Operator): bl_options = {'REGISTER', 'UNDO', 'PRESET'} tooth_spacing: FloatProperty( - name="distance per tooth", + name="Distance per Tooth", default=0.010, min=0.001, max=1.0, @@ -1475,19 +1475,19 @@ class CamCurveGear(Operator): unit="LENGTH", ) tooth_amount: IntProperty( - name="Amount of teeth", + name="Amount of Teeth", default=7, min=4, ) spoke_amount: IntProperty( - name="Amount of spokes", + name="Amount of Spokes", default=4, min=0, ) hole_diameter: FloatProperty( - name="Hole diameter", + name="Hole Diameter", default=0.003175, min=0, max=3.0, @@ -1495,7 +1495,7 @@ class CamCurveGear(Operator): unit="LENGTH", ) rim_size: FloatProperty( - name="Rim size", + name="Rim Size", default=0.003175, min=0, max=3.0, @@ -1503,7 +1503,7 @@ class CamCurveGear(Operator): unit="LENGTH", ) hub_diameter: FloatProperty( - name="Hub diameter", + name="Hub Diameter", default=0.005, min=0, max=3.0, @@ -1544,14 +1544,14 @@ class CamCurveGear(Operator): unit="LENGTH", ) rack_tooth_per_hole: IntProperty( - name="teeth per mounting hole", + name="Teeth per Mounting Hole", default=7, min=2, ) gear_type: EnumProperty( - name='Type of gear', + name='Type of Gear', items=( - ('PINION', 'Pinion', 'circular gear'), + ('PINION', 'Pinion', 'Circular Gear'), ('RACK', 'Rack', 'Straight Rack') ), description='Type of gear', diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index bba928244..d8ddb20d6 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -35,14 +35,14 @@ class CamSineCurve(bpy.types.Operator): - """Object sine """ # by Alain Pelletier april 2021 + """Object Sine """ # by Alain Pelletier april 2021 bl_idname = "object.sine" - bl_label = "Create Periodic wave" + bl_label = "Create Periodic Wave" bl_options = {'REGISTER', 'UNDO', 'PRESET'} # zstring: StringProperty(name="Z equation", description="Equation for z=F(u,v)", default="0.05*sin(2*pi*4*t)" ) axis: EnumProperty( - name="displacement axis", + name="Displacement Axis", items=( ('XY', 'Y to displace X axis', 'Y constant; X sine displacement'), ('YX', 'X to displace Y axis', 'X constant; Y sine displacement'), @@ -78,7 +78,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) beatperiod: FloatProperty( - name="Beat Period offset", + name="Beat Period Offset", default=0.0, min=0.0, max=100, @@ -86,7 +86,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) shift: FloatProperty( - name="phase shift", + name="Phase Shift", default=0, min=-360, max=360, @@ -94,7 +94,7 @@ class CamSineCurve(bpy.types.Operator): unit="ROTATION", ) offset: FloatProperty( - name="offset", + name="Offset", default=0, min=- 1.0, @@ -103,13 +103,13 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) iteration: IntProperty( - name="iteration", + name="Iteration", default=100, min=50, max=2000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=0.5, min=-3.0, max=3, @@ -117,7 +117,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-3.0, max=3, @@ -125,7 +125,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) wave_distance: FloatProperty( - name="distance between multiple waves", + name="Distance Between Multiple Waves", default=0.0, min=0.0, max=100, @@ -133,7 +133,7 @@ class CamSineCurve(bpy.types.Operator): unit="LENGTH", ) wave_angle_offset: FloatProperty( - name="angle offset for multiple waves", + name="Angle Offset for Multiple Waves", default=pi/2, min=-200*pi, max=200*pi, @@ -141,7 +141,7 @@ class CamSineCurve(bpy.types.Operator): unit="ROTATION", ) wave_amount: IntProperty( - name="amount of multiple waves", + name="Amount of Multiple Waves", default=1, min=1, max=2000, @@ -195,7 +195,7 @@ def f(t, offset: float = 0.0, angle_offset: float = 0.0): class CamLissajousCurve(bpy.types.Operator): """Lissajous """ # by Alain Pelletier april 2021 bl_idname = "object.lissajous" - bl_label = "Create Lissajous figure" + bl_label = "Create Lissajous Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} amplitude_A: FloatProperty( @@ -264,7 +264,7 @@ class CamLissajousCurve(bpy.types.Operator): unit="LENGTH", ) shift: FloatProperty( - name="phase shift", + name="Phase Shift", default=0, min=-360, max=360, @@ -273,13 +273,13 @@ class CamLissajousCurve(bpy.types.Operator): ) iteration: IntProperty( - name="iteration", + name="Iteration", default=500, min=50, max=10000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=11, min=-3.0, max=1000000, @@ -287,7 +287,7 @@ class CamLissajousCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-10.0, max=3, @@ -330,20 +330,20 @@ def f(t, offset: float = 0.0): class CamHypotrochoidCurve(bpy.types.Operator): - """hypotrochoid """ # by Alain Pelletier april 2021 + """Hypotrochoid """ # by Alain Pelletier april 2021 bl_idname = "object.hypotrochoid" - bl_label = "Create Spirograph type figure" + bl_label = "Create Spirograph Type Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} typecurve: EnumProperty( - name="type of curve", + name="Type of Curve", items=( - ('hypo', 'Hypotrochoid', 'inside ring'), - ('epi', 'Epicycloid', 'outside inner ring') + ('hypo', 'Hypotrochoid', 'Inside ring'), + ('epi', 'Epicycloid', 'Outside inner ring') ), ) R: FloatProperty( - name="Big circle radius", + name="Big Circle Radius", default=0.25, min=0.001, max=100, @@ -351,7 +351,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) r: FloatProperty( - name="Small circle radius", + name="Small Circle Radius", default=0.18, min=0.0001, max=100, @@ -359,7 +359,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) d: FloatProperty( - name="distance from center of interior circle", + name="Distance from Center of Interior Circle", default=0.050, min=0, max=100, @@ -367,7 +367,7 @@ class CamHypotrochoidCurve(bpy.types.Operator): unit="LENGTH", ) dip: FloatProperty( - name="variable depth from center", + name="Variable Depth from Center", default=0.00, min=-100, max=100, @@ -424,35 +424,35 @@ def f(t, offset: float = 0.0): class CamCustomCurve(bpy.types.Operator): - """Object customCurve """ # by Alain Pelletier april 2021 + """Object Custom Curve """ # by Alain Pelletier april 2021 bl_idname = "object.customcurve" - bl_label = "Create custom curve" + bl_label = "Create Custom Curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} xstring: StringProperty( - name="X equation", + name="X Equation", description="Equation x=F(t)", default="t", ) ystring: StringProperty( - name="Y equation", + name="Y Equation", description="Equation y=F(t)", default="0", ) zstring: StringProperty( - name="Z equation", + name="Z Equation", description="Equation z=F(t)", default="0.05*sin(2*pi*4*t)", ) iteration: IntProperty( - name="iteration", + name="Iteration", default=100, min=50, max=2000, ) maxt: FloatProperty( - name="Wave ends at x", + name="Wave Ends at X", default=0.5, min=-3.0, max=10, @@ -460,7 +460,7 @@ class CamCustomCurve(bpy.types.Operator): unit="LENGTH", ) mint: FloatProperty( - name="Wave starts at x", + name="Wave Starts at X", default=0, min=-3.0, max=3, diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index dae598fcc..ac3e94731 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -46,19 +46,19 @@ # boolean operations for curve objects class CamCurveBoolean(Operator): - """perform Boolean operation on two or more curves""" + """Perform Boolean Operation on Two or More Curves""" bl_idname = "object.curve_boolean" bl_label = "Curve Boolean" bl_options = {'REGISTER', 'UNDO'} boolean_type: EnumProperty( - name='type', + name='Type', items=( ('UNION', 'Union', ''), ('DIFFERENCE', 'Difference', ''), ('INTERSECT', 'Intersect', '') ), - description='boolean type', + description='Boolean type', default='UNION' ) @@ -76,7 +76,7 @@ def execute(self, context): class CamCurveConvexHull(Operator): - """perform hull operation on single or multiple curves""" # by Alain Pelletier april 2021 + """Perform Hull Operation on Single or Multiple Curves""" # by Alain Pelletier april 2021 bl_idname = "object.convex_hull" bl_label = "Convex Hull" bl_options = {'REGISTER', 'UNDO'} @@ -92,13 +92,13 @@ def execute(self, context): # intarsion or joints class CamCurveIntarsion(Operator): - """makes curve cuttable both inside and outside, for intarsion and joints""" + """Makes Curve Cuttable Both Inside and Outside, for Intarsion and Joints""" bl_idname = "object.curve_intarsion" bl_label = "Intarsion" bl_options = {'REGISTER', 'UNDO', 'PRESET'} diameter: FloatProperty( - name="cutter diameter", + name="Cutter Diameter", default=.001, min=0, max=0.025, @@ -106,7 +106,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) tolerance: FloatProperty( - name="cutout Tolerance", + name="Cutout Tolerance", default=.0001, min=0, max=0.005, @@ -114,7 +114,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) backlight: FloatProperty( - name="Backlight seat", + name="Backlight Seat", default=0.000, min=0, max=0.010, @@ -122,7 +122,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) perimeter_cut: FloatProperty( - name="Perimeter cut offset", + name="Perimeter Cut Offset", default=0.000, min=0, max=0.100, @@ -130,7 +130,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) base_thickness: FloatProperty( - name="Base material thickness", + name="Base Material Thickness", default=0.000, min=0, max=0.100, @@ -138,7 +138,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) intarsion_thickness: FloatProperty( - name="Intarsion material thickness", + name="Intarsion Material Thickness", default=0.000, min=0, max=0.100, @@ -146,7 +146,7 @@ class CamCurveIntarsion(Operator): unit="LENGTH", ) backlight_depth_from_top: FloatProperty( - name="Backlight well depth", + name="Backlight Well Depth", default=0.000, min=0, max=0.100, @@ -214,13 +214,13 @@ def execute(self, context): # intarsion or joints class CamCurveOvercuts(Operator): - """Adds overcuts for slots""" + """Adds Overcuts for Slots""" bl_idname = "object.curve_overcuts" - bl_label = "Add Overcuts" + bl_label = "Add Overcuts - A" bl_options = {'REGISTER', 'UNDO'} diameter: FloatProperty( - name="diameter", + name="Diameter", default=.003175, min=0, max=100, @@ -228,7 +228,7 @@ class CamCurveOvercuts(Operator): unit="LENGTH", ) threshold: FloatProperty( - name="threshold", + name="Threshold", default=pi / 2 * .99, min=-3.14, max=3.14, @@ -237,7 +237,7 @@ class CamCurveOvercuts(Operator): unit="ROTATION", ) do_outer: BoolProperty( - name="Outer polygons", + name="Outer Polygons", default=True, ) invert: BoolProperty( @@ -322,13 +322,13 @@ def execute(self, context): # Overcut type B class CamCurveOvercutsB(Operator): - """Adds overcuts for slots""" + """Adds Overcuts for Slots""" bl_idname = "object.curve_overcuts_b" - bl_label = "Add Overcuts-B" + bl_label = "Add Overcuts - B" bl_options = {'REGISTER', 'UNDO'} diameter: FloatProperty( - name="Tool diameter", + name="Tool Diameter", default=.003175, description='Tool bit diameter used in cut operation', min=0, @@ -337,7 +337,7 @@ class CamCurveOvercutsB(Operator): unit="LENGTH", ) style: EnumProperty( - name="style", + name="Style", items=( ('OPEDGE', 'opposite edge', 'place corner overcuts on opposite edges'), @@ -359,7 +359,7 @@ class CamCurveOvercutsB(Operator): unit="ROTATION", ) do_outer: BoolProperty( - name="Include outer curve", + name="Include Outer Curve", description='Include the outer curve if there are curves inside', default=True, ) @@ -369,7 +369,7 @@ class CamCurveOvercutsB(Operator): default=True, ) otherEdge: BoolProperty( - name="other edge", + name="Other Edge", description='change to the other edge for the overcut to be on', default=False, ) @@ -592,9 +592,9 @@ def getCornerDelta(curidx, nextidx): class CamCurveRemoveDoubles(Operator): - """curve remove doubles""" + """Curve Remove Doubles""" bl_idname = "object.curve_remove_doubles" - bl_label = "C-Remove doubles" + bl_label = "Remove Curve Doubles" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -604,11 +604,11 @@ def poll(cls, context): def execute(self, context): obs = bpy.context.selected_objects #bpy.context.object.data.dimensions = '3D' - bpy.context.object.data.resolution_u = 32 + #bpy.context.object.data.resolution_u = 32 for ob in obs: bpy.context.view_layer.objects.active = ob if bpy.context.mode == 'OBJECT': - bpy.ops.object.editmode_toggle() + bpy.ops.object.editmode_toggle() bpy.ops.curve.select_all() bpy.ops.curve.decimate(ratio=1) bpy.ops.curve.remove_double(distance=0.0001) @@ -618,13 +618,13 @@ def execute(self, context): class CamMeshGetPockets(Operator): - """Detect pockets in a mesh and extract them as curves""" + """Detect Pockets in a Mesh and Extract Them as Curves""" bl_idname = "object.mesh_get_pockets" - bl_label = "Get pocket surfaces" + bl_label = "Get Pocket Surfaces" bl_options = {'REGISTER', 'UNDO'} threshold: FloatProperty( - name="horizontal threshold", + name="Horizontal Threshold", description="How horizontal the surface must be for a pocket: " "1.0 perfectly flat, 0.0 is any orientation", default=.99, @@ -633,8 +633,8 @@ class CamMeshGetPockets(Operator): precision=4, ) zlimit: FloatProperty( - name="z limit", - description="maximum z height considered for pocket operation, " + name="Z Limit", + description="Maximum z height considered for pocket operation, " "default is 0.0", default=0.0, min=-1000.0, @@ -729,13 +729,13 @@ def execute(self, context): # this operator finds the silhouette of objects(meshes, curves just get converted) and offsets it. class CamOffsetSilhouete(Operator): - """Curve offset operation """ + """Curve Offset Operation """ bl_idname = "object.silhouete_offset" - bl_label = "Silhouete offset" + bl_label = "Silhouette Offset" bl_options = {'REGISTER', 'UNDO', 'PRESET'} offset: FloatProperty( - name="offset", + name="Offset", default=.003, min=-100, max=100, @@ -751,7 +751,7 @@ class CamOffsetSilhouete(Operator): unit="LENGTH", ) style: EnumProperty( - name="type of curve", + name="Type of Curve", items=( ('1', 'Round', ''), ('2', 'Mitre', ''), @@ -759,7 +759,7 @@ class CamOffsetSilhouete(Operator): ), ) opencurve: BoolProperty( - name="Dialate open curve", + name="Dialate Open Curve", default=False, ) @@ -804,9 +804,9 @@ def execute(self, context): # Finds object silhouette, usefull for meshes, since with curves it's not needed. class CamObjectSilhouete(Operator): - """Object silhouete """ + """Object Silhouette""" bl_idname = "object.silhouete" - bl_label = "Object silhouete" + bl_label = "Object Silhouette" bl_options = {'REGISTER', 'UNDO'} @classmethod diff --git a/scripts/addons/cam/engine.py b/scripts/addons/cam/engine.py index 8e426006b..5316857d4 100644 --- a/scripts/addons/cam/engine.py +++ b/scripts/addons/cam/engine.py @@ -25,7 +25,7 @@ class BLENDERCAM_ENGINE(RenderEngine): bl_idname = "BLENDERCAM_RENDER" - bl_label = "Cam" + bl_label = "BlenderCAM" bl_use_eevee_viewport = True diff --git a/scripts/addons/cam/gcodeimportparser.py b/scripts/addons/cam/gcodeimportparser.py index ee09d06ac..3d5143ec2 100644 --- a/scripts/addons/cam/gcodeimportparser.py +++ b/scripts/addons/cam/gcodeimportparser.py @@ -46,7 +46,7 @@ def import_gcode(context, filepath): model.draw(split_layers=False) now = time.time() - print("importing Gcode took ", round(now - then, 1), "seconds") + print("Importing Gcode Took ", round(now - then, 1), "Seconds") return {'FINISHED'} diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index 19b0f0c9d..dc68d4a4c 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -91,9 +91,9 @@ def pointonline(a, b, c, tolerence): def exportGcodePath(filename, vertslist, operations): - """exports gcode with the heeks nc adopted library.""" + """Exports G-code with the Heeks NC Adopted Library.""" print("EXPORT") - progress('exporting gcode file') + progress('Exporting G-code File') t = time.time() s = bpy.context.scene m = s.cam_machine @@ -113,7 +113,7 @@ def exportGcodePath(filename, vertslist, operations): if totops > m.split_limit: split = True filesnum = ceil(totops / m.split_limit) - print('file will be separated into %i files' % filesnum) + print('File Will Be Separated Into %i Files' % filesnum) print('1') basefilename = bpy.data.filepath[:- @@ -203,7 +203,7 @@ def startNewFile(): # start program c.program_begin(0, filename) c.flush_nc() - c.comment('G-code generated with BlenderCAM and NC library') + c.comment('G-code Generated with BlenderCAM and NC library') # absolute coordinates c.absolute() @@ -571,7 +571,7 @@ async def getPath(context, operation): # should do all path calculations. pr.enable() await getPath3axis(context, operation) pr.disable() - pr.dump_stats(time.strftime("blendercam_%Y%m%d_%H%M.prof")) + pr.dump_stats(time.strftime("BlenderCAM_%Y%m%d_%H%M.prof")) else: await getPath3axis(context, operation) @@ -600,8 +600,8 @@ async def getPath(context, operation): # should do all path calculations. def getChangeData(o): - """this is a function to check if object props have changed, - to see if image updates are needed in the image based method""" + """This Is a Function to Check if Object Props Have Changed, + to See if Image Updates Are Needed in the Image Based Method""" changedata = '' obs = [] if o.geometry_source == 'OBJECT': @@ -628,8 +628,8 @@ def checkMemoryLimit(o): if res > limit: ratio = (res / limit) o.optimisation.pixsize = o.optimisation.pixsize * sqrt(ratio) - o.info.warnings += f"Memory limit: sampling resolution reduced to {o.optimisation.pixsize:.2e}\n" - print('changing sampling resolution to %f' % o.optimisation.pixsize) + o.info.warnings += f"Memory limit: Sampling Resolution Reduced to {o.optimisation.pixsize:.2e}\n" + print('Changing Sampling Resolution to %f' % o.optimisation.pixsize) # this is the main function. diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 074f1f5d0..c007b4c27 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -160,7 +160,7 @@ def _offset_inner_loop(y1, y2, cutterArrayNan, cwidth, sourceArray, width, heigh async def offsetArea(o, samples): - """ offsets the whole image with the cutter + skin offsets """ + """ Offsets the Whole Image with the Cutter + Skin Offsets """ if o.update_offsetimage_tag: minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z @@ -192,7 +192,7 @@ async def offsetArea(o, samples): await progress_async('offset depth image', int((y2 * 100) / comparearea.shape[1])) o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea - print('\nOffset image time ' + str(time.time() - t)) + print('\nOffset Image Time ' + str(time.time() - t)) o.update_offsetimage_tag = False return o.offset_image @@ -205,9 +205,9 @@ def dilateAr(ar, cycles): def getOffsetImageCavities(o, i): # for pencil operation mainly - """detects areas in the offset image which are 'cavities' - the curvature changes.""" + """Detects Areas in the Offset Image Which Are 'cavities' - the Curvature Changes.""" # i=numpy.logical_xor(lastislice , islice) - progress('detect corners in the offset image') + progress('Detect Corners in the Offset Image') vertical = i[:-2, 1:-1] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[2:, 1:-1] horizontal = i[1:-1, :-2] - i[1:-1, 1:-1] - o.pencil_threshold > i[1:-1, 1:-1] - i[1:-1, 2:] # if bpy.app.debug_value==2: @@ -267,7 +267,7 @@ def imageEdgeSearch_online(o, ar, zimage): if perc != int(100 - 100 * totpix / startpix): perc = int(100 - 100 * totpix / startpix) - progress('pencil path searching', perc) + progress('Pencil Path Searching', perc) # progress('simulation ',int(100*i/l)) success = False testangulardistance = 0 # distance from initial direction in the list of direction @@ -286,7 +286,7 @@ def imageEdgeSearch_online(o, ar, zimage): last_direction = test_direction ar[xs, ys] = False if 0: - print('success') + print('Success') print(xs, ys, testlength, testangle) print(lastvect) print(testvect) @@ -1100,7 +1100,7 @@ def _restore_render_settings(pairs, properties): def renderSampleImage(o): t = time.time() - progress('getting zbuffer') + progress('Getting Z-Buffer') # print(o.zbuffer_image) o.update_offsetimage_tag = True if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION': @@ -1221,7 +1221,7 @@ def renderSampleImage(o): if backup_settings is not None: _restore_render_settings(SETTINGS_TO_BACKUP, backup_settings) else: - print("Failed to backup scene settings") + print("Failed to Backup Scene Settings") i = bpy.data.images.load(iname) bpy.context.scene.render.engine = 'BLENDERCAM_RENDER' @@ -1248,7 +1248,7 @@ def renderSampleImage(o): #o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth) o.optimisation.pixsize = o.source_image_size_x / i.size[0] - progress('pixel size in the image source', o.optimisation.pixsize) + progress('Pixel Size in the Image Source', o.optimisation.pixsize) rawimage = imagetonumpy(i) maxa = numpy.max(rawimage) @@ -1304,7 +1304,7 @@ async def prepareArea(o): iname = getCachePath(o) + '_off.exr' if not o.update_offsetimage_tag: - progress('loading offset image') + progress('Loading Offset Image') try: o.offset_image = imagetonumpy(bpy.data.images.load(iname)) @@ -1390,7 +1390,7 @@ def getCutterArray(operation, pixsize): # print(cutob.scale) vstart = Vector((0, 0, -10)) vend = Vector((0, 0, 10)) - print('sampling custom cutter') + print('Sampling Custom Cutter') maxz = -1 for a in range(0, res): vstart.x = (a + 0.5 - m) * ps * scale diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 2077461aa..9857e99c8 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -311,7 +311,7 @@ def fixed_finger(loop, loop_length, finger_size, finger_thick, finger_tolerance, old_mortise_angle = 0 distance = finger_size / 2 j = 0 - print("joinery loop length", round(loop_length * 1000), "mm") + print("Joinery Loop Length", round(loop_length * 1000), "mm") for i, p in enumerate(coords): if i == 0: p_start = p @@ -527,8 +527,8 @@ def distributed_interlock(loop, loop_length, finger_depth, finger_thick, finger_ end_distance = loop_length j = 0 - print("joinery loop length", round(loop_length * 1000), "mm") - print("distance between joints", round(spacing * 1000), "mm") + print("Joinery Loop Length", round(loop_length * 1000), "mm") + print("Distance Between Joints", round(spacing * 1000), "mm") for i, p in enumerate(coords): if i == 0: diff --git a/scripts/addons/cam/machine_settings.py b/scripts/addons/cam/machine_settings.py index b466dfc20..58178bace 100644 --- a/scripts/addons/cam/machine_settings.py +++ b/scripts/addons/cam/machine_settings.py @@ -15,17 +15,17 @@ class machineSettings(PropertyGroup): """stores all data for machines""" # name = StringProperty(name="Machine Name", default="Machine") post_processor: EnumProperty( - name='Post processor', + name='Post Processor', items=( - ('ISO', 'Iso', 'exports standardized gcode ISO 6983 (RS-274)'), - ('MACH3', 'Mach3', 'default mach3'), + ('ISO', 'Iso', 'Exports standardized gcode ISO 6983 (RS-274)'), + ('MACH3', 'Mach3', 'Default mach3'), ('EMC', 'LinuxCNC - EMC2', 'Linux based CNC control software - formally EMC2'), ('FADAL', 'Fadal', 'Fadal VMC'), ('GRBL', 'grbl', - 'optimized gcode for grbl firmware on Arduino with cnc shield'), - ('HEIDENHAIN', 'Heidenhain', 'heidenhain'), - ('HEIDENHAIN530', 'Heidenhain530', 'heidenhain530'), + 'Optimized gcode for grbl firmware on Arduino with cnc shield'), + ('HEIDENHAIN', 'Heidenhain', 'Heidenhain'), + ('HEIDENHAIN530', 'Heidenhain530', 'Heidenhain530'), ('TNC151', 'Heidenhain TNC151', 'Post Processor for the Heidenhain TNC151 machine'), ('SIEGKX1', 'Sieg KX1', 'Sieg KX1'), @@ -37,19 +37,19 @@ class machineSettings(PropertyGroup): ('SHOPBOT MTC', 'ShopBot MTC', 'ShopBot MTC'), ('LYNX_OTTER_O', 'Lynx Otter o', 'Lynx Otter o') ), - description='Post processor', + description='Post Processor', default='MACH3', ) # units = EnumProperty(name='Units', items = (('IMPERIAL', '')) # position definitions: use_position_definitions: BoolProperty( - name="Use position definitions", + name="Use Position Definitions", description="Define own positions for op start, " "toolchange, ending position", default=False, ) starting_position: FloatVectorProperty( - name='Start position', + name='Start Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -57,7 +57,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) mtc_position: FloatVectorProperty( - name='MTC position', + name='MTC Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -65,7 +65,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) ending_position: FloatVectorProperty( - name='End position', + name='End Position', default=(0, 0, 0), unit='LENGTH', precision=constants.PRECISION, @@ -82,7 +82,7 @@ class machineSettings(PropertyGroup): update=updateMachine, ) feedrate_min: FloatProperty( - name="Feedrate minimum /min", + name="Feedrate Minimum /min", default=0.0, min=0.00001, max=320000, @@ -90,7 +90,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) feedrate_max: FloatProperty( - name="Feedrate maximum /min", + name="Feedrate Maximum /min", default=2, min=0.00001, max=320000, @@ -98,7 +98,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) feedrate_default: FloatProperty( - name="Feedrate default /min", + name="Feedrate Default /min", default=1.5, min=0.00001, max=320000, @@ -106,7 +106,7 @@ class machineSettings(PropertyGroup): unit='LENGTH', ) hourly_rate: FloatProperty( - name="Price per hour", + name="Price per Hour", default=100, min=0.005, precision=2, @@ -115,28 +115,28 @@ class machineSettings(PropertyGroup): # UNSUPPORTED: spindle_min: FloatProperty( - name="Spindle speed minimum RPM", + name="Spindle Speed Minimum RPM", default=5000, min=0.00001, max=320000, precision=1, ) spindle_max: FloatProperty( - name="Spindle speed maximum RPM", + name="Spindle Speed Maximum RPM", default=30000, min=0.00001, max=320000, precision=1, ) spindle_default: FloatProperty( - name="Spindle speed default RPM", + name="Spindle Speed Default RPM", default=15000, min=0.00001, max=320000, precision=1, ) spindle_start_time: FloatProperty( - name="Spindle start delay seconds", + name="Spindle Start Delay Seconds", description='Wait for the spindle to start spinning before starting ' 'the feeds , in seconds', default=0, @@ -146,23 +146,23 @@ class machineSettings(PropertyGroup): ) axis4: BoolProperty( - name="#4th axis", + name="#4th Axis", description="Machine has 4th axis", default=0, ) axis5: BoolProperty( - name="#5th axis", + name="#5th Axis", description="Machine has 5th axis", default=0, ) eval_splitting: BoolProperty( - name="Split files", - description="split gcode file with large number of operations", + name="Split Files", + description="Split gcode file with large number of operations", default=True, ) # split large files split_limit: IntProperty( - name="Operations per file", + name="Operations per File", description="Split files with larger number of operations than this", min=1000, max=20000000, @@ -178,7 +178,7 @@ class machineSettings(PropertyGroup): # default='X', update = updateOffsetImage) collet_size: FloatProperty( - name="#Collet size", + name="#Collet Size", description="Collet size for collision detection", default=33, min=0.00001, @@ -191,38 +191,38 @@ class machineSettings(PropertyGroup): # post processor options output_block_numbers: BoolProperty( - name="output block numbers", - description="output block numbers ie N10 at start of line", + name="Output Block Numbers", + description="Output block numbers ie N10 at start of line", default=False, ) start_block_number: IntProperty( - name="start block number", - description="the starting block number ie 10", + name="Start Block Number", + description="The starting block number ie 10", default=10, ) block_number_increment: IntProperty( - name="block number increment", - description="how much the block number should " + name="Block Number Increment", + description="How much the block number should " "increment for the next line", default=10, ) output_tool_definitions: BoolProperty( - name="output tool definitions", - description="output tool definitions", + name="Output Tool Definitions", + description="Output tool definitions", default=True, ) output_tool_change: BoolProperty( - name="output tool change commands", - description="output tool change commands ie: Tn M06", + name="Output Tool Change Commands", + description="Output tool change commands ie: Tn M06", default=True, ) output_g43_on_tool_change: BoolProperty( - name="output G43 on tool change", - description="output G43 on tool change line", + name="Output G43 on Tool Change", + description="Output G43 on tool change line", default=False, ) diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index 6be08d42d..e99f5425f 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -43,7 +43,7 @@ def get_oclSTL(operation): # FIXME needs to work with collections if not found_mesh: raise CamException( - "This operation requires a mesh or curve object or equivalent (e.g. text, volume).") + "This Operation Requires a Mesh or Curve Object or Equivalent (e.g. Text, Volume).") return oclSTL @@ -78,7 +78,7 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): cutter = ocl.BullCutter((op_cutter_diameter + operation.skin * 2) * 1000, operation.bull_corner_radius*1000, cutter_length) else: - print("Cutter unsupported: {0}\n".format(op_cutter_type)) + print("Cutter Unsupported: {0}\n".format(op_cutter_type)) quit() bdc = ocl.BatchDropCutter() @@ -93,7 +93,7 @@ async def ocl_sample(operation, chunks, use_cached_mesh=False): for chunk in chunks: for coord in chunk.get_points_np(): bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000)) - await progress_async("OpenCAMLib sampling") + await progress_async("OpenCAMLib Sampling") bdc.run() cl_points = bdc.getCLPoints() diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index f63740ac5..f96132a99 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -68,7 +68,7 @@ def __init__(self, o, proc): def threadread(tcom): - """reads stdout of background process, done this way to have it non-blocking""" + """Reads Stdout of Background Process, Done This Way to Have It Non-blocking""" inline = tcom.proc.stdout.readline() inline = str(inline) s = inline.find('progress{') @@ -79,7 +79,7 @@ def threadread(tcom): @bpy.app.handlers.persistent def timer_update(context): - """monitoring of background processes""" + """Monitoring of Background Processes""" text = '' s = bpy.context.scene if hasattr(bpy.ops.object.calculate_cam_paths_background.__class__, 'cam_processes'): @@ -114,9 +114,9 @@ def timer_update(context): class PathsBackground(Operator): - """calculate CAM paths in background. File has to be saved before.""" + """Calculate CAM Paths in Background. File Has to Be Saved Before.""" bl_idname = "object.calculate_cam_paths_background" - bl_label = "Calculate CAM paths in background" + bl_label = "Calculate CAM Paths in Background" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -149,9 +149,9 @@ def execute(self, context): class KillPathsBackground(Operator): - """Remove CAM path processes in background.""" + """Remove CAM Path Processes in Background.""" bl_idname = "object.kill_calculate_cam_paths_background" - bl_label = "Kill background computation of an operation" + bl_label = "Kill Background Computation of an Operation" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -201,8 +201,8 @@ async def _calc_path(operator, context): # check for free movement height < maxz and return with error if(o.movement.free_height < o.maxz): operator.report({'ERROR_INVALID_INPUT'}, - "Free movement height is less than Operation depth start \n correct and try again.") - progress_async("Operation can't be performed, see warnings for info") + "Free Movement Height Is Less than Operation Depth Start \n Correct and Try Again.") + progress_async("Operation Can't Be Performed, See Warnings for Info") return {'FINISHED', False} if o.computing: @@ -214,7 +214,7 @@ async def _calc_path(operator, context): o.movement.parallel_step_back = False try: await gcodepath.getPath(context, o) - print("Got path okay") + print("Got Path Okay") except CamException as e: traceback.print_tb(e.__traceback__) error_str = "\n".join(textwrap.wrap(str(e), width=80)) @@ -235,9 +235,9 @@ async def _calc_path(operator, context): class CalculatePath(Operator, AsyncOperatorMixin): - """calculate CAM paths""" + """Calculate CAM Paths""" bl_idname = "object.calculate_cam_path" - bl_label = "Calculate CAM paths" + bl_label = "Calculate CAM Paths" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -256,16 +256,16 @@ async def execute_async(self, context): class PathsAll(Operator): - """calculate all CAM paths""" + """Calculate All CAM Paths""" bl_idname = "object.calculate_cam_paths_all" - bl_label = "Calculate all CAM paths" + bl_label = "Calculate All CAM Paths" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): i = 0 for o in bpy.context.scene.cam_operations: bpy.context.scene.cam_active_operation = i - print('\nCalculating path :' + o.name) + print('\nCalculating Path :' + o.name) print('\n') bpy.ops.object.calculate_cam_paths_background() i += 1 @@ -279,9 +279,9 @@ def draw(self, context): class CamPackObjects(Operator): - """calculate all CAM paths""" + """Calculate All CAM Paths""" bl_idname = "object.cam_pack_objects" - bl_label = "Pack curves on sheet" + bl_label = "Pack Curves on Sheet" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -296,10 +296,10 @@ def draw(self, context): class CamSliceObjects(Operator): - """Slice a mesh object horizontally""" + """Slice a Mesh Object Horizontally""" # warning, this is a separate and neglected feature, it's a mess - by now it just slices up the object. bl_idname = "object.cam_slice_objects" - bl_label = "Slice object - usefull for lasercut puzzles e.t.c." + bl_label = "Slice Object - Useful for Lasercut Puzzles etc" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -313,7 +313,7 @@ def draw(self, context): def getChainOperations(chain): - """return chain operations, currently chain object can't store operations directly due to blender limitations""" + """Return Chain Operations, Currently Chain Object Can't Store Operations Directly Due to Blender Limitations""" chop = [] for cho in chain.operations: for so in bpy.context.scene.cam_operations: @@ -323,9 +323,9 @@ def getChainOperations(chain): class PathsChain(Operator, AsyncOperatorMixin): - """calculate a chain and export the gcode alltogether. """ + """Calculate a Chain and Export the G-code Alltogether. """ bl_idname = "object.calculate_cam_paths_chain" - bl_label = "Calculate CAM paths in current chain and export chain gcode" + bl_label = "Calculate CAM Paths in Current Chain and Export Chain G-code" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -344,11 +344,11 @@ async def execute_async(self, context): for i in range(0, len(chainops)): s.cam_active_operation = s.cam_operations.find( chainops[i].name) - self.report({'INFO'}, f"Calculating path: {chainops[i].name}") + self.report({'INFO'}, f"Calculating Path: {chainops[i].name}") result, success = await _calc_path(self, context) if not success and 'FINISHED' in result: self.report( - {'ERROR'}, f"Couldn't calculate path: {chainops[i].name}") + {'ERROR'}, f"Couldn't Calculate Path: {chainops[i].name}") except Exception as e: print("FAIL", e) traceback.print_tb(e.__traceback__) @@ -362,9 +362,9 @@ async def execute_async(self, context): class PathExportChain(Operator): - """calculate a chain and export the gcode alltogether. """ + """Calculate a Chain and Export the G-code Together.""" bl_idname = "object.cam_export_paths_chain" - bl_label = "Export CAM paths in current chain as gcode" + bl_label = "Export CAM Paths in Current Chain as G-code" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -390,9 +390,9 @@ def execute(self, context): class PathExport(Operator): - """Export gcode. Can be used only when the path object is present""" + """Export G-code. Can Be Used only when the Path Object Is Present""" bl_idname = "object.cam_export" - bl_label = "Export operation gcode" + bl_label = "Export Operation G-code" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): @@ -409,11 +409,11 @@ def execute(self, context): class CAMSimulate(Operator, AsyncOperatorMixin): - """simulate CAM operation - this is performed by: creating an image, painting Z depth of the brush substractively. - Works only for some operations, can not be used for 4-5 axis.""" + """Simulate CAM Operation + This Is Performed by: Creating an Image, Painting Z Depth of the Brush Subtractively. + Works only for Some Operations, Can Not Be Used for 4-5 Axis.""" bl_idname = "object.cam_simulate" - bl_label = "CAM simulation" + bl_label = "CAM Simulation" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} operation: StringProperty( @@ -434,7 +434,7 @@ async def execute_async(self, context): except AsyncCancelledException as e: return {'CANCELLED'} else: - self.report({'ERROR'}, 'no computed path to simulate') + self.report({'ERROR'}, 'No Computed Path to Simulate') return {'FINISHED'} return {'FINISHED'} @@ -445,10 +445,10 @@ def draw(self, context): class CAMSimulateChain(Operator, AsyncOperatorMixin): - """simulate CAM chain, compared to single op simulation just writes into one image and thus enables - to see how ops work together.""" + """Simulate CAM Chain, Compared to Single Op Simulation Just Writes Into One Image and Thus Enables + to See how Ops Work Together.""" bl_idname = "object.cam_simulate_chain" - bl_label = "CAM simulation" + bl_label = "CAM Simulation" bl_options = {'REGISTER', 'UNDO', 'BLOCKING'} @classmethod @@ -490,9 +490,9 @@ def draw(self, context): class CamChainAdd(Operator): - """Add new CAM chain""" + """Add New CAM Chain""" bl_idname = "scene.cam_chain_add" - bl_label = "Add new CAM chain" + bl_label = "Add New CAM Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -513,9 +513,9 @@ def execute(self, context): class CamChainRemove(Operator): - """Remove CAM chain""" + """Remove CAM Chain""" bl_idname = "scene.cam_chain_remove" - bl_label = "Remove CAM chain" + bl_label = "Remove CAM Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -531,9 +531,9 @@ def execute(self, context): class CamChainOperationAdd(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_add" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -551,9 +551,9 @@ def execute(self, context): class CamChainOperationUp(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_up" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -571,9 +571,9 @@ def execute(self, context): class CamChainOperationDown(Operator): - """Add operation to chain""" + """Add Operation to Chain""" bl_idname = "scene.cam_chain_operation_down" - bl_label = "Add operation to chain" + bl_label = "Add Operation to Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -591,9 +591,9 @@ def execute(self, context): class CamChainOperationRemove(Operator): - """Remove operation from chain""" + """Remove Operation from Chain""" bl_idname = "scene.cam_chain_operation_remove" - bl_label = "Remove operation from chain" + bl_label = "Remove Operation from Chain" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -611,7 +611,7 @@ def execute(self, context): def fixUnits(): - """Sets up units for blender CAM""" + """Sets up Units for BlenderCAM""" s = bpy.context.scene s.unit_settings.system_rotation = 'DEGREES' @@ -621,9 +621,9 @@ def fixUnits(): class CamOperationAdd(Operator): - """Add new CAM operation""" + """Add New CAM Operation""" bl_idname = "scene.cam_operation_add" - bl_label = "Add new CAM operation" + bl_label = "Add New CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -637,7 +637,7 @@ def execute(self, context): ob = bpy.context.active_object if ob is None: self.report({'ERROR_INVALID_INPUT'}, - "Please add an object to base the operation on.") + "Please Add an Object to Base the Operation on.") return {'CANCELLED'} minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace([ob]) @@ -658,9 +658,9 @@ def execute(self, context): class CamOperationCopy(Operator): - """Copy CAM operation""" + """Copy CAM Operation""" bl_idname = "scene.cam_operation_copy" - bl_label = "Copy active CAM operation" + bl_label = "Copy Active CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -709,9 +709,9 @@ def execute(self, context): class CamOperationRemove(Operator): - """Remove CAM operation""" + """Remove CAM Operation""" bl_idname = "scene.cam_operation_remove" - bl_label = "Remove CAM operation" + bl_label = "Remove CAM Operation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -744,18 +744,18 @@ def execute(self, context): # move cam operation in the list up or down class CamOperationMove(Operator): - """Move CAM operation""" + """Move CAM Operation""" bl_idname = "scene.cam_operation_move" - bl_label = "Move CAM operation in list" + bl_label = "Move CAM Operation in List" bl_options = {'REGISTER', 'UNDO'} direction: EnumProperty( - name='direction', + name='Direction', items=( ('UP', 'Up', ''), ('DOWN', 'Down', '') ), - description='direction', + description='Direction', default='DOWN', ) @@ -781,9 +781,9 @@ def execute(self, context): class CamOrientationAdd(Operator): - """Add orientation to cam operation, for multiaxis operations""" + """Add Orientation to CAM Operation, for Multiaxis Operations""" bl_idname = "scene.cam_orientation_add" - bl_label = "Add orientation" + bl_label = "Add Orientation" bl_options = {'REGISTER', 'UNDO'} @classmethod @@ -808,9 +808,9 @@ def execute(self, context): class CamBridgesAdd(Operator): - """Add bridge objects to curve""" + """Add Bridge Objects to Curve""" bl_idname = "scene.cam_bridges_add" - bl_label = "Add bridges" + bl_label = "Add Bridges / Tabs" bl_options = {'REGISTER', 'UNDO'} @classmethod diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index 3e99dbe3c..28114681e 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -218,7 +218,7 @@ class PackObjectsSettings(PropertyGroup): """stores all data for machines""" sheet_fill_direction: EnumProperty( - name="Fill direction", + name="Fill Direction", items=( ("X", "X", "Fills sheet in X axis direction"), ("Y", "Y", "Fills sheet in Y axis direction"), @@ -227,7 +227,7 @@ class PackObjectsSettings(PropertyGroup): default="Y", ) sheet_x: FloatProperty( - name="X size", + name="X Size", description="Sheet size", min=0.001, max=10, @@ -236,7 +236,7 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) sheet_y: FloatProperty( - name="Y size", + name="Y Size", description="Sheet size", min=0.001, max=10, @@ -245,8 +245,8 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) distance: FloatProperty( - name="Minimum distance", - description="minimum distance between objects(should be " + name="Minimum Distance", + description="Minimum distance between objects(should be " "at least cutter diameter!)", min=0.001, max=10, @@ -264,13 +264,13 @@ class PackObjectsSettings(PropertyGroup): unit="LENGTH", ) rotate: BoolProperty( - name="enable rotation", + name="Enable Rotation", description="Enable rotation of elements", default=True, ) rotate_angle: FloatProperty( - name="Placement Angle rotation step", - description="bigger rotation angle,faster placemant", + name="Placement Angle Rotation Step", + description="Bigger rotation angle, faster placemant", default=0.19635 * 4, min=pi / 180, max=pi, diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index 3e84a0049..b3f5e37e8 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -150,7 +150,7 @@ def getPathPatternParallel(o, angle): def getPathPattern(operation): o = operation t = time.time() - progress('building path pattern') + progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] @@ -408,7 +408,7 @@ def getPathPattern(operation): def getPathPattern4axis(operation): o = operation t = time.time() - progress('building path pattern') + progress('Building Path Pattern') minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z pathchunks = [] zlevel = 1 # minz#this should do layers... diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index acd31bc46..06ebc53ac 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -73,7 +73,7 @@ def shapelyToMultipolygon(anydata): else: return sgeometry.MultiPolygon() else: - print(anydata.type, 'shapely conversion aborted') + print(anydata.type, 'Shapely Conversion Aborted') return sgeometry.MultiPolygon() diff --git a/scripts/addons/cam/preferences.py b/scripts/addons/cam/preferences.py index 71d83514f..b2a67c797 100644 --- a/scripts/addons/cam/preferences.py +++ b/scripts/addons/cam/preferences.py @@ -15,17 +15,17 @@ class CamAddonPreferences(AddonPreferences): bl_idname = __package__ op_preset_update: BoolProperty( - name="Have the Operation Presets been Updated", + name="Have the Operation Presets Been Updated", default=False, ) experimental: BoolProperty( - name="Show experimental features", + name="Show Experimental Features", default=False, ) update_source: StringProperty( - name="Source of updates for the addon", + name="Source of Updates for the Addon", description="This can be either a github repo link in which case " "it will download the latest release on there, " "or an api link like " @@ -35,39 +35,39 @@ class CamAddonPreferences(AddonPreferences): ) last_update_check: IntProperty( - name="Last update time", + name="Last Update Time", default=0, ) last_commit_hash: StringProperty( - name="Hash of last commit from updater", + name="Hash of Last Commit from Updater", default="", ) just_updated: BoolProperty( - name="Set to true on update or initial install", + name="Set to True on Update or Initial Install", default=True, ) new_version_available: StringProperty( - name="Set to new version name if one is found", + name="Set to New Version Name if One Is Found", default="", ) default_interface_level: EnumProperty( - name="Interface level in new file", + name="Interface Level in New File", description="Choose visible options", items=[ - ("0", "Basic", "Only show essential options"), - ("1", "Advanced", "Show advanced options"), - ("2", "Complete", "Show all options"), - ("3", "Experimental", "Show experimental options"), + ("0", "Basic", "Only show Essential Options"), + ("1", "Advanced", "Show Advanced Options"), + ("2", "Complete", "Show All Options"), + ("3", "Experimental", "Show Experimental Options"), ], default="3", ) default_machine_preset: StringProperty( - name="Machine preset in new file", + name="Machine Preset in New File", description="So that machine preset choice persists between files", default="", ) @@ -75,11 +75,11 @@ class CamAddonPreferences(AddonPreferences): def draw(self, context): layout = self.layout layout.label( - text="Use experimental features when you want to help development of Blender CAM:" + text="Use Experimental Features when you want to help development of BlenderCAM:" ) layout.prop(self, "experimental") layout.prop(self, "update_source") - layout.label(text="Choose a preset update source") + layout.label(text="Choose a Preset Update Source") UPDATE_SOURCES = [ ( diff --git a/scripts/addons/cam/preset_managers.py b/scripts/addons/cam/preset_managers.py index 16c4986a3..751925ddf 100644 --- a/scripts/addons/cam/preset_managers.py +++ b/scripts/addons/cam/preset_managers.py @@ -7,14 +7,14 @@ class CAM_CUTTER_MT_presets(Menu): - bl_label = "Cutter presets" + bl_label = "Cutter Presets" preset_subdir = "cam_cutters" preset_operator = "script.execute_preset" draw = Menu.draw_preset class CAM_MACHINE_MT_presets(Menu): - bl_label = "Machine presets" + bl_label = "Machine Presets" preset_subdir = "cam_machines" preset_operator = "script.execute_preset" draw = Menu.draw_preset @@ -54,7 +54,7 @@ class AddPresetCamCutter(AddPresetBase, Operator): class CAM_OPERATION_MT_presets(Menu): - bl_label = "Operation presets" + bl_label = "Operation Presets" preset_subdir = "cam_operations" preset_operator = "script.execute_preset" draw = Menu.draw_preset diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index 8d52cfe52..b75460839 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -71,7 +71,7 @@ def timingprint(tinf): def progress(text, n=None): - """function for reporting during the script, works for background operations in the header.""" + """Function for Reporting During the Script, Works for Background Operations in the Header.""" text = str(text) if n is None: n = '' @@ -82,7 +82,7 @@ def progress(text, n=None): def activate(o): - """makes an object active, used many times in blender""" + """Makes an Object Active, Used Many Times in Blender""" s = bpy.context.scene bpy.ops.object.select_all(action='DESELECT') o.select_set(state=True) @@ -91,18 +91,18 @@ def activate(o): def dist2d(v1, v2): - """distance between two points in 2d""" + """Distance Between Two Points in 2D""" return hypot((v1[0] - v2[0]), (v1[1] - v2[1])) def delob(ob): - """object deletion for multiple uses""" + """Object Deletion for Multiple Uses""" activate(ob) bpy.ops.object.delete(use_global=False) def dupliob(o, pos): - """helper function for visualising cutter positions in bullet simulation""" + """Helper Function for Visualising Cutter Positions in Bullet Simulation""" activate(o) bpy.ops.object.duplicate() s = 1.0 / BULLET_SCALE @@ -123,7 +123,7 @@ def addToGroup(ob, groupname): def compare(v1, v2, vmiddle, e): - """comparison for optimisation of paths""" + """Comparison for Optimisation of Paths""" # e=0.0001 v1 = Vector(v1) v2 = Vector(v2) @@ -139,7 +139,7 @@ def compare(v1, v2, vmiddle, e): def isVerticalLimit(v1, v2, limit): - """test path segment on verticality threshold, for protect_vertical option""" + """Test Path Segment on Verticality Threshold, for protect_vertical Option""" z = abs(v1[2] - v2[2]) # verticality=0.05 # this will be better. diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index 398c13498..dbdba25d1 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -98,7 +98,7 @@ def createSimulationObject(name, operations, i): async def doSimulation(name, operations): - """perform simulation of operations. Currently only for 3 axis""" + """Perform Simulation of Operations. Currently only for 3 Axis""" for o in operations: getOperationSources(o) limits = getBoundsMultiple( @@ -296,8 +296,8 @@ async def generateSimulationImage(operations, limits): def simCutterSpot(xs, ys, z, cutterArray, si, getvolume=False): - """simulates a cutter cutting into stock, taking away the volume, - and optionally returning the volume that has been milled. This is now used for feedrate tweaking.""" + """Simulates a Cutter Cutting Into Stock, Taking Away the Volume, + and Optionally Returning the Volume that Has Been Milled. This Is Now Used for Feedrate Tweaking.""" m = int(cutterArray.shape[0] / 2) size = cutterArray.shape[0] # whole cutter in image there diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index 8dcbeaaf8..1cc9493fc 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -145,11 +145,11 @@ def sliceObject(ob): # April 2020 Alain Pelletier class SliceObjectsSettings(PropertyGroup): - """stores all data for machines""" + """Stores All Data for Machines""" slice_distance: FloatProperty( - name="Slicing distance", - description="slices distance in z, should be most often " + name="Slicing Distance", + description="Slices distance in z, should be most often " "thickness of plywood sheet.", min=0.001, max=10, @@ -158,17 +158,17 @@ class SliceObjectsSettings(PropertyGroup): unit="LENGTH", ) slice_above0: BoolProperty( - name="Slice above 0", + name="Slice Above 0", description="only slice model above 0", default=False, ) slice_3d: BoolProperty( - name="3d slice", - description="for 3d carving", + name="3D Slice", + description="For 3D carving", default=False, ) indexes: BoolProperty( - name="add indexes", - description="adds index text of layer + index", + name="Add Indexes", + description="Adds index text of layer + index", default=True, ) diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index 98ebc0781..ccadaffcb 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -110,7 +110,7 @@ async def cutout(o): join = 2 else: join = 1 - print('operation: cutout') + print('Operation: Cutout') offset = True if o.cut_type == 'ONLINE' and o.onlycurves: # is separate to allow open curves :) print('separate') @@ -196,9 +196,9 @@ async def cutout(o): chunks = [] if o.use_bridges: # add bridges to chunks - print('using bridges') + print('Using Bridges') remove_multiple(o.name+'_cut_bridges') - print("old briddge cut removed") + print("Old Briddge Cut Removed") bridgeheight = min(o.max.z, o.min.z + abs(o.bridges_height)) @@ -209,7 +209,7 @@ async def cutout(o): useBridges(chunk, o) if o.profile_start > 0: - print("cutout change profile start") + print("Cutout Change Profile Start") for chl in extendorder: chunk = chl[0] if chunk.closed: @@ -217,7 +217,7 @@ async def cutout(o): # Lead in if o.lead_in > 0.0 or o.lead_out > 0: - print("cutout leadin") + print("Cutout Lead-in") for chl in extendorder: chunk = chl[0] if chunk.closed: @@ -242,11 +242,11 @@ async def cutout(o): async def curve(o): - print('operation: curve') + print('Operation: Curve') pathSamples = [] getOperationSources(o) if not o.onlycurves: - raise CamException("All objects must be curves for this operation.") + raise CamException("All Objects Must Be Curves for This Operation.") for ob in o.objects: # make the chunks from curve here @@ -289,7 +289,7 @@ async def curve(o): async def proj_curve(s, o): - print('operation: projected curve') + print('Operation: Projected Curve') pathSamples = [] chunks = [] ob = bpy.data.objects[o.curve_object] @@ -299,7 +299,7 @@ async def proj_curve(s, o): from cam import cam_chunk if targetCurve.type != 'CURVE': - raise CamException('Projection target and source have to be curve objects!') + raise CamException('Projection Target and Source Have to Be Curve Objects!') if 1: extend_up = 0.1 @@ -340,7 +340,7 @@ async def proj_curve(s, o): async def pocket(o): - print('operation: pocket') + print('Operation: Pocket') scene = bpy.context.scene remove_multiple("3D_poc") @@ -358,11 +358,11 @@ async def pocket(o): c_offset = o.cutter_diameter / 2 c_offset += o.skin # add skin - print("cutter offset", c_offset) + print("Cutter Offset", c_offset) p = getObjectOutline(c_offset, o, False) approxn = (min(o.max.x - o.min.x, o.max.y - o.min.y) / o.dist_between_paths) / 2 - print("approximative:" + str(approxn)) + print("Approximative:" + str(approxn)) print(o) i = 0 @@ -394,7 +394,7 @@ async def pocket(o): lastchunks = nchunks percent = int(i / approxn * 100) - progress('outlining polygons ', percent) + progress('Outlining Polygons ', percent) p = pnew i += 1 @@ -537,7 +537,7 @@ async def pocket(o): async def drill(o): - print('operation: Drill') + print('Operation: Drill') chunks = [] for ob in o.objects: activate(ob) @@ -631,7 +631,7 @@ async def drill(o): async def medial_axis(o): - print('operation: Medial Axis') + print('Operation: Medial Axis') remove_multiple("medialMesh") @@ -660,7 +660,7 @@ async def medial_axis(o): elif o.cutter_type == 'BALLNOSE': maxdepth = - new_cutter_diameter / 2 - o.skin else: - raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.") + raise CamException("Only Ballnose and V-carve Cutters Are Supported for Medial Axis.") # remember resolutions of curves, to refine them, # otherwise medial axis computation yields too many branches in curved parts resolutions_before = [] @@ -681,7 +681,7 @@ async def medial_axis(o): # just a multipolygon mpoly = polys else: - raise CamException("Failed getting object silhouette. Is input curve closed?") + raise CamException("Failed Getting Object Silhouette. Is Input Curve Closed?") mpoly_boundary = mpoly.boundary ipol = 0 @@ -700,19 +700,19 @@ async def medial_axis(o): # verts= points#[[vert.x, vert.y, vert.z] for vert in vertsPts] nDupli, nZcolinear = unique(verts) nVerts = len(verts) - print(str(nDupli) + " duplicates points ignored") - print(str(nZcolinear) + " z colinear points excluded") + print(str(nDupli) + " Duplicates Points Ignored") + print(str(nZcolinear) + " Z Colinear Points Excluded") if nVerts < 3: - print("Not enough points") + print("Not Enough Points") return {'FINISHED'} # Check colinear xValues = [pt[0] for pt in verts] yValues = [pt[1] for pt in verts] if checkEqual(xValues) or checkEqual(yValues): - print("Points are colinear") + print("Points Are Colinear") return {'FINISHED'} # Create diagram - print("Tesselation... (" + str(nVerts) + " points)") + print("Tesselation... (" + str(nVerts) + " Points)") xbuff, ybuff = 5, 5 # % zPosition = 0 vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] @@ -725,14 +725,14 @@ async def medial_axis(o): newIdx = 0 vertr = [] filteredPts = [] - print('filter points') + print('Filter Points') ipts = 0 for p in pts: ipts = ipts + 1 if ipts % 500 == 0: sys.stdout.write('\r') # the exact output you're looking for: - prog_message = "points: " + str(ipts) + " / " + str(len(pts)) + " " + str( + prog_message = "Points: " + str(ipts) + " / " + str(len(pts)) + " " + str( round(100 * ipts / len(pts))) + "%" sys.stdout.write(prog_message) sys.stdout.flush() @@ -761,7 +761,7 @@ async def medial_axis(o): filteredPts.append((p[0], p[1], z)) newIdx += 1 - print('filter edges') + print('Filter Edges') filteredEdgs = [] ledges = [] for e in edgesIdx: @@ -829,17 +829,17 @@ async def medial_axis(o): def getLayers(operation, startdepth, enddepth): - """returns a list of layers bounded by startdepth and enddepth - uses operation.stepdown to determine number of layers. + """Returns a List of Layers Bounded by Startdepth and Enddepth + Uses Operation.stepdown to Determine Number of Layers. """ if startdepth < enddepth: - raise CamException("Start depth is lower than end depth. " - "If you have set a custom depth end, it must be lower than depth start, " - "and should usually be negative. Set this in the CAM Operation Area panel.") + raise CamException("Start Depth Is Lower than End Depth. " + "if You Have Set a Custom Depth End, It Must Be Lower than Depth Start, " + "and Should Usually Be Negative. Set This in the CAM Operation Area Panel.") if operation.use_layers: layers = [] n = ceil((startdepth - enddepth) / operation.stepdown) - print("start " + str(startdepth) + " end " + str(enddepth) + " n " + str(n)) + print("Start " + str(startdepth) + " End " + str(enddepth) + " n " + str(n)) layerstart = operation.maxz for x in range(0, n): @@ -856,7 +856,7 @@ def getLayers(operation, startdepth, enddepth): def chunksToMesh(chunks, o): - """convert sampled chunks to path, optimization of paths""" + """Convert Sampled Chunks to Path, Optimization of Paths""" t = time.time() s = bpy.context.scene m = s.cam_machine @@ -888,7 +888,7 @@ def chunksToMesh(chunks, o): nchunks.append(ch) chunks = nchunks - progress('building paths from chunks') + progress('Building Paths from Chunks') e = 0.0001 lifted = True diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index 4decdd0c7..b5ebcfd37 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -141,7 +141,7 @@ def testOperation(i): newresult = bpy.data.objects[o.path_object_name] origname = "test_cam_path_" + o.name if origname not in s.objects: - report += 'operation test has nothing to compare with, making the new result as comparable result.\n\n' + report += 'Operation Test Has Nothing to Compare with, Making the New Result as Comparable Result.\n\n' newresult.name = origname else: testresult = bpy.data.objects[origname] @@ -149,7 +149,7 @@ def testOperation(i): m2 = newresult.data test_ok = True if len(m1.vertices) != len(m2.vertices): - report += "vertex counts don't match\n\n" + report += "Vertex Counts Don't Match\n\n" test_ok = False else: different_co_count = 0 @@ -159,12 +159,12 @@ def testOperation(i): if v1.co != v2.co: different_co_count += 1 if different_co_count > 0: - report += 'vertex position is different on %i vertices \n\n' % (different_co_count) + report += 'Vertex Position Is Different on %i Vertices \n\n' % (different_co_count) test_ok = False if test_ok: - report += 'test ok\n\n' + report += 'Test Ok\n\n' else: - report += 'test result is different\n \n ' + report += 'Test Result Is Different\n \n ' print(report) return report diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index fe56733ae..8f8cc786a 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -98,7 +98,7 @@ class CustomPanel(Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" - bl_label = "Import Gcode" + bl_label = "Import G-code" bl_idname = "OBJECT_PT_importgcode" bl_options = {'DEFAULT_CLOSED'} @@ -119,7 +119,7 @@ def draw(self, context): col = layout.column(align=True) col = col.row(align=True) col.split() - col.label(text="Segment length") + col.label(text="Segment Length") col.prop(isettings, "max_segment_size") col.enabled = isettings.subdivide @@ -131,9 +131,9 @@ def draw(self, context): class WM_OT_gcode_import(Operator, ImportHelper): - """Import Gcode, travel lines don't get drawn""" + """Import G-code, Travel Lines Don't Get Drawn""" bl_idname = "wm.gcode_import" # important since its how bpy.ops.import_test.some_data is constructed - bl_label = "Import Gcode" + bl_label = "Import G-code" # ImportHelper mixin class uses this filename_ext = ".txt" @@ -162,7 +162,7 @@ class import_settings(PropertyGroup): default=False, ) output: EnumProperty( - name="output type", + name="Output Type", items=( ("mesh", "Mesh", "Make a mesh output"), ("curve", "Curve", "Make curve output"), @@ -171,7 +171,7 @@ class import_settings(PropertyGroup): ) max_segment_size: FloatProperty( name="", - description="Only Segments bigger then this value get subdivided", + description="Only Segments bigger than this value get subdivided", default=0.001, min=0.0001, max=1.0, diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index c07a0b68f..af6cf76f2 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -6,8 +6,8 @@ class CAM_AREA_Panel(CAMButtonsPanel, Panel): - """CAM operation area panel""" - bl_label = "CAM operation area " + """CAM Operation Area Panel""" + bl_label = "CAM Operation Area" bl_idname = "WORLD_PT_CAM_OPERATION_AREA" panel_interface_level = 0 @@ -45,7 +45,7 @@ def draw_maxz(self): self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: self.layout.label(text='!ERROR! COLLISION!') - self.layout.label(text='Depth start > Free movement height') + self.layout.label(text='Depth Start > Free Movement Height') self.layout.label(text='!ERROR! COLLISION!') def draw_minz(self): @@ -53,10 +53,10 @@ def draw_minz(self): return if self.op.geometry_source in ['OBJECT', 'COLLECTION']: if self.op.strategy == 'CURVE': - self.layout.label(text="cannot use depth from object using CURVES") + self.layout.label(text="Cannot Use Depth from Object Using Curves") row = self.layout.row(align=True) - row.label(text='Set max depth from') + row.label(text='Set Max Depth from') row.prop(self.op, 'minz_from', text='') if self.op.minz_from == 'CUSTOM': self.layout.prop(self.op, 'minz') @@ -68,16 +68,16 @@ def draw_minz(self): i = bpy.data.images[self.op.source_image_name] if i is not None: sy = int((self.op.source_image_size_x / i.size[0]) * i.size[1] * 1000000) / 1000 - self.layout.label(text='image size on y axis: ' + strInUnits(sy, 8)) + self.layout.label(text='Image Size on Y Axis: ' + strInUnits(sy, 8)) self.layout.separator() self.layout.prop(self.op, 'source_image_offset') col = self.layout.column(align=True) - col.prop(self.op, 'source_image_crop', text='Crop source image') + col.prop(self.op, 'source_image_crop', text='Crop Source Image') if self.op.source_image_crop: - col.prop(self.op, 'source_image_crop_start_x', text='start x') - col.prop(self.op, 'source_image_crop_start_y', text='start y') - col.prop(self.op, 'source_image_crop_end_x', text='end x') - col.prop(self.op, 'source_image_crop_end_y', text='end y') + col.prop(self.op, 'source_image_crop_start_x', text='Start X') + col.prop(self.op, 'source_image_crop_start_y', text='Start Y') + col.prop(self.op, 'source_image_crop_end_x', text='End X') + col.prop(self.op, 'source_image_crop_end_y', text='End Y') def draw_ambient(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 15c112d1b..4c42aa36e 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -34,8 +34,8 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn class CAM_CHAINS_Panel(CAMButtonsPanel, Panel): - """CAM chains panel""" - bl_label = "CAM chains" + """CAM Chains Panel""" + bl_label = "CAM Chains" bl_idname = "WORLD_PT_CAM_CHAINS" panel_interface_level = 1 always_show_panel = True @@ -67,16 +67,16 @@ def draw(self, context): if not chain.computing: layout.operator("object.calculate_cam_paths_chain", - text="Calculate chain paths & Export Gcode") - layout.operator("object.cam_export_paths_chain", text="Export chain gcode") - layout.operator("object.cam_simulate_chain", text="Simulate this chain") + text="Calculate Chain Paths & Export Gcode") + layout.operator("object.cam_export_paths_chain", text="Export Chain G-code") + layout.operator("object.cam_simulate_chain", text="Simulate This Chain") valid, reason = isChainValid(chain, context) if not valid: - layout.label(icon="ERROR", text=f"Can't compute chain - reason:\n") + layout.label(icon="ERROR", text=f"Can't Compute Chain - Reason:\n") layout.label(text=reason) else: - layout.label(text='chain is currently computing') + layout.label(text='Chain Is Currently Computing') layout.prop(chain, 'name') layout.prop(chain, 'filename') diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index 31973e170..1a2ddc207 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -5,7 +5,7 @@ class CAM_CUTTER_Panel(CAMButtonsPanel, Panel): - """CAM cutter panel""" + """CAM Cutter Panel""" bl_label = "CAM Cutter" bl_idname = "WORLD_PT_CAM_CUTTER" panel_interface_level = 0 @@ -95,9 +95,9 @@ def draw_custom(self): if self.op.cutter_type in ['CUSTOM']: if self.op.optimisation.use_exact: self.layout.label( - text='Warning - only convex shapes are supported. ', icon='COLOR_RED') - self.layout.label(text='If your custom cutter is concave,') - self.layout.label(text='switch exact mode off.') + text='Warning - only Convex Shapes Are Supported. ', icon='COLOR_RED') + self.layout.label(text='If Your Custom Cutter Is Concave,') + self.layout.label(text='Switch Exact Mode Off.') self.layout.prop_search(self.op, "cutter_object_name", bpy.data, "objects") def draw_cutter_diameter(self): @@ -129,7 +129,7 @@ def draw_engagement(self): else: engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) - self.layout.label(text=f"Cutter engagement: {engagement}%") + self.layout.label(text=f"Cutter Engagement: {engagement}%") if engagement > 50: self.layout.label(text="WARNING: CUTTER ENGAGEMENT > 50%") diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index e66f041d9..84d1a3877 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -5,8 +5,8 @@ class CAM_FEEDRATE_Panel(CAMButtonsPanel, Panel): - """CAM feedrate panel""" - bl_label = "CAM feedrate" + """CAM Feedrate Panel""" + bl_label = "CAM Feedrate" bl_idname = "WORLD_PT_CAM_FEEDRATE" panel_interface_level = 0 diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index 934eaefba..95991cbf9 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -5,8 +5,8 @@ class CAM_GCODE_Panel(CAMButtonsPanel, Panel): - """CAM operation g-code options panel""" - bl_label = "CAM g-code options " + """CAM Operation G-code Options Panel""" + bl_label = "CAM G-code Options" bl_idname = "WORLD_PT_CAM_GCODE" panel_interface_level = 1 diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index 4acbd0578..cbab7838d 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -28,20 +28,23 @@ class CAM_INFO_Properties(PropertyGroup): warnings: StringProperty( - name='warnings', - description='warnings', + name='Warnings', + description='Warnings', default='', update=update_operation, ) chipload: FloatProperty( - name="chipload", description="Calculated chipload", + name="Chipload", + description="Calculated chipload", default=0.0, unit='LENGTH', precision=CHIPLOAD_PRECISION, ) duration: FloatProperty( - name="Estimated time", default=0.01, min=0.0000, + name="Estimated Time", + default=0.01, + min=0.0000, max=MAX_OPERATION_TIME, precision=PRECISION, unit="TIME", @@ -49,7 +52,7 @@ class CAM_INFO_Properties(PropertyGroup): class CAM_INFO_Panel(CAMButtonsPanel, Panel): - bl_label = "CAM info & warnings" + bl_label = "CAM Info & Warnings" bl_idname = "WORLD_PT_CAM_INFO" panel_interface_level = 0 always_show_panel = True @@ -68,9 +71,9 @@ def draw_blendercam_version(self): if not self.has_correct_level(): return self.layout.label( - text=f'Blendercam version: {".".join([str(x) for x in cam_version])}') + text=f'BlenderCAM v{".".join([str(x) for x in cam_version])}') if len(bpy.context.preferences.addons['cam'].preferences.new_version_available) > 0: - self.layout.label(text=f"New version available:") + self.layout.label(text=f"New Version Available:") self.layout.label( text=f" {bpy.context.preferences.addons['cam'].preferences.new_version_available}") self.layout.operator("render.cam_update_now") @@ -81,10 +84,10 @@ def draw_opencamlib_version(self): return ocl_version = opencamlib_version() if ocl_version is None: - self.layout.label(text="Opencamlib is not installed") + self.layout.label(text="OpenCAMLib is not Installed") else: self.layout.label( - text=f"Opencamlib v{ocl_version} installed") + text=f"OpenCAMLib v{ocl_version}") # Display warnings related to the current operation def draw_op_warnings(self): @@ -101,7 +104,7 @@ def draw_op_time(self): if not int(self.op.info.duration * 60) > 0: return - time_estimate = f"Operation duration: {int(self.op.info.duration*60)}s " + time_estimate = f"Operation Duration: {int(self.op.info.duration*60)}s " if self.op.info.duration > 60: time_estimate += f" ({int(self.op.info.duration / 60)}h" time_estimate += f" {round(self.op.info.duration % 60)}min)" @@ -136,7 +139,7 @@ def draw_op_money_cost(self): cost_per_second = bpy.context.scene.cam_machine.hourly_rate / 3600 total_cost = self.op.info.duration * 60 * cost_per_second - op_cost = f"Operation cost: ${total_cost:.2f} (${cost_per_second:.2f}/s)" + op_cost = f"Operation Cost: ${total_cost:.2f} (${cost_per_second:.2f}/s)" self.layout.label(text=op_cost) # Display the Info Panel diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index 8e2b18689..b34c97c68 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -5,7 +5,7 @@ class CAM_MACHINE_Panel(CAMButtonsPanel, Panel): - """CAM machine panel""" + """CAM Machine Panel""" bl_label = "CAM Machine" bl_idname = "WORLD_PT_CAM_MACHINE" always_show_panel = True diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index 28ee78cfd..dd3075508 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -22,14 +22,14 @@ class CAM_MATERIAL_Properties(PropertyGroup): estimate_from_model: BoolProperty( - name="Estimate cut area from model", + name="Estimate Cut Area from Model", description="Estimate cut area based on model geometry", default=True, update=update_material, ) radius_around_model: FloatProperty( - name='Radius around model', + name='Radius Around Model', description="Increase cut area around the model on X and " "Y by this amount", default=0.0, @@ -39,21 +39,21 @@ class CAM_MATERIAL_Properties(PropertyGroup): ) center_x: BoolProperty( - name="Center on X axis", + name="Center on X Axis", description="Position model centered on X", default=False, update=update_material, ) center_y: BoolProperty( - name="Center on Y axis", + name="Center on Y Axis", description="Position model centered on Y", default=False, update=update_material, ) z_position: EnumProperty( - name="Z placement", + name="Z Placement", items=( ('ABOVE', 'Above', 'Place object vertically above the XY plane'), ('BELOW', 'Below', 'Place object vertically below the XY plane'), @@ -67,7 +67,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): # material_origin origin: FloatVectorProperty( - name='Material origin', + name='Material Origin', default=(0, 0, 0), unit='LENGTH', precision=PRECISION, @@ -77,7 +77,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): # material_size size: FloatVectorProperty( - name='Material size', + name='Material Size', default=(0.200, 0.200, 0.100), min=0, unit='LENGTH', @@ -92,7 +92,7 @@ class CAM_MATERIAL_Properties(PropertyGroup): class CAM_MATERIAL_PositionObject(Operator): bl_idname = "object.material_cam_position" - bl_label = "position object for CAM operation" + bl_label = "Position Object for CAM Operation" bl_options = {'REGISTER', 'UNDO'} interface_level = 0 @@ -102,7 +102,7 @@ def execute(self, context): if operation.object_name in bpy.data.objects: positionObject(operation) else: - print('no object assigned') + print('No Object Assigned') return {'FINISHED'} def draw(self, context): @@ -113,7 +113,7 @@ def draw(self, context): class CAM_MATERIAL_Panel(CAMButtonsPanel, Panel): - bl_label = "CAM Material size and position" + bl_label = "CAM Material Size and Position" bl_idname = "WORLD_PT_CAM_MATERIAL" panel_interface_level = 0 @@ -127,7 +127,7 @@ def draw_estimate_from_image(self): if not self.has_correct_level(): return if self.op.geometry_source not in ['OBJECT', 'COLLECTION']: - self.layout.label(text='Estimated from image') + self.layout.label(text='Estimated from Image') def draw_estimate_from_object(self): if not self.has_correct_level(): @@ -136,7 +136,7 @@ def draw_estimate_from_object(self): self.layout.prop(self.op.material, 'estimate_from_model') if self.op.material.estimate_from_model: row_radius = self.layout.row() - row_radius.label(text="Additional radius") + row_radius.label(text="Additional Radius") row_radius.prop(self.op.material, 'radius_around_model', text='') else: @@ -153,7 +153,7 @@ def draw_axis_alignment(self): row_axis.prop(self.op.material, 'center_y') self.layout.prop(self.op.material, 'z_position') self.layout.operator( - "object.material_cam_position", text="Position object") + "object.material_cam_position", text="Position Object") def draw(self, context): self.context = context diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index a29885528..3dc111c26 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -22,14 +22,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): # movement parallel_step_back type: EnumProperty( - name='Movement type', + name='Movement Type', items=( ('CONVENTIONAL', 'Conventional / Up milling', - 'cutter rotates against the direction of the feed'), + 'Cutter rotates against the direction of the feed'), ('CLIMB', 'Climb / Down milling', - 'cutter rotates with the direction of the feed'), + 'Cutter rotates with the direction of the feed'), ('MEANDER', 'Meander / Zig Zag', - 'cutting is done both with and against the ' + 'Cutting is done both with and against the ' 'rotation of the spindle') ), description='movement type', @@ -43,16 +43,16 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ('INSIDEOUT', 'Inside out', 'a'), ('OUTSIDEIN', 'Outside in', 'a') ), - description='approach to the piece', + description='Approach to the piece', default='INSIDEOUT', update=update_operation, ) spindle_rotation: EnumProperty( - name='Spindle rotation', + name='Spindle Rotation', items=( - ('CW', 'Clock wise', 'a'), - ('CCW', 'Counter clock wise', 'a') + ('CW', 'Clockwise', 'a'), + ('CCW', 'Counter clockwise', 'a') ), description='Spindle rotation direction', default='CW', @@ -60,7 +60,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) free_height: FloatProperty( - name="Free movement height", + name="Free Movement Height", default=0.01, min=0.0000, max=32, @@ -70,7 +70,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) useG64: BoolProperty( - name="G64 trajectory", + name="G64 Trajectory", description='Use only if your machine supports ' 'G64 code. LinuxCNC and Mach3 do', default=False, @@ -88,7 +88,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) parallel_step_back: BoolProperty( - name="Parallel step back", + name="Parallel Step Back", description='For roughing and finishing in one pass: mills ' 'material in climb mode, then steps back and goes ' 'between 2 last chunks back', @@ -97,14 +97,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) helix_enter: BoolProperty( - name="Helix enter - EXPERIMENTAL", + name="Helix Enter - EXPERIMENTAL", description="Enter material in helix", default=False, update=update_operation, ) ramp_in_angle: FloatProperty( - name="Ramp in angle", + name="Ramp-in Angle", default=pi / 6, min=0, max=pi * 0.4999, @@ -115,7 +115,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) helix_diameter: FloatProperty( - name='Helix diameter - % of cutter diameter', + name='Helix Diameter - % of Cutter Diameter', default=90, min=10, max=100, @@ -125,7 +125,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) ramp: BoolProperty( - name="Ramp in - EXPERIMENTAL", + name="Ramp-in - EXPERIMENTAL", description="Ramps down the whole contour, so the cutline looks " "like helix", default=False, @@ -133,14 +133,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) ramp_out: BoolProperty( - name="Ramp out - EXPERIMENTAL", + name="Ramp-out - EXPERIMENTAL", description="Ramp out to not leave mark on surface", default=False, update=update_operation, ) ramp_out_angle: FloatProperty( - name="Ramp out angle", + name="Ramp-out Angle", default=pi / 6, min=0, max=pi * 0.4999, @@ -151,14 +151,14 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) retract_tangential: BoolProperty( - name="Retract tangential - EXPERIMENTAL", + name="Retract Tangential - EXPERIMENTAL", description="Retract from material in circular motion", default=False, update=update_operation, ) retract_radius: FloatProperty( - name='Retract arc radius', + name='Retract Arc Radius', default=0.001, min=0.000001, max=100, @@ -168,7 +168,7 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) retract_height: FloatProperty( - name='Retract arc height', + name='Retract Arc Height', default=0.001, min=0.00000, max=100, @@ -178,13 +178,13 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) stay_low: BoolProperty( - name="Stay low if possible", + name="Stay Low if Possible", default=True, update=update_operation, ) merge_dist: FloatProperty( - name="Merge distance - EXPERIMENTAL", + name="Merge Distance - EXPERIMENTAL", default=0.0, min=0.0000, max=0.1, @@ -194,15 +194,15 @@ class CAM_MOVEMENT_Properties(PropertyGroup): ) protect_vertical: BoolProperty( - name="Protect vertical", + name="Protect Vertical", description="The path goes only vertically next to steep areas", default=True, update=update_operation, ) protect_vertical_limit: FloatProperty( - name="Verticality limit", - description="What angle is allready considered vertical", + name="Verticality Limit", + description="What angle is already considered vertical", default=pi / 45, min=0, max=pi * 0.5, @@ -214,8 +214,8 @@ class CAM_MOVEMENT_Properties(PropertyGroup): class CAM_MOVEMENT_Panel(CAMButtonsPanel, Panel): - """CAM movement panel""" - bl_label = "CAM movement" + """CAM Movement Panel""" + bl_label = "CAM Movement" bl_idname = "WORLD_PT_CAM_MOVEMENT" panel_interface_level = 0 @@ -249,7 +249,7 @@ def draw_free_height(self): return self.layout.prop(self.op.movement, 'free_height') if self.op.maxz > self.op.movement.free_height: - self.layout.label(text='Depth start > Free movement') + self.layout.label(text='Depth Start > Free Movement') self.layout.label(text='POSSIBLE COLLISION') def draw_use_g64(self): diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index dc52da053..ef1e2f066 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -5,8 +5,8 @@ class CAM_OPERATION_PROPERTIES_Panel(CAMButtonsPanel, Panel): - """CAM operation properties panel""" - bl_label = "CAM operation setup" + """CAM Operation Properties Panel""" + bl_label = "CAM Operation Setup" bl_idname = "WORLD_PT_CAM_OPERATION" panel_interface_level = 0 @@ -44,9 +44,9 @@ def draw_cutter_engagement(self): engagement = round(100 * self.op.dist_between_paths / self.op.cutter_diameter, 1) if engagement > 50: - self.layout.label(text="Warning: High cutter engagement") + self.layout.label(text="Warning: High Cutter Engagement") - self.layout.label(text=f"Cutter engagement: {engagement}%") + self.layout.label(text=f"Cutter Engagement: {engagement}%") def draw_machine_axis(self): if not self.has_correct_level(): @@ -139,14 +139,14 @@ def draw_waterline_options(self): if not self.has_correct_level(): return if self.op.strategy in ['WATERLINE']: - self.layout.label(text="OCL doesn't support fill areas") + self.layout.label(text="Ocl Doesn't Support Fill Areas") if not self.op.optimisation.use_opencamlib: self.layout.prop(self.op, 'slice_detail') self.layout.prop(self.op, 'waterline_fill') if self.op.waterline_fill: self.layout.prop(self.op, 'dist_between_paths') self.layout.prop(self.op, 'waterline_project') - self.layout.label(text="Waterline needs a skin margin") + self.layout.label(text="Waterline Needs a Skin Margin") def draw_carve_options(self): if not self.has_correct_level(): @@ -203,7 +203,7 @@ def draw_bridges_options(self): self.layout.prop(self.op, 'bridges_height') self.layout.prop_search(self.op, "bridges_collection_name", bpy.data, "collections") self.layout.prop(self.op, 'use_bridge_modifiers') - self.layout.operator("scene.cam_bridges_add", text="Autogenerate bridges") + self.layout.operator("scene.cam_bridges_add", text="Autogenerate Bridges / Tabs") def draw_skin(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index 7fa38ef27..d5d8ca5c9 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -14,8 +14,8 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, Panel): - """CAM operations panel""" - bl_label = "CAM operations" + """CAM Operations Panel""" + bl_label = "CAM Operations" bl_idname = "WORLD_PT_CAM_OPERATIONS" always_show_panel = True panel_interface_level = 0 @@ -60,14 +60,14 @@ def draw_calculate_path(self): return if self.op.maxz > self.op.movement.free_height: self.layout.label(text='!ERROR! COLLISION!') - self.layout.label(text='Depth start > Free movement height') + self.layout.label(text='Depth Start > Free Movement Height') self.layout.label(text='!ERROR! COLLISION!') self.layout.prop(self.op.movement, 'free_height') if not self.op.valid: - self.layout.label(text="Select a valid object to calculate the path.") + self.layout.label(text="Select a Valid Object to Calculate the Path.") # will be disable if not valid - self.layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode") + self.layout.operator("object.calculate_cam_path", text="Calculate Path & Export Gcode") def draw_export_gcode(self): if not self.has_correct_level(): @@ -82,7 +82,7 @@ def draw_simulate_op(self): if not self.has_correct_level(): return if self.op.valid: - self.layout.operator("object.cam_simulate", text="Simulate this operation") + self.layout.operator("object.cam_simulate", text="Simulate This Operation") def draw_op_name(self): if not self.has_correct_level(): diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index 4b6490119..39bcdd416 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -23,14 +23,14 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): optimize: BoolProperty( - name="Reduce path points", + name="Reduce Path Points", description="Reduce path points", default=True, update=update_operation, ) optimize_threshold: FloatProperty( - name="Reduction threshold in μm", + name="Reduction Threshold in μm", default=.2, min=0.000000001, max=1000, @@ -39,7 +39,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) use_exact: BoolProperty( - name="Use exact mode", + name="Use Exact Mode", description="Exact mode allows greater precision, but is slower " "with complex meshes", default=True, @@ -47,7 +47,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) imgres_limit: IntProperty( - name="Maximum resolution in megapixels", + name="Maximum Resolution in Megapixels", default=16, min=1, max=512, @@ -57,7 +57,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) pixsize: FloatProperty( - name="sampling raster detail", + name="Sampling Raster Detail", default=0.0001, min=0.00001, max=0.1, @@ -74,7 +74,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) exact_subdivide_edges: BoolProperty( - name="Auto subdivide long edges", + name="Auto Subdivide Long Edges", description="This can avoid some collision issues when " "importing CAD models", default=False, @@ -82,7 +82,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) circle_detail: IntProperty( - name="Detail of circles used for curve offsets", + name="Detail of Circles Used for Curve Offsets", default=64, min=12, max=512, @@ -90,7 +90,7 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): ) simulation_detail: FloatProperty( - name="Simulation sampling raster detail", + name="Simulation Sampling Raster Detail", default=0.0002, min=0.00001, max=0.01, @@ -101,8 +101,8 @@ class CAM_OPTIMISATION_Properties(PropertyGroup): class CAM_OPTIMISATION_Panel(CAMButtonsPanel, Panel): - """CAM optimisation panel""" - bl_label = "CAM optimisation" + """CAM Optimisation Panel""" + bl_label = "CAM Optimisation" bl_idname = "WORLD_PT_CAM_OPTIMISATION" panel_interface_level = 2 @@ -150,7 +150,7 @@ def draw_use_opencamlib(self): ocl_version = opencamlib_version() if ocl_version is None: - self.layout.label(text="Opencamlib is not available ") + self.layout.label(text="OpenCAMLib is not Available ") self.layout.prop(self.op.optimisation, 'exact_subdivide_edges') else: self.layout.prop(self.op.optimisation, 'use_opencamlib') diff --git a/scripts/addons/cam/ui_panels/pack.py b/scripts/addons/cam/ui_panels/pack.py index 6d2f3767e..105c30923 100644 --- a/scripts/addons/cam/ui_panels/pack.py +++ b/scripts/addons/cam/ui_panels/pack.py @@ -5,8 +5,8 @@ class CAM_PACK_Panel(CAMButtonsPanel, Panel): - """CAM material panel""" - bl_label = "Pack curves on sheet" + """CAM Pack Panel""" + bl_label = "Pack Curves on Sheet" bl_idname = "WORLD_PT_CAM_PACK" panel_interface_level = 2 @@ -16,8 +16,8 @@ def draw(self, context): layout = self.layout scene = bpy.context.scene settings = scene.cam_pack - layout.label(text='warning - algorithm is slow.') - layout.label(text='only for curves now.') + layout.label(text='Warning - Algorithm Is Slow.') + layout.label(text='Only for Curves Now.') layout.operator("object.cam_pack_objects") layout.prop(settings, 'sheet_fill_direction') diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index 10ef67029..8a1daebb7 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -5,8 +5,8 @@ class CAM_SLICE_Panel(CAMButtonsPanel, Panel): - """CAM slicer panel""" - bl_label = "Slice model to plywood sheets" + """CAM Slicer Panel""" + bl_label = "Slice Model to Plywood Sheets" bl_idname = "WORLD_PT_CAM_SLICE" panel_interface_level = 2 diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 476e7ad16..750573de9 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -68,7 +68,7 @@ oclSample, oclResampleChunks, ) -from .polygon_utils_cam import shapelyToCurve +from .polygon_utils_cam import shapelyToCurve, shapelyToMultipolygon from .simple import ( activate, progress, @@ -287,21 +287,21 @@ def getOperationSources(o): def getBounds(o): # print('kolikrat sem rpijde') if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION' or o.geometry_source == 'CURVE': - print("valid geometry") + print("Valid Geometry") minx, miny, minz, maxx, maxy, maxz = getBoundsWorldspace(o.objects, o.use_modifiers) if o.minz_from == 'OBJECT': if minz == 10000000: minz = 0 - print("minz from object:" + str(minz)) + print("Minz from Object:" + str(minz)) o.min.z = minz o.minz = o.min.z else: o.min.z = o.minz # max(bb[0][2]+l.z,o.minz)# - print("not minz from object") + print("Not Minz from Object") if o.material.estimate_from_model: - print("Estimate material from model") + print("Estimate Material from Model") o.min.x = minx - o.material.radius_around_model o.min.y = miny - o.material.radius_around_model @@ -310,7 +310,7 @@ def getBounds(o): o.max.x = maxx + o.material.radius_around_model o.max.y = maxy + o.material.radius_around_model else: - print("not material from model") + print("Not Material from Model") o.min.x = o.material.origin.x o.min.y = o.material.origin.y o.min.z = o.material.origin.z - o.material.size.z @@ -342,14 +342,14 @@ def getBounds(o): s = bpy.context.scene m = s.cam_machine # make sure this message only shows once and goes away once fixed - o.info.warnings.replace('Operation exceeds your machine limits\n', '') + o.info.warnings.replace('Operation Exceeds Your Machine Limits\n', '') if o.max.x - o.min.x > m.working_area.x or o.max.y - o.min.y > m.working_area.y \ or o.max.z - o.min.z > m.working_area.z: - o.info.warnings += 'Operation exceeds your machine limits\n' + o.info.warnings += 'Operation Exceeds Your Machine Limits\n' def getBoundsMultiple(operations): - """gets bounds of multiple operations, mainly for purpose of simulations or rest milling. highly suboptimal.""" + """Gets Bounds of Multiple Operations, Mainly for Purpose of Simulations or Rest Milling. Highly Suboptimal.""" maxx = maxy = maxz = -10000000 minx = miny = minz = 10000000 for o in operations: @@ -458,7 +458,7 @@ async def sampleChunks(o, pathSamples, layers): ob = bpy.data.objects[o.object_name] zinvert = ob.location.z + maxz # ob.bound_box[6][2] - print(f"Total sample points {totlen}") + print(f"Total Sample Points {totlen}") n = 0 last_percent = -1 @@ -616,7 +616,7 @@ async def sampleChunks(o, pathSamples, layers): lastrunchunks = thisrunchunks # print(len(layerchunks[i])) - progress('checking relations between paths') + progress('Checking Relations Between Paths') timingstart(sortingtime) if o.strategy == 'PARALLEL' or o.strategy == 'CROSS' or o.strategy == 'OUTLINEFILL': @@ -665,7 +665,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): cutterdepth = cutter.dimensions.z / 2 t = time.time() - print('sampling paths') + print('Sampling Paths') totlen = 0 # total length of all chunks, to estimate sampling time. for chs in pathSamples: @@ -868,7 +868,7 @@ async def sampleChunksNAxis(o, pathSamples, layers): # print(len(layerchunks[i])) - progress('checking relations between paths') + progress('Checking Relations Between Paths') """#this algorithm should also work for n-axis, but now is "sleeping" if (o.strategy=='PARALLEL' or o.strategy=='CROSS'): if len(layers)>1:# sorting help so that upper layers go first always @@ -1025,7 +1025,7 @@ def overlaps(bb1, bb2): # true if bb1 is child of bb2 async def connectChunksLow(chunks, o): - """ connects chunks that are close to each other without lifting, sampling them 'low' """ + """ Connects Chunks that Are Close to Each Other without Lifting, Sampling Them 'low' """ if not o.movement.stay_low or (o.strategy == 'CARVE' and o.carve_depth > 0): return chunks @@ -1183,7 +1183,7 @@ def getVectorRight(lastv, verts): def cleanUpDict(ndict): # now it should delete all junk first, iterate over lonely verts. - print('removing lonely points') + print('Removing Lonely Points') # found_solitaires=True # while found_solitaires: found_solitaires = False @@ -1234,8 +1234,8 @@ def cutloops(csource, parentloop, loops): def getOperationSilhouete(operation): - """gets silhouete for the operation - uses image thresholding for everything except curves. + """Gets Silhouette for the Operation + Uses Image Thresholding for Everything Except Curves. """ if operation.update_silhouete_tag: image = None @@ -1255,7 +1255,7 @@ def getOperationSilhouete(operation): totfaces += len(ob.data.polygons) if (stype == 'OBJECTS' and totfaces > 200000) or stype == 'IMAGE': - print('image method') + print('Image Method') samples = renderSampleImage(operation) if stype == 'OBJECTS': i = samples > operation.minz - 0.0000001 @@ -1296,7 +1296,7 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if totfaces < 20000000: # boolean polygons method originaly was 20 000 poly limit, now limitless, # it might become teribly slow, but who cares? t = time.time() - print('shapely getting silhouette') + print('Shapely Getting Silhouette') polys = [] for ob in objects: if use_modifiers: @@ -1334,7 +1334,7 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if totfaces < 20000: p = sops.unary_union(polys) else: - print('computing in parts') + print('Computing in Parts') bigshapes = [] i = 1 part = 20000 @@ -1346,13 +1346,13 @@ def getObjectSilhouete(stype, objects=None, use_modifiers=False): if (i - 1) * part < totfaces: last_ar = polys[(i - 1) * part:] bigshapes.append(sops.unary_union(last_ar)) - print('joining') + print('Joining') p = sops.unary_union(bigshapes) print(time.time() - t) t = time.time() - silhouete = [p] # [polygon_utils_cam.Shapely2Polygon(p)] + silhouete = shapelyToMultipolygon(p) # [polygon_utils_cam.Shapely2Polygon(p)] return silhouete @@ -1427,7 +1427,7 @@ def getObjectOutline(radius, o, Offset): # FIXME: make this one operation indep def addOrientationObject(o): - """the orientation object should be used to set up orientations of the object for 4 and 5 axis milling.""" + """The Orientation Object Should Be Used to Set up Orientations of the Object for 4 and 5 Axis Milling.""" name = o.name + ' orientation' s = bpy.context.scene if s.objects.find(name) == -1: @@ -1588,7 +1588,7 @@ def __init__(self, x, y, z): def unique(L): - """Return a list of unhashable elements in s, but without duplicates. + """Return a List of Unhashable Elements in S, but without Duplicates. [[1, 2], [2, 3], [1, 2]] >>> [[1, 2], [2, 3]]""" # For unhashable objects, you can sort the sequence and then scan from the end of the list, # deleting duplicates as you go @@ -1684,9 +1684,9 @@ def cleanupIndexed(operation): def rotTo2axes(e, axescombination): - """converts an orientation object rotation to rotation defined by 2 rotational axes on the machine - - for indexed machining. - attempting to do this for all axes combinations. + """Converts an Orientation Object Rotation to Rotation Defined by 2 Rotational Axes on the Machine - + for Indexed Machining. + Attempting to Do This for All Axes Combinations. """ v = Vector((0, 0, 1)) v.rotate(e) @@ -1813,13 +1813,13 @@ def reload_paths(o): def updateMachine(self, context): - print('update machine ') + print('Update Machine') if not _IS_LOADING_DEFAULTS: addMachineAreaObject() def updateMaterial(self, context): - print('update material') + print('Update Material') addMaterialAreaObject() @@ -1885,7 +1885,7 @@ def operationValid(self, context): o = scene.cam_operations[scene.cam_active_operation] o.changed = True o.valid = isValid(o, context) - invalidmsg = "Invalid source object for operation.\n" + invalidmsg = "Invalid Source Object for Operation.\n" if o.valid: o.info.warnings = "" else: @@ -1908,9 +1908,9 @@ def isChainValid(chain, context): if so.name == cho.name: found_op = so if found_op == None: - return (False, f"Couldn't find operation {cho.name}") + return (False, f"Couldn't Find Operation {cho.name}") if isValid(found_op, context) is False: - return (False, f"Operation {found_op.name} is not valid") + return (False, f"Operation {found_op.name} Is Not Valid") return (True, "") @@ -1920,8 +1920,8 @@ def updateOperationValid(self, context): # Update functions start here def updateChipload(self, context): - """this is very simple computation of chip size, could be very much improved""" - print('update chipload ') + """This Is Very Simple Computation of Chip Size, Could Be Very Much Improved""" + print('Update Chipload ') o = self # Old chipload o.info.chipload = (o.feedrate / (o.spindle_rpm * o.cutter_flutes)) @@ -1942,15 +1942,15 @@ def updateChipload(self, context): def updateOffsetImage(self, context): - """refresh offset image tag for rerendering""" + """Refresh Offset Image Tag for Rerendering""" updateChipload(self, context) - print('update offset') + print('Update Offset') self.changed = True self.update_offsetimage_tag = True def updateZbufferImage(self, context): - """changes tags so offset and zbuffer images get updated on calculation time.""" + """Changes Tags so Offset and Zbuffer Images Get Updated on Calculation Time.""" # print('updatezbuf') # print(self,context) self.changed = True @@ -1962,7 +1962,7 @@ def updateZbufferImage(self, context): def updateStrategy(o, context): """""" o.changed = True - print('update strategy') + print('Update Strategy') if o.machine_axes == '5' or ( o.machine_axes == '4' and o.strategy4axis == 'INDEXED'): # INDEXED 4 AXIS DOESN'T EXIST NOW... addOrientationObject(o) @@ -1976,35 +1976,35 @@ def updateCutout(o, context): def updateExact(o, context): - print('update exact ') + print('Update Exact ') o.changed = True o.update_zbufferimage_tag = True o.update_offsetimage_tag = True if o.optimisation.use_exact: if o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS' or o.inverse: o.optimisation.use_opencamlib = False - print('Current operation cannot use exact mode') + print('Current Operation Cannot Use Exact Mode') else: o.optimisation.use_opencamlib = False def updateOpencamlib(o, context): - print('update opencamlib ') + print('Update OpenCAMLib ') o.changed = True if o.optimisation.use_opencamlib and ( o.strategy == 'POCKET' or o.strategy == 'MEDIAL_AXIS'): o.optimisation.use_exact = False o.optimisation.use_opencamlib = False - print('Current operation cannot use opencamlib') + print('Current Operation Cannot Use OpenCAMLib') def updateBridges(o, context): - print('update bridges ') + print('Update Bridges ') o.changed = True def updateRotation(o, context): - print('update rotation') + print('Update Rotation') if o.enable_B or o.enable_A: print(o, o.rotation_A) ob = bpy.data.objects[o.object_name] @@ -2029,7 +2029,7 @@ def updateRotation(o, context): # o.changed = True def updateRest(o, context): - print('update rest ') + print('Update Rest ') o.changed = True @@ -2101,7 +2101,7 @@ def update_zbuffer_image(self, context): @bpy.app.handlers.persistent def check_operations_on_load(context): - """checks any broken computations on load and reset them.""" + """Checks Any Broken Computations on Load and Reset Them.""" s = bpy.context.scene for o in s.cam_operations: if o.computing: @@ -2113,7 +2113,7 @@ def check_operations_on_load(context): machine_preset = bpy.context.preferences.addons[ "cam"].preferences.machine_preset = bpy.context.preferences.addons["cam"].preferences.default_machine_preset if len(machine_preset) > 0: - print("Loading preset:", machine_preset) + print("Loading Preset:", machine_preset) # load last used machine preset bpy.ops.script.execute_preset( filepath=machine_preset, menu_idname="CAM_MACHINE_MT_presets" diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index eb9ecdf17..32f985d18 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -581,16 +581,16 @@ def __init__(self, edge=None, pm=Edge.LE): def dump(self): print("Halfedge--------------------------") - print("left: ", self.left) - print("right: ", self.right) - print("edge: ", self.edge) - print("pm: ", self.pm) - print("vertex: "), + print("Left: ", self.left) + print("Right: ", self.right) + print("Edge: ", self.edge) + print("PM: ", self.pm) + print("Vertex: "), if self.vertex: self.vertex.dump() else: print("None") - print("ystar: ", self.ystar) + print("Ystar: ", self.ystar) def __lt__(self, other): if self.ystar < other.ystar: From 32a30b6e0be704c121fcaaba8f7f034f6d122c86 Mon Sep 17 00:00:00 2001 From: Release robot Date: Thu, 11 Apr 2024 20:19:18 +0000 Subject: [PATCH 073/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 593d1c898..1a76c6eb9 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "CAM - gcode generation tools", "author": "Vilem Novak & Contributors", - "version":(1,0,17), + "version":(1,0,18), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate machining paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 98d021a92..ba1cf70ff 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,17) \ No newline at end of file +__version__=(1,0,18) \ No newline at end of file From 94015bcd373efab1256097ee8d0d2048c8d0937b Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Fri, 12 Apr 2024 12:06:22 -0400 Subject: [PATCH 074/100] Pie Menu, Curve Create Name Adjustment ## Pie Menu - bound to 'Alt + C' by default - 2 main levels - CAM - Machine, Tools, Material, Info - Operation - Feedrate, Cutter, Setup etc - navigate between all levels with 'Home' and 'Back' buttons - most, if not all functionality from the ui_panels has been replicated here ## Curve Create Names - most operations were named after the objects they created - a few were named 'Create...' e.g. 'Create Lissajous' - these have been renamed to remove the word 'Create', making them consistent with other Curve Creator names --- scripts/addons/cam/__init__.py | 60 ++++++- scripts/addons/cam/curvecamequation.py | 8 +- .../addons/cam/pie_menu/active_op/pie_area.py | 90 ++++++++++ .../cam/pie_menu/active_op/pie_cutter.py | 104 +++++++++++ .../cam/pie_menu/active_op/pie_feedrate.py | 47 +++++ .../cam/pie_menu/active_op/pie_gcode.py | 63 +++++++ .../cam/pie_menu/active_op/pie_movement.py | 87 +++++++++ .../cam/pie_menu/active_op/pie_operation.py | 76 ++++++++ .../pie_menu/active_op/pie_optimisation.py | 88 +++++++++ .../cam/pie_menu/active_op/pie_setup.py | 167 ++++++++++++++++++ scripts/addons/cam/pie_menu/pie_cam.py | 91 ++++++++++ scripts/addons/cam/pie_menu/pie_chains.py | 157 ++++++++++++++++ .../addons/cam/pie_menu/pie_curvecreators.py | 49 +++++ scripts/addons/cam/pie_menu/pie_curvetools.py | 46 +++++ scripts/addons/cam/pie_menu/pie_info.py | 100 +++++++++++ scripts/addons/cam/pie_menu/pie_machine.py | 77 ++++++++ scripts/addons/cam/pie_menu/pie_material.py | 60 +++++++ .../cam/pie_menu/pie_pack_slice_relief.py | 111 ++++++++++++ 18 files changed, 1474 insertions(+), 7 deletions(-) create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_area.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_cutter.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_feedrate.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_gcode.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_movement.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_operation.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_optimisation.py create mode 100644 scripts/addons/cam/pie_menu/active_op/pie_setup.py create mode 100644 scripts/addons/cam/pie_menu/pie_cam.py create mode 100644 scripts/addons/cam/pie_menu/pie_chains.py create mode 100644 scripts/addons/cam/pie_menu/pie_curvecreators.py create mode 100644 scripts/addons/cam/pie_menu/pie_curvetools.py create mode 100644 scripts/addons/cam/pie_menu/pie_info.py create mode 100644 scripts/addons/cam/pie_menu/pie_machine.py create mode 100644 scripts/addons/cam/pie_menu/pie_material.py create mode 100644 scripts/addons/cam/pie_menu/pie_pack_slice_relief.py diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 1a76c6eb9..349eb8e8c 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -122,6 +122,22 @@ timer_update, ) from .pack import PackObjectsSettings +from .pie_menu.pie_cam import VIEW3D_MT_PIE_CAM +from .pie_menu.pie_chains import VIEW3D_MT_PIE_Chains +from .pie_menu.pie_curvecreators import VIEW3D_MT_PIE_CurveCreators +from .pie_menu.pie_curvetools import VIEW3D_MT_PIE_CurveTools +from .pie_menu.pie_info import VIEW3D_MT_PIE_Info +from .pie_menu.pie_machine import VIEW3D_MT_PIE_Machine +from .pie_menu.pie_material import VIEW3D_MT_PIE_Material +from .pie_menu.pie_pack_slice_relief import VIEW3D_MT_PIE_PackSliceRelief +from .pie_menu.active_op.pie_area import VIEW3D_MT_PIE_Area +from .pie_menu.active_op.pie_cutter import VIEW3D_MT_PIE_Cutter +from .pie_menu.active_op.pie_feedrate import VIEW3D_MT_PIE_Feedrate +from .pie_menu.active_op.pie_gcode import VIEW3D_MT_PIE_Gcode +from .pie_menu.active_op.pie_movement import VIEW3D_MT_PIE_Movement +from .pie_menu.active_op.pie_operation import VIEW3D_MT_PIE_Operation +from .pie_menu.active_op.pie_optimisation import VIEW3D_MT_PIE_Optimisation +from .pie_menu.active_op.pie_setup import VIEW3D_MT_PIE_Setup from .preferences import CamAddonPreferences from .preset_managers import ( AddPresetCamCutter, @@ -181,12 +197,12 @@ bl_info = { - "name": "CAM - gcode generation tools", + "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,18), + "version": (1, 0, 18), "blender": (3, 6, 0), "location": "Properties > render", - "description": "Generate machining paths for CNC", + "description": "Generate Machining Paths for CNC", "warning": "", "doc_url": "https://blendercam.com/", "tracker_url": "", @@ -314,6 +330,24 @@ CustomPanel, WM_OT_gcode_import, + # .pie_menu and .pie_menu.active_op - placed after .ui in case inheritance is possible + VIEW3D_MT_PIE_CAM, + VIEW3D_MT_PIE_Machine, + VIEW3D_MT_PIE_Material, + VIEW3D_MT_PIE_Operation, + VIEW3D_MT_PIE_Chains, + VIEW3D_MT_PIE_Setup, + VIEW3D_MT_PIE_Optimisation, + VIEW3D_MT_PIE_Area, + VIEW3D_MT_PIE_Movement, + VIEW3D_MT_PIE_Feedrate, + VIEW3D_MT_PIE_Cutter, + VIEW3D_MT_PIE_Gcode, + VIEW3D_MT_PIE_Info, + VIEW3D_MT_PIE_PackSliceRelief, + VIEW3D_MT_PIE_CurveCreators, + VIEW3D_MT_PIE_CurveTools, + # .cam_operation - last to allow dependencies to register before it camOperation, ] @@ -366,6 +400,19 @@ def register() -> None: for panel in get_panels(): panel.COMPAT_ENGINES.add("BLENDERCAM_RENDER") + wm = bpy.context.window_manager + addon_kc = wm.keyconfigs.addon + + km = addon_kc.keymaps.new(name='Object Mode') + kmi = km.keymap_items.new( + "wm.call_menu_pie", + 'C', + 'PRESS', + alt=True, + ) + kmi.properties.name = 'VIEW3D_MT_PIE_CAM' + kmi.active = True + def unregister() -> None: for cls in classes: @@ -389,3 +436,10 @@ def unregister() -> None: for panel in get_panels(): if 'BLENDERCAM_RENDER' in panel.COMPAT_ENGINES: panel.COMPAT_ENGINES.remove('BLENDERCAM_RENDER') + + wm = bpy.context.window_manager + active_kc = wm.keyconfigs.active + + for key in active_kc.keymaps['Object Mode'].keymap_items: + if (key.idname == 'wm.call_menu' and key.properties.name == 'VIEW3D_MT_PIE_CAM'): + active_kc.keymaps['Object Mode'].keymap_items.remove(key) diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index d8ddb20d6..c30e2b4d2 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -37,7 +37,7 @@ class CamSineCurve(bpy.types.Operator): """Object Sine """ # by Alain Pelletier april 2021 bl_idname = "object.sine" - bl_label = "Create Periodic Wave" + bl_label = "Periodic Wave" bl_options = {'REGISTER', 'UNDO', 'PRESET'} # zstring: StringProperty(name="Z equation", description="Equation for z=F(u,v)", default="0.05*sin(2*pi*4*t)" ) @@ -195,7 +195,7 @@ def f(t, offset: float = 0.0, angle_offset: float = 0.0): class CamLissajousCurve(bpy.types.Operator): """Lissajous """ # by Alain Pelletier april 2021 bl_idname = "object.lissajous" - bl_label = "Create Lissajous Figure" + bl_label = "Lissajous Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} amplitude_A: FloatProperty( @@ -332,7 +332,7 @@ def f(t, offset: float = 0.0): class CamHypotrochoidCurve(bpy.types.Operator): """Hypotrochoid """ # by Alain Pelletier april 2021 bl_idname = "object.hypotrochoid" - bl_label = "Create Spirograph Type Figure" + bl_label = "Spirograph Type Figure" bl_options = {'REGISTER', 'UNDO', 'PRESET'} typecurve: EnumProperty( @@ -426,7 +426,7 @@ def f(t, offset: float = 0.0): class CamCustomCurve(bpy.types.Operator): """Object Custom Curve """ # by Alain Pelletier april 2021 bl_idname = "object.customcurve" - bl_label = "Create Custom Curve" + bl_label = "Custom Curve" bl_options = {'REGISTER', 'UNDO', 'PRESET'} xstring: StringProperty( diff --git a/scripts/addons/cam/pie_menu/active_op/pie_area.py b/scripts/addons/cam/pie_menu/active_op/pie_area.py new file mode 100644 index 000000000..c9657b93a --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_area.py @@ -0,0 +1,90 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Area(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Area ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + col = column.column(align=True) + row = col.row(align=True) + row.prop(operation, 'use_layers') + if operation.use_layers: + row.prop(operation, 'stepdown') + + if operation.strategy in ['CUTOUT', 'POCKET', 'MEDIAL_AXIS']: + row = col.row(align=True) + row.label(text="") + row.prop(operation, 'first_down') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.geometry_source in ['OBJECT', 'COLLECTION']: + if operation.strategy == 'CURVE': + column.label(text="Cannot Use Depth from Object Using Curves") + + row = column.row(align=True) + row.label(text='Max Depth from') + row.prop(operation, 'minz_from', text='') + if operation.minz_from == 'CUSTOM': + column.prop(operation, 'minz') + + else: + column.prop(operation, 'source_image_scale_z') + column.prop(operation, 'source_image_size_x') + if operation.source_image_name != '': + i = bpy.data.images[operation.source_image_name] + if i is not None: + sy = int((operation.source_image_size_x / + i.size[0]) * i.size[1] * 1000000) / 1000 + column.label(text='Image Size on Y Axis: ' + strInUnits(sy, 8)) + column.separator() + column.prop(operation, 'source_image_offset') + col = column.column(align=True) + col.prop(operation, 'source_image_crop', text='Crop Source Image') + if operation.source_image_crop: + col.prop(operation, 'source_image_crop_start_x', text='start x') + col.prop(operation, 'source_image_crop_start_y', text='start y') + col.prop(operation, 'source_image_crop_end_x', text='end x') + col.prop(operation, 'source_image_crop_end_y', text='end y') + + # Bottom + box = pie.box() + column = box.column(align=True) + if operation.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: + column.prop(operation, 'ambient_behaviour') + if operation.ambient_behaviour == 'AROUND': + column.prop(operation, 'ambient_radius') + column.prop(operation, "ambient_cutter_restrict") + + if operation.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES', 'PARALLEL', 'CROSS']: + column.prop(operation, 'use_limit_curve') + if operation.use_limit_curve: + column.prop_search(operation, "limit_curve", bpy.data, "objects") + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_cutter.py b/scripts/addons/cam/pie_menu/active_op/pie_cutter.py new file mode 100644 index 000000000..0c8ba245c --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_cutter.py @@ -0,0 +1,104 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Cutter(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Cutter ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + row = column.row(align=True) + row.menu("CAM_CUTTER_MT_presets", text=bpy.types.CAM_CUTTER_MT_presets.bl_label) + row.operator("render.cam_preset_cutter_add", text="", icon='ADD') + row.operator("render.cam_preset_cutter_add", text="", icon='REMOVE').remove_active = True + column.prop(operation, 'cutter_id') + + column.prop(operation, 'cutter_type') + + if operation.cutter_type in ['BALLCONE']: + column.prop(operation, 'ball_radius') + + if operation.cutter_type in ['BULLNOSE']: + column.prop(operation, 'bull_corner_radius') + + if operation.cutter_type in ['CYLCONE']: + column.prop(operation, 'cylcone_diameter') + + if operation.cutter_type in ['VCARVE', 'BALLCONE', 'BULLNOSE', 'CYLCONE']: + column.prop(operation, 'cutter_tip_angle') + + if operation.cutter_type in ['LASER']: + column.prop(operation, 'Laser_on') + column.prop(operation, 'Laser_off') + column.prop(operation, 'Laser_cmd') + column.prop(operation, 'Laser_delay') + + if operation.cutter_type in ['PLASMA']: + column.prop(operation, 'Plasma_on') + column.prop(operation, 'Plasma_off') + column.prop(operation, 'Plasma_delay') + column.prop(operation, 'Plasma_dwell') + column.prop(operation, 'lead_in') + column.prop(operation, 'lead_out') + + if operation.cutter_type in ['CUSTOM']: + if operation.optimisation.use_exact: + column.label( + text='Warning - only Convex Shapes Are Supported. ', icon='COLOR_RED') + column.label(text='If Your Custom Cutter Is Concave,') + column.label(text='Switch Exact Mode Off.') + column.prop_search(operation, "cutter_object_name", bpy.data, "objects") + + column.prop(operation, 'cutter_diameter') + + if operation.cutter_type not in ['LASER', 'PLASMA']: + column.prop(operation, 'cutter_flutes') + + column.prop(operation, 'cutter_description') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.cutter_type in ['LASER', 'PLASMA']: + return + if operation.strategy in ['CUTOUT']: + return + + if operation.cutter_type in ['BALLCONE']: + engagement = round(100 * operation.dist_between_paths / operation.ball_radius, 1) + else: + engagement = round(100 * operation.dist_between_paths / operation.cutter_diameter, 1) + + column.label(text=f"Cutter Engagement: {engagement}%") + + if engagement > 50: + column.label(text="WARNING: CUTTER ENGAGEMENT > 50%") + + # Bottom + row = pie.row() + row.label(text='') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py b/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py new file mode 100644 index 000000000..6cd2cadde --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py @@ -0,0 +1,47 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Feedrate(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Feedrate ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + column.prop(operation, 'feedrate') + column.prop(operation, 'do_simulation_feedrate') + + # Right + box = pie.box() + column = box.column(align=True) + column.prop(operation, 'plunge_feedrate') + column.prop(operation, 'plunge_angle') + column.prop(operation, 'spindle_rpm') + + # Bottom + row = pie.row() + row.label(text='') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_gcode.py b/scripts/addons/cam/pie_menu/active_op/pie_gcode.py new file mode 100644 index 000000000..6e4b88cb8 --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_gcode.py @@ -0,0 +1,63 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Gcode(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation G-code ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + column.prop(operation, 'output_header') + if operation.output_header: + column.prop(operation, 'gcode_header') + + column.prop(operation, 'output_trailer') + if operation.output_trailer: + column.prop(operation, 'gcode_trailer') + + # Right + box = pie.box() + column = box.column(align=True) + column.prop(operation, 'enable_dust') + if operation.enable_dust: + column.prop(operation, 'gcode_start_dust_cmd') + column.prop(operation, 'gcode_stop_dust_cmd') + + column.prop(operation, 'enable_hold') + if operation.enable_hold: + column.prop(operation, 'gcode_start_hold_cmd') + column.prop(operation, 'gcode_stop_hold_cmd') + + column.prop(operation, 'enable_mist') + if operation.enable_mist: + column.prop(operation, 'gcode_start_mist_cmd') + column.prop(operation, 'gcode_stop_mist_cmd') + + # Bottom + row = pie.row() + row.label(text='') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_movement.py b/scripts/addons/cam/pie_menu/active_op/pie_movement.py new file mode 100644 index 000000000..9d60f276e --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_movement.py @@ -0,0 +1,87 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Movement(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Movement ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + if operation.strategy in ['POCKET']: + column.prop(operation.movement, 'helix_enter') + if operation.movement.helix_enter: + column.prop(operation.movement, 'ramp_in_angle') + column.prop(operation.movement, 'helix_diameter') + + column.prop(operation.movement, 'ramp') + if operation.movement.ramp: + column.prop(operation.movement, 'ramp_in_angle') + column.prop(operation.movement, 'ramp_out') + if operation.movement.ramp_out: + column.prop(operation.movement, 'ramp_out_angle') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.strategy in ['POCKET']: + column.prop(operation.movement, 'retract_tangential') + if operation.movement.retract_tangential: + column.prop(operation.movement, 'retract_radius') + column.prop(operation.movement, 'retract_height') + + column.prop(operation.movement, 'stay_low') + if operation.movement.stay_low: + column.prop(operation.movement, 'merge_dist') + + if operation.cutter_type not in ['BALLCONE']: + column.prop(operation.movement, 'protect_vertical') + if operation.movement.protect_vertical: + column.prop(operation.movement, 'protect_vertical_limit') + + # Bottom + box = pie.box() + column = box.column(align=True) + column.prop(operation.movement, 'type') + if operation.movement.type in ['BLOCK', 'SPIRAL', 'CIRCLES']: + column.prop(operation.movement, 'insideout') + + column.prop(operation.movement, 'spindle_rotation') + + column.prop(operation.movement, 'free_height') + if operation.maxz > operation.movement.free_height: + column.label(text='Depth Start > Free Movement') + column.label(text='POSSIBLE COLLISION') + + # if self.context.scene.cam_machine.post_processor not in G64_INCOMPATIBLE_MACHINES: + # column.prop(operation.movement, 'useG64') + # if operation.movement.useG64: + # column.prop(operation.movement, 'G64') + + if operation.strategy in ['PARALLEL', 'CROSS']: + if not operation.movement.ramp: + column.prop(operation.movement, 'parallel_step_back') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_operation.py b/scripts/addons/cam/pie_menu/active_op/pie_operation.py new file mode 100644 index 000000000..6c289911b --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_operation.py @@ -0,0 +1,76 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Operation(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Active Operation ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + pie.scale_y = 1.5 + + # Left + pie.operator( + "wm.call_menu_pie", + text='Area', + icon='SHADING_BBOX' + ).name = 'VIEW3D_MT_PIE_Area' + # Right + pie.operator( + "wm.call_menu_pie", + text='Optimisation', + icon='MODIFIER' + ).name = 'VIEW3D_MT_PIE_Optimisation' + # Bottom + pie.operator( + "wm.call_menu_pie", + text='Setup', + icon='PREFERENCES' + ).name = 'VIEW3D_MT_PIE_Setup' + + # Top + box = pie.box() + box.scale_x = 2 + box.scale_y = 1.5 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' + + # Top Left + pie.operator( + "wm.call_menu_pie", + text='Movement', + icon='ANIM_DATA' + ).name = 'VIEW3D_MT_PIE_Movement' + + # Top Right + pie.operator( + "wm.call_menu_pie", + text='Feedrate', + icon='AUTO' + ).name = 'VIEW3D_MT_PIE_Feedrate' + + # Bottom Left + pie.operator( + "wm.call_menu_pie", + text='Cutter', + icon='OUTLINER_DATA_GP_LAYER' + ).name = 'VIEW3D_MT_PIE_Cutter' + + # Bottom Right + pie.operator( + "wm.call_menu_pie", + text='G-Code Options', + icon='EVENT_G' + ).name = 'VIEW3D_MT_PIE_Gcode' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py b/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py new file mode 100644 index 000000000..9b564dd79 --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py @@ -0,0 +1,88 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Optimisation(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Optimisation ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + if not operation.geometry_source == 'OBJECT' or operation.geometry_source == 'COLLECTION': + return + + self.exact_possible = operation.strategy not in [ + 'MEDIAL_AXIS', 'POCKET', 'CUTOUT', 'DRILL', 'PENCIL', 'CURVE'] + + if self.exact_possible: + column.prop(operation.optimisation, 'use_exact') + + if not self.exact_possible or not operation.optimisation.use_exact: + column.prop(operation.optimisation, 'pixsize') + column.prop(operation.optimisation, 'imgres_limit') + + sx = operation.max.x - operation.min.x + sy = operation.max.y - operation.min.y + resx = int(sx / operation.optimisation.pixsize) + resy = int(sy / operation.optimisation.pixsize) + + if resx > 0 and resy > 0: + resolution = 'Resolution: ' + str(resx) + ' x ' + str(resy) + column.label(text=resolution) + + # if not operation.optimisation.use_exact: + # return + # + # ocl_version = opencamlib_version() + # + # if ocl_version is None: + # column.label(text="OpenCAMLib Is Not Available") + # column.prop(operation.optimisation, 'exact_subdivide_edges') + # else: + # column.prop(operation.optimisation, 'use_opencamlib') + + column.prop(operation.optimisation, 'simulation_detail') + column.prop(operation.optimisation, 'circle_detail') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.strategy not in ['DRILL']: + column.prop(operation, 'remove_redundant_points') + if operation.remove_redundant_points: + column.prop(operation, 'simplify_tol') + if operation.geometry_source in ['OBJECT', 'COLLECTION']: + column.prop(operation, 'use_modifiers') + column.prop(operation, 'hide_all_others') + column.prop(operation, 'parent_path_to_object') + + # Bottom + box = pie.box() + column = box.column(align=True) + column.prop(operation.optimisation, 'optimize') + if operation.optimisation.optimize: + column.prop(operation.optimisation, 'optimize_threshold') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/active_op/pie_setup.py b/scripts/addons/cam/pie_menu/active_op/pie_setup.py new file mode 100644 index 000000000..a35c56d7a --- /dev/null +++ b/scripts/addons/cam/pie_menu/active_op/pie_setup.py @@ -0,0 +1,167 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Setup(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operation Setup ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + column.prop(operation, 'machine_axes') + if operation.machine_axes == '4': + column.prop(operation, 'strategy4axis') + if operation.strategy4axis == 'INDEXED': + column.prop(operation, 'strategy') + column.prop(operation, 'rotary_axis_1') + elif operation.machine_axes == '5': + column.prop(operation, 'strategy5axis') + if operation.strategy5axis == 'INDEXED': + column.prop(operation, 'strategy') + column.prop(operation, 'rotary_axis_1') + column.prop(operation, 'rotary_axis_2') + else: + column.prop(operation, 'strategy') + column.prop(operation, 'cut_type') + column.prop(operation, 'straight') + + column.prop(operation, 'enable_A') + if operation.enable_A: + column.prop(operation, 'rotation_A') + column.prop(operation, 'A_along_x') + if operation.A_along_x: + column.label(text='A || X - B || Y') + else: + column.label(text='A || Y - B ||X') + column.prop(operation, 'enable_B') + if operation.enable_B: + column.prop(operation, 'rotation_B') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.cutter_type in ['BALLCONE']: + engagement = round(100 * operation.dist_between_paths / operation.ball_radius, 1) + else: + engagement = round(100 * operation.dist_between_paths / operation.cutter_diameter, 1) + + if engagement > 50: + column.label(text="Warning: High Cutter Engagement") + column.label(text=f"Cutter Engagement: {engagement}%") + + column.prop(operation, 'profile_start') + column.prop(operation, 'lead_in') + column.prop(operation, 'lead_out') + column.prop(operation, 'outlines_count') + if operation.outlines_count > 1: + column.prop(operation, 'dist_between_paths') + self.draw_cutter_engagement() + column.prop(operation.movement, 'insideout') + column.prop(operation, 'dont_merge') + + # Bottom + box = pie.box() + column = box.column(align=True) + if operation.strategy in ['CUTOUT']: + pass + # self.draw_cutout_type() +# self.draw_overshoot() +# self.draw_startpoint() +# self.draw_lead_in_out() + + if operation.strategy in ['CUTOUT', 'CURVE']: + pass +# self.draw_enable_A_B_axis() +# self.draw_outlines() +# self.draw_merge() + + if operation.strategy in ['WATERLINE']: + column.label(text="OCL Doesn't Support Fill Areas") + if not operation.optimisation.use_opencamlib: + column.prop(operation, 'slice_detail') + column.prop(operation, 'waterline_fill') + if operation.waterline_fill: + column.prop(operation, 'dist_between_paths') + column.prop(operation, 'waterline_project') + column.label(text="Waterline needs a skin margin") + + if operation.strategy in ['CARVE']: + column.prop(operation, 'carve_depth') + column.prop(operation, 'dist_along_paths') + + if operation.strategy in ['MEDIAL_AXIS']: + column.prop(operation, 'medial_axis_threshold') + column.prop(operation, 'medial_axis_subdivision') + column.prop(operation, 'add_pocket_for_medial') + column.prop(operation, 'add_mesh_for_medial') + + if operation.strategy in ['DRILL']: + column.prop(operation, 'drill_type') +# self.draw_enable_A_B_axis() + + if operation.strategy in ['POCKET']: + column.prop(operation, 'pocket_option') + column.prop(operation, 'pocketToCurve') + column.prop(operation, 'dist_between_paths') +# self.draw_cutter_engagement() +# self.draw_enable_A_B_axis() + + if operation.strategy not in [ + 'CUTOUT', + 'CURVE', + 'WATERLINE', + 'CARVE', + 'MEDIAL_AXIS', + 'DRILL', + 'POCKET', + ]: + column.prop(operation, 'dist_between_paths') +# self.draw_cutter_engagement() + column.prop(operation, 'dist_along_paths') + if operation.strategy in ['PARALLEL', 'CROSS']: + column.prop(operation, 'parallel_angle') +# self.draw_enable_A_B_axis() + column.prop(operation, 'inverse') + + if operation.strategy not in ['POCKET', 'DRILL', 'CURVE', 'MEDIAL_AXIS']: + column.prop(operation, 'use_bridges') + if operation.use_bridges: + column.prop(operation, 'bridges_width') + column.prop(operation, 'bridges_height') + column.prop_search(operation, "bridges_collection_name", bpy.data, "collections") + column.prop(operation, 'use_bridge_modifiers') + column.operator("scene.cam_bridges_add", text="Autogenerate Bridges / Tabs") + + column.prop(operation, 'skin') + + if operation.machine_axes == '3': + column.prop(operation, 'array') + if operation.array: + column.prop(operation, 'array_x_count') + column.prop(operation, 'array_x_distance') + column.prop(operation, 'array_y_count') + column.prop(operation, 'array_y_distance') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='BACK' + ).name = 'VIEW3D_MT_PIE_Operation' diff --git a/scripts/addons/cam/pie_menu/pie_cam.py b/scripts/addons/cam/pie_menu/pie_cam.py new file mode 100644 index 000000000..1648d6efb --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_cam.py @@ -0,0 +1,91 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_CAM(Menu): + bl_label = "∴ BlenderCAM ∴" + + def draw(self, context): + layout = self.layout + scene = context.scene + + pie = layout.menu_pie() + pie.scale_y = 1.5 + pie.emboss = 'NONE' + + # Left + pie.operator( + "wm.call_menu_pie", + text='Machine', + icon='DESKTOP' + ).name = 'VIEW3D_MT_PIE_Machine' + + # Right + pie.operator( + "wm.call_menu_pie", + text='Material', + icon='META_CUBE' + ).name = 'VIEW3D_MT_PIE_Material' + + # Bottom + if len(scene.cam_operations) < 1: + pie.operator( + "scene.cam_operation_add", + text='Add Operation', + icon='ADD' + ) + else: + pie.operator( + "wm.call_menu_pie", + text='Active Operation', + icon='MOD_ENVELOPE' + ).name = 'VIEW3D_MT_PIE_Operation' + + # Top + if len(scene.cam_operations) < 1: + box = pie.box() + box.label(text='(No Operations)') + else: + pie.operator( + "wm.call_menu_pie", + text='Operations & Chains', + icon='LINKED' + ).name = 'VIEW3D_MT_PIE_Chains' + + # Top Left + box = pie.box() + if len(scene.cam_operations) > 0: + operation = scene.cam_operations[scene.cam_active_operation] + if len(operation.info.warnings) > 1: + box.alert = True + else: + box.alert = False + box.operator( + "wm.call_menu_pie", + text='Info & Warnings', + icon='INFO' + ).name = 'VIEW3D_MT_PIE_Info' + + # Top Right + box = pie.box() + box.operator( + "wm.call_menu_pie", + text='Pack, Slice, Relief', + icon='PACKAGE' + ).name = 'VIEW3D_MT_PIE_PackSliceRelief' + + # Bottom Left + box = pie.box() + box.operator( + "wm.call_menu_pie", + text='Curve Tools', + icon='TOOL_SETTINGS' + ).name = 'VIEW3D_MT_PIE_CurveTools' + + # Bottom Right + box = pie.box() + box.operator( + "wm.call_menu_pie", + text='Curve Creators', + icon='CURVE_DATA' + ).name = 'VIEW3D_MT_PIE_CurveCreators' diff --git a/scripts/addons/cam/pie_menu/pie_chains.py b/scripts/addons/cam/pie_menu/pie_chains.py new file mode 100644 index 000000000..103b0c3fc --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_chains.py @@ -0,0 +1,157 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Chains(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Operations & Chains ∴" + + def draw(self, context): + scene = context.scene + operation = scene.cam_operations[scene.cam_active_operation] + + layout = self.layout + + layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column() + column.label(text='Operations', icon='MOD_ENVELOPE') + column.menu("CAM_OPERATION_MT_presets", text='Presets', icon='RIGHTARROW') + row = column.row() + row.template_list( + "CAM_UL_operations", + '', + scene, + "cam_operations", + scene, + 'cam_active_operation' + ) + + column = row.column(align=True) + column.operator("scene.cam_operation_add", icon='ADD', text="") + column.operator("scene.cam_operation_copy", icon='COPYDOWN', text="") + column.operator("scene.cam_operation_remove", icon='REMOVE', text="") + column.separator() + column.operator( + "scene.cam_operation_move", + icon='TRIA_UP', + text="" + ).direction = 'UP' + column.operator( + "scene.cam_operation_move", + icon='TRIA_DOWN', + text="" + ).direction = 'DOWN' + + column = box.column(align=True) + if not operation.valid: + column.label(text="Select a Valid Object to Calculate the Path.") + # will be disable if not valid + column.operator("object.calculate_cam_path", text="Calculate Path & Export G-code") + if operation.valid: + if operation.name is not None: + name = f"cam_path_{operation.name}" + if scene.objects.get(name) is not None: + column.operator("object.cam_export", text="Export G-code ") + + if operation.valid: + column.operator("object.cam_simulate", text="Simulate This Operation") + + column = box.column(align=True) + column.prop(operation, 'name') + column.prop(operation, 'filename') + column.prop(operation, 'geometry_source') + if operation.strategy == 'CURVE': + if operation.geometry_source == 'OBJECT': + column.prop_search(operation, "object_name", bpy.data, "objects") + elif operation.geometry_source == 'COLLECTION': + column.prop_search(operation, "collection_name", bpy.data, "collections") + else: + if operation.geometry_source == 'OBJECT': + column.prop_search(operation, "object_name", bpy.data, "objects") + if operation.enable_A: + column.prop(operation, 'rotation_A') + if operation.enable_B: + column.prop(operation, 'rotation_B') + + elif operation.geometry_source == 'COLLECTION': + column.prop_search(operation, "collection_name", bpy.data, "collections") + else: + column.prop_search(operation, "source_image_name", bpy.data, "images") + + if operation.strategy in ['CARVE', 'PROJECTED_CURVE']: + column.prop_search(operation, "curve_object", bpy.data, "objects") + if operation.strategy == 'PROJECTED_CURVE': + column.prop_search(operation, "curve_object1", bpy.data, "objects") + + # Right + box = pie.box() + column = box.column() + column.label(text='Chains', icon='LINKED') + row = column.row() + row.template_list("CAM_UL_chains", '', scene, "cam_chains", scene, 'cam_active_chain') + + column = row.column() + column.operator("scene.cam_chain_add", icon='ADD', text="") + column.operator("scene.cam_chain_remove", icon='REMOVE', text="") + + column = box.column() + + if len(scene.cam_chains) > 0: + chain = scene.cam_chains[scene.cam_active_chain] + row = column.row(align=True) + + if chain: + row.template_list("CAM_UL_operations", '', chain, + "operations", chain, 'active_operation') + column = row.column(align=True) + column.operator("scene.cam_chain_operation_add", icon='ADD', text="") + column.operator("scene.cam_chain_operation_remove", icon='REMOVE', text="") + if len(chain.operations) > 0: + column.operator("scene.cam_chain_operation_up", icon='TRIA_UP', text="") + column.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="") + + column = box.column(align=True) + if not chain.computing: + column.operator("object.calculate_cam_paths_chain", + text="Calculate Chain Paths & Export G-code") + column.operator("object.cam_export_paths_chain", text="Export Chain G-code") + column.operator("object.cam_simulate_chain", text="Simulate This Chain") + +# valid, reason = isChainValid(chain, context) +# if not valid: +# column.label(icon="ERROR", text=f"Can't compute chain - reason:\n") +# column.label(text=reason) + else: + column.label(text='chain is currently computing') + + column = box.column() + column.prop(chain, 'name') + column.prop(chain, 'filename') + + # Bottom + row = pie.row() + row.label(text='') + + # Top + box = pie.box() + column = box.column(align=True) + if operation.maxz > operation.movement.free_height: + column.label(text='!ERROR! COLLISION!') + column.label(text='Depth Start > Free Movement Height') + column.label(text='!ERROR! COLLISION!') + column.prop(operation.movement, 'free_height') + separator = column.separator(factor=1) + column.scale_x = 2 + column.scale_y = 2 + column.emboss = 'NONE' + column.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_curvecreators.py b/scripts/addons/cam/pie_menu/pie_curvecreators.py new file mode 100644 index 000000000..dbc534f56 --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_curvecreators.py @@ -0,0 +1,49 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_CurveCreators(Menu): + bl_label = "∴ Curve Creators ∴" + + def draw(self, context): + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + column.operator("object.curve_plate") + column.operator("object.curve_drawer") + column.operator("object.curve_mortise") + column.operator("object.curve_interlock") + + # Right + box = pie.box() + column = box.column(align=True) + column.operator("object.curve_puzzle") + column.operator("object.sine") + column.operator("object.lissajous") + column.operator("object.hypotrochoid") + + # Bottom + box = pie.box() + column = box.column(align=True) + column.operator("object.customcurve") + column.operator("object.curve_hatch") + column.operator("object.curve_gear") + column.operator("object.curve_flat_cone") + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_curvetools.py b/scripts/addons/cam/pie_menu/pie_curvetools.py new file mode 100644 index 000000000..cf23955cd --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_curvetools.py @@ -0,0 +1,46 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_CurveTools(Menu): + bl_label = "∴ Curve Tools ∴" + + def draw(self, context): + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + column.operator("object.curve_boolean") + column.operator("object.convex_hull") + column.operator("object.curve_intarsion") + + # Right + box = pie.box() + column = box.column(align=True) + column.operator("object.curve_overcuts") + column.operator("object.curve_overcuts_b") + + # Bottom + box = pie.box() + column = box.column(align=True) + column.operator("object.silhouete") + column.operator("object.silhouete_offset") + column.operator("object.curve_remove_doubles") + column.operator("object.mesh_get_pockets") + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_info.py b/scripts/addons/cam/pie_menu/pie_info.py new file mode 100644 index 000000000..548ac1889 --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_info.py @@ -0,0 +1,100 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Info(Menu): + bl_label = "∴ Info ∴" + + def draw(self, context): + layout = self.layout + scene = context.scene + preferences = context.preferences.addons['cam'].preferences + no_operation = len(scene.cam_operations) < 1 + if not no_operation: + operation = scene.cam_operations[scene.cam_active_operation] + + pie = layout.menu_pie() + + if no_operation: + pie.separator() + pie.separator() + pie.separator() + + else: + # Left + box = pie.box() + column = box.column(align=True) + # column.label(text=f'BlenderCAM v{".".join([str(x) for x in cam_version])}') + if len(preferences.new_version_available) > 0: + column.label(text=f"New Version Available:") + column.label( + text=f" {preferences.new_version_available}") + column.operator("render.cam_update_now") + + # ocl_version = opencamlib_version() + # if ocl_version is None: + # column.label(text="OpenCAMLib is not installed") + # else: + # column.label(text=f"OpenCAMLib v{ocl_version}") + + if int(operation.info.duration * 60) > 0: + # Right + box = pie.box() + column = box.column(align=True) + + time_estimate = f"Operation Duration: {int(operation.info.duration*60)}s " + if operation.info.duration > 60: + time_estimate += f" ({int(operation.info.duration / 60)}h" + time_estimate += f" {round(operation.info.duration % 60)}min)" + elif operation.info.duration > 1: + time_estimate += f" ({round(operation.info.duration % 60)}min)" + + column.label(text=time_estimate) + else: + pass + + # if not operation.info.chipload > 0: + # return + + # chipload = f"Chipload: {strInUnits(operation.info.chipload, 4)}/tooth" + # column.label(text=chipload) + + if int(operation.info.duration * 60) > 0: + row = column.row() + row.label(text='Hourly Rate') + row.prop(scene.cam_machine, 'hourly_rate', text='') + + if float(scene.cam_machine.hourly_rate) < 0.01: + return + + cost_per_second = context.scene.cam_machine.hourly_rate / 3600 + total_cost = operation.info.duration * 60 * cost_per_second + op_cost = f"Operation Cost: ${total_cost:.2f} (${cost_per_second:.2f}/s)" + column.label(text=op_cost) + else: + pie.separator() + + if len(operation.info.warnings) > 1: + # Bottom + box = pie.box() + box.alert = True + column = box.column(align=True) + # column.alert = True + column.label(text='Errors') + for line in operation.info.warnings.rstrip("\n").split("\n"): + if len(line) > 0: + column.label(text=line, icon='ERROR') + else: + pie.separator() + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_machine.py b/scripts/addons/cam/pie_menu/pie_machine.py new file mode 100644 index 000000000..ea331ea3e --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_machine.py @@ -0,0 +1,77 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Machine(Menu): + bl_label = "∴ Machine Settings ∴" + + def draw(self, context): + scene = context.scene + machine = scene.cam_machine + + layout = self.layout + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column() + column.emboss = 'RADIAL_MENU' + column.menu("CAM_MACHINE_MT_presets", text='Presets', icon='RIGHTARROW') + column.emboss = 'NORMAL' + column.prop(machine, 'post_processor') + column.prop(machine, 'eval_splitting') + if machine.eval_splitting: + column.prop(machine, 'split_limit') + column.prop(scene.unit_settings, 'system') + column.prop(machine, 'working_area') + column = box.column(align=True) + column.prop(machine, 'feedrate_min') + column.prop(machine, 'feedrate_max') + column.prop(machine, 'feedrate_default') + column.prop(machine, 'spindle_min') + column.prop(machine, 'spindle_max') + column.prop(machine, 'spindle_start_time') + column.prop(machine, 'spindle_default') + + # Right + box = pie.box() + column = box.column(align=True) + column.prop(machine, 'output_tool_definitions') + column.prop(machine, 'output_tool_change') + if machine.output_tool_change: + column.prop(machine, 'output_g43_on_tool_change') + column.prop(machine, 'axis4') + column.prop(machine, 'axis5') + column = box.column() + column.prop(machine, 'collet_size') + column.prop(machine, 'hourly_rate') + + # Bottom + pie_column = pie.column() + pie_column.separator(factor=12) + box = pie_column.box() + column = box.column(align=True) + column.prop(machine, 'use_position_definitions') + if machine.use_position_definitions: + column.prop(machine, 'starting_position') + column.prop(machine, 'mtc_position') + column.prop(machine, 'ending_position') + column.prop(machine, 'output_block_numbers') + if machine.output_block_numbers: + column.prop(machine, 'start_block_number') + column.prop(machine, 'block_number_increment') + pie_column.separator(factor=15) + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_material.py b/scripts/addons/cam/pie_menu/pie_material.py new file mode 100644 index 000000000..34863cfb8 --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_material.py @@ -0,0 +1,60 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_Material(Menu): + # label is displayed at the center of the pie menu. + bl_label = "∴ Material Size & Position ∴" + + def draw(self, context): + scene = context.scene + + layout = self.layout + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + if len(scene.cam_operations) > 0: + operation = scene.cam_operations[scene.cam_active_operation] + material = operation.material + + # Left + box = pie.box() + column = box.column(align=True) + if operation.geometry_source in ['OBJECT', 'COLLECTION']: + column.prop(material, 'estimate_from_model') + if material.estimate_from_model: + column.prop(material, 'radius_around_model', text='Additional Radius') + else: + column.prop(material, 'origin') + column.prop(material, 'size') + + # Right + box = pie.box() + column = box.column(align=True) + if operation.geometry_source in ['OBJECT', 'COLLECTION']: + column.prop(material, 'center_x') + column.prop(material, 'center_y') + column.prop(material, 'z_position') + column.operator("object.material_cam_position", text="Position Object") + + else: + pie.separator() + pie.separator() + + # Bottom + row = pie.row() + row.label(text='') + + # Top + box = pie.box() + column = box.column(align=True) + column.scale_y = 2 + column.scale_x = 2 + column.emboss = 'NONE' + column.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' diff --git a/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py b/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py new file mode 100644 index 000000000..693f424fe --- /dev/null +++ b/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py @@ -0,0 +1,111 @@ +import bpy +from bpy.types import Menu + + +class VIEW3D_MT_PIE_PackSliceRelief(Menu): + bl_label = "∴ Pack | Slice | Bas Relief ∴" + + def draw(self, context): + layout = self.layout + scene = context.scene + # layout.use_property_split = True + layout.use_property_decorate = False + + pie = layout.menu_pie() + + # Left + box = pie.box() + column = box.column(align=True) + box = column.box() + box.label(text='Pack', icon='PACKAGE') + pack_settings = scene.cam_pack + column.label(text='Warning - Algorithm Is Slow.') + column.label(text='Only for Curves Now.') + column.operator("object.cam_pack_objects") + column.prop(pack_settings, 'sheet_fill_direction') + column.prop(pack_settings, 'sheet_x') + column.prop(pack_settings, 'sheet_y') + column.prop(pack_settings, 'distance') + column.prop(pack_settings, 'tolerance') + column.prop(pack_settings, 'rotate') + if pack_settings.rotate: + column.prop(pack_settings, 'rotate_angle') + + # Right + box = pie.box() + column = box.column(align=True) + box = column.box() + box.label(text='Slice', icon='ALIGN_JUSTIFY') + slice_settings = scene.cam_slice + column.operator("object.cam_slice_objects") + column.prop(slice_settings, 'slice_distance') + column.prop(slice_settings, 'slice_above0') + column.prop(slice_settings, 'slice_3d') + column.prop(slice_settings, 'indexes') + + # Bottom + column = pie.column() + column.separator(factor=5) + box = column.box() + column = box.column(align=True) + box = column.box() + box.label(text='Bas Relief', icon='RNDCURVE') + relief_settings = scene.basreliefsettings + column.operator("scene.calculate_bas_relief", text="Calculate Relief") + column.prop(relief_settings, 'advanced') + column.prop(relief_settings, 'use_image_source') + if relief_settings.use_image_source: + column.prop_search(relief_settings, 'source_image_name', bpy.data, "images") + else: + column.prop_search(relief_settings, 'view_layer_name', + bpy.context.scene, "view_layers") + column.prop(relief_settings, 'depth_exponent') + column.label(text="Project parameters") + column.prop(relief_settings, 'bit_diameter') + column.prop(relief_settings, 'pass_per_radius') + column.prop(relief_settings, 'widthmm') + column.prop(relief_settings, 'heightmm') + column.prop(relief_settings, 'thicknessmm') + + column.label(text="Justification") + column.prop(relief_settings, 'justifyx') + column.prop(relief_settings, 'justifyy') + column.prop(relief_settings, 'justifyz') + + column.label(text="Silhouette") + column.prop(relief_settings, 'silhouette_threshold') + column.prop(relief_settings, 'recover_silhouettes') + if relief_settings.recover_silhouettes: + column.prop(relief_settings, 'silhouette_scale') + if relief_settings.advanced: + column.prop(relief_settings, 'silhouette_exponent') + # column.template_curve_mapping(relief_settings,'curva') + if relief_settings.advanced: + # column.prop(relief_settings,'attenuation') + column.prop(relief_settings, 'min_gridsize') + column.prop(relief_settings, 'smooth_iterations') + column.prop(relief_settings, 'vcycle_iterations') + column.prop(relief_settings, 'linbcg_iterations') + column.prop(relief_settings, 'use_planar') + column.prop(relief_settings, 'decimate_ratio') + column.prop(relief_settings, 'gradient_scaling_mask_use') + if relief_settings.advanced: + if relief_settings.gradient_scaling_mask_use: + column.prop_search( + relief_settings, 'gradient_scaling_mask_name', bpy.data, "images") + column.prop(relief_settings, 'detail_enhancement_use') + if relief_settings.detail_enhancement_use: + # column.prop(relief_settings,'detail_enhancement_freq') + column.prop(relief_settings, 'detail_enhancement_amount') + + # Top + column = pie.column() + box = column.box() + box.scale_y = 2 + box.scale_x = 2 + box.emboss = 'NONE' + box.operator( + "wm.call_menu_pie", + text='', + icon='HOME' + ).name = 'VIEW3D_MT_PIE_CAM' From 39fe9d0e38b50c0c8c66811f3bae045e62fea570 Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 12 Apr 2024 19:27:27 +0000 Subject: [PATCH 075/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 349eb8e8c..55a28f98b 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -199,7 +199,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 18), + "version":(1,0,19), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index ba1cf70ff..c39d9ae94 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,18) \ No newline at end of file +__version__=(1,0,19) \ No newline at end of file From c926449d444aa80966093741d48a3f6f03a66f82 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 13 Apr 2024 11:49:02 -0400 Subject: [PATCH 076/100] GPLv2 update to GPLv3 --- LICENSE | 906 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 620 insertions(+), 286 deletions(-) diff --git a/LICENSE b/LICENSE index d6a93266f..f288702d2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,281 +1,622 @@ -GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of this License. - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. END OF TERMS AND CONDITIONS @@ -287,15 +628,15 @@ free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least +state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - {description} - Copyright (C) {year} {fullname} + + Copyright (C) - This program is free software; you can redistribute it and/or modify + This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or + the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, @@ -303,38 +644,31 @@ the "copyright" line and a pointer to where the full notice is found. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + You should have received a copy of the GNU General Public License + along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. - +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 6ec5bed9cb37c6c08e4bd71a07e74e11ec0e267c Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Sat, 13 Apr 2024 11:50:06 -0400 Subject: [PATCH 077/100] GPLv3 added to Addon root Second copy of the License in the addon root for users who download only from the Releases section. --- scripts/addons/cam/LICENSE | 674 +++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 scripts/addons/cam/LICENSE diff --git a/scripts/addons/cam/LICENSE b/scripts/addons/cam/LICENSE new file mode 100644 index 000000000..f288702d2 --- /dev/null +++ b/scripts/addons/cam/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From fb3005c8c57e303142b514c561cddb5e332ae242 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:02:58 -0400 Subject: [PATCH 078/100] GPLv2 - GPLv3, format header comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per discussion in Matrix Dev channel, GPLv2 license has been updated to GPLv3, and is now included in the base level of the addon, to ensure everyone will receive a copy, regardless of how they download or install. Formatted header comments for consistency. Now every file follows this format: """BlenderCAM 'filename.py' © 20xx Author Name (if available) Brief summary of what is in the file (functions, classes) and what they do. Original developer comments. (if available) """ --- scripts/addons/cam/__init__.py | 26 +--- scripts/addons/cam/async_op.py | 6 + scripts/addons/cam/autoupdate.py | 5 + scripts/addons/cam/basrelief.py | 6 + scripts/addons/cam/bridges.py | 27 +--- scripts/addons/cam/cam_chunk.py | 24 +--- scripts/addons/cam/cam_operation.py | 5 + scripts/addons/cam/chain.py | 5 + scripts/addons/cam/collision.py | 27 +--- scripts/addons/cam/constants.py | 11 +- scripts/addons/cam/curvecamcreate.py | 25 +--- scripts/addons/cam/curvecamequation.py | 25 +--- scripts/addons/cam/curvecamtools.py | 27 +--- scripts/addons/cam/engine.py | 5 + scripts/addons/cam/exception.py | 6 + scripts/addons/cam/gcodeimportparser.py | 27 +--- scripts/addons/cam/gcodepath.py | 28 +--- scripts/addons/cam/image_utils.py | 24 +--- scripts/addons/cam/involute_gear.py | 125 +++++++++--------- scripts/addons/cam/joinery.py | 24 +--- scripts/addons/cam/machine_settings.py | 5 + scripts/addons/cam/numba_wrapper.py | 5 + scripts/addons/cam/opencamlib/oclSample.py | 5 + scripts/addons/cam/opencamlib/opencamlib.py | 6 +- scripts/addons/cam/ops.py | 28 +--- scripts/addons/cam/pack.py | 38 ++---- scripts/addons/cam/parametric.py | 63 ++++----- scripts/addons/cam/pattern.py | 25 +--- .../addons/cam/pie_menu/active_op/pie_area.py | 5 + .../cam/pie_menu/active_op/pie_cutter.py | 5 + .../cam/pie_menu/active_op/pie_feedrate.py | 5 + .../cam/pie_menu/active_op/pie_gcode.py | 5 + .../cam/pie_menu/active_op/pie_movement.py | 5 + .../cam/pie_menu/active_op/pie_operation.py | 5 + .../pie_menu/active_op/pie_optimisation.py | 5 + .../cam/pie_menu/active_op/pie_setup.py | 5 + scripts/addons/cam/pie_menu/pie_cam.py | 5 + scripts/addons/cam/pie_menu/pie_chains.py | 5 + .../addons/cam/pie_menu/pie_curvecreators.py | 5 + scripts/addons/cam/pie_menu/pie_curvetools.py | 5 + scripts/addons/cam/pie_menu/pie_info.py | 5 + scripts/addons/cam/pie_menu/pie_machine.py | 5 + scripts/addons/cam/pie_menu/pie_material.py | 5 + .../cam/pie_menu/pie_pack_slice_relief.py | 5 + scripts/addons/cam/polygon_utils_cam.py | 24 +--- scripts/addons/cam/preferences.py | 5 + scripts/addons/cam/preset_managers.py | 5 + scripts/addons/cam/puzzle_joinery.py | 27 +--- scripts/addons/cam/simple.py | 25 +--- scripts/addons/cam/simulation.py | 27 +--- scripts/addons/cam/slice.py | 28 +--- scripts/addons/cam/strategy.py | 28 +--- scripts/addons/cam/testing.py | 25 +--- scripts/addons/cam/ui.py | 25 +--- scripts/addons/cam/ui_panels/area.py | 5 + scripts/addons/cam/ui_panels/buttons_panel.py | 6 + scripts/addons/cam/ui_panels/chains.py | 5 + scripts/addons/cam/ui_panels/cutter.py | 5 + scripts/addons/cam/ui_panels/feedrate.py | 5 + scripts/addons/cam/ui_panels/gcode.py | 5 + scripts/addons/cam/ui_panels/info.py | 5 + scripts/addons/cam/ui_panels/interface.py | 5 + scripts/addons/cam/ui_panels/machine.py | 5 + scripts/addons/cam/ui_panels/material.py | 5 + scripts/addons/cam/ui_panels/movement.py | 5 + scripts/addons/cam/ui_panels/op_properties.py | 5 + scripts/addons/cam/ui_panels/operations.py | 5 + scripts/addons/cam/ui_panels/optimisation.py | 5 + scripts/addons/cam/ui_panels/pack.py | 5 + scripts/addons/cam/ui_panels/slice.py | 5 + scripts/addons/cam/utils.py | 27 +--- scripts/addons/cam/version.py | 7 +- scripts/addons/cam/voronoi.py | 121 +++++++++-------- 73 files changed, 517 insertions(+), 651 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 55a28f98b..c14b4100c 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -1,23 +1,7 @@ -# blender CAM __init__.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK **** +"""BlenderCAM '__init__.py' © 2012 Vilem Novak + +Import Modules, bl_info, Register and Unregister Classes +""" # Python Standard Library import subprocess @@ -199,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,19), + "version": (1, 0, 19), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/async_op.py b/scripts/addons/cam/async_op.py index 5cc115662..cdba9000e 100644 --- a/scripts/addons/cam/async_op.py +++ b/scripts/addons/cam/async_op.py @@ -1,3 +1,9 @@ +"""BlenderCAM 'async_op.py' + +Functions and Classes to allow asynchronous updates. +Used to report progress during path calculation. +""" + import sys import types diff --git a/scripts/addons/cam/autoupdate.py b/scripts/addons/cam/autoupdate.py index 7ac6a32a9..a2f165e0d 100644 --- a/scripts/addons/cam/autoupdate.py +++ b/scripts/addons/cam/autoupdate.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'autoupdate.py' + +Classes to check for, download and install BlenderCAM updates. +""" + import calendar from datetime import date import io diff --git a/scripts/addons/cam/basrelief.py b/scripts/addons/cam/basrelief.py index 08a2aa4ba..2554ce2ab 100644 --- a/scripts/addons/cam/basrelief.py +++ b/scripts/addons/cam/basrelief.py @@ -1,3 +1,9 @@ +"""BlenderCAM 'basrelief.py' + +Module to allow the creation of reliefs from Images or View Layers. +(https://en.wikipedia.org/wiki/Relief#Bas-relief_or_low_relief) +""" + from math import ( ceil, floor, diff --git a/scripts/addons/cam/bridges.py b/scripts/addons/cam/bridges.py index 17a513ae7..026e50e19 100644 --- a/scripts/addons/cam/bridges.py +++ b/scripts/addons/cam/bridges.py @@ -1,24 +1,9 @@ -# blender CAM utils.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** -# here is the bridges functionality of Blender CAM. The functions here are called with operators defined from ops.py. +"""BlenderCAM 'bridges.py' © 2012 Vilem Novak + +Functions to add Bridges / Tabs to meshes or curves. +Called with Operators defined in 'ops.py' +""" + from math import ( hypot, pi, diff --git a/scripts/addons/cam/cam_chunk.py b/scripts/addons/cam/cam_chunk.py index 8730ac7bb..ebfbbdffe 100644 --- a/scripts/addons/cam/cam_chunk.py +++ b/scripts/addons/cam/cam_chunk.py @@ -1,23 +1,7 @@ -# blender CAM chunk.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'chunk.py' © 2012 Vilem Novak + +Classes and Functions to build, store and optimize CAM path chunks. +""" from math import ( ceil, diff --git a/scripts/addons/cam/cam_operation.py b/scripts/addons/cam/cam_operation.py index b141a726c..e3bdcc7cb 100644 --- a/scripts/addons/cam/cam_operation.py +++ b/scripts/addons/cam/cam_operation.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'cam_operation.py' + +All properties of a single CAM Operation. +""" + from math import pi import numpy diff --git a/scripts/addons/cam/chain.py b/scripts/addons/cam/chain.py index bd793ad9f..c9ab8b56c 100644 --- a/scripts/addons/cam/chain.py +++ b/scripts/addons/cam/chain.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'chain.py' + +All properties of a CAM Chain (a series of Operations), and the Chain's Operation reference. +""" + from bpy.props import ( BoolProperty, CollectionProperty, diff --git a/scripts/addons/cam/collision.py b/scripts/addons/cam/collision.py index 074fcfe19..dafd46568 100644 --- a/scripts/addons/cam/collision.py +++ b/scripts/addons/cam/collision.py @@ -1,23 +1,8 @@ -# blender CAM collision.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'collision.py' © 2012 Vilem Novak + +Functions for Bullet and Cutter collision checks. +""" + from math import ( cos, pi, @@ -318,7 +303,7 @@ def cleanupBulletCollision(o): def getSampleBullet(cutter, x, y, radius, startz, endz): - """collision test for 3 axis milling. Is simplified compared to the full 3d test""" + """Collision Test for 3 Axis Milling. Is Simplified Compared to the Full 3D Test""" scene = bpy.context.scene pos = scene.rigidbody_world.convex_sweep_test(cutter, (x * BULLET_SCALE, y * BULLET_SCALE, startz * BULLET_SCALE), (x * BULLET_SCALE, y * BULLET_SCALE, endz * BULLET_SCALE)) diff --git a/scripts/addons/cam/constants.py b/scripts/addons/cam/constants.py index 34d2f7d4b..ded350449 100644 --- a/scripts/addons/cam/constants.py +++ b/scripts/addons/cam/constants.py @@ -1,5 +1,7 @@ +"""BlenderCAM 'constants.py' -# Package to store all constants of BlenderCAM +Package to store all constants of BlenderCAM. +""" # PRECISION is used in most operations PRECISION = 5 @@ -10,8 +12,9 @@ G64_INCOMPATIBLE_MACHINES = ['GRBL'] +# Upscale factor for higher precision from Bullet library - (Rigidbody Collision World) BULLET_SCALE = 10000 -# this is a constant for scaling the rigidbody collision world for higher precision from bullet library + +# Cutter object must be present in the scene, so we need to put it aside for sweep collisions, +# otherwise it collides with itself. CUTTER_OFFSET = (-5 * BULLET_SCALE, -5 * BULLET_SCALE, -5 * BULLET_SCALE) -# the cutter object has to be present in the scene , so we need to put it aside for sweep collisions, -# otherwise it collides itself. diff --git a/scripts/addons/cam/curvecamcreate.py b/scripts/addons/cam/curvecamcreate.py index 805709276..848f470d6 100644 --- a/scripts/addons/cam/curvecamcreate.py +++ b/scripts/addons/cam/curvecamcreate.py @@ -1,23 +1,8 @@ -# blender CAM ops.py (c) 2022 Alain Pelletier -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'curvecamcreate.py' © 2021, 2022 Alain Pelletier + +Operators to create a number of predefined curve objects. +""" + from math import ( degrees, hypot, diff --git a/scripts/addons/cam/curvecamequation.py b/scripts/addons/cam/curvecamequation.py index c30e2b4d2..a2c195dba 100644 --- a/scripts/addons/cam/curvecamequation.py +++ b/scripts/addons/cam/curvecamequation.py @@ -1,23 +1,8 @@ -# blender CAM curvecamequation.py (c) 2021 Alain Pelletier -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'curvecamequation.py' © 2021, 2022 Alain Pelletier + +Operators to create a number of geometric shapes with curves. +""" + from math import pi from Equation import Expression diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index ac3e94731..6773322c6 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -1,25 +1,8 @@ -# blender CAM ops.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# blender operators definitions are in this file. They mostly call the functions from utils.py +"""BlenderCAM 'curvecamtools.py' © 2012 Vilem Novak, 2021 Alain Pelletier + +Operators that perform various functions on existing curves. +""" + from math import ( pi, tan diff --git a/scripts/addons/cam/engine.py b/scripts/addons/cam/engine.py index 5316857d4..1876c0120 100644 --- a/scripts/addons/cam/engine.py +++ b/scripts/addons/cam/engine.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'engine.py' + +Engine definition, options and panels. +""" + from bl_ui.properties_material import ( EEVEE_MATERIAL_PT_context_material, EEVEE_MATERIAL_PT_settings, diff --git a/scripts/addons/cam/exception.py b/scripts/addons/cam/exception.py index 8d46ffc8c..10cf40300 100644 --- a/scripts/addons/cam/exception.py +++ b/scripts/addons/cam/exception.py @@ -1,2 +1,8 @@ +"""BlenderCAM 'exception.py' + +Generic CAM Exception class. +""" + + class CamException(Exception): pass diff --git a/scripts/addons/cam/gcodeimportparser.py b/scripts/addons/cam/gcodeimportparser.py index 3d5143ec2..643ce635b 100644 --- a/scripts/addons/cam/gcodeimportparser.py +++ b/scripts/addons/cam/gcodeimportparser.py @@ -1,22 +1,9 @@ -#!/usr/bin/env python -# https://github.com/jonathanwin/yagv with no licence -# code modified from YAGV -yet another gcode viewer -# will assume release GNU release -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 3 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'gcodeimportparser.py' + +Code modified from YAGV (Yet Another G-code Viewer) - https://github.com/jonathanwin/yagv +No license terms found in YAGV repo, will assume GNU release +""" + import math import numpy as np @@ -27,7 +14,7 @@ def import_gcode(context, filepath): - print("running read_some_data...") + print("Running read_some_data...") scene = context.scene mytool = scene.cam_import_gcode diff --git a/scripts/addons/cam/gcodepath.py b/scripts/addons/cam/gcodepath.py index dc68d4a4c..fe24cfec0 100644 --- a/scripts/addons/cam/gcodepath.py +++ b/scripts/addons/cam/gcodepath.py @@ -1,25 +1,9 @@ -# blender CAM gcodepath.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# here is the Gcode generaton +"""BlenderCAM 'gcodepath.py' © 2012 Vilem Novak + +Generate and Export G-Code based on scene, machine, chain, operation and path settings. +""" + +# G-code Generaton from math import ( ceil, floor, diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index c007b4c27..5ed83458c 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -1,23 +1,7 @@ -# blender CAM image_utils.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'image_utils.py' © 2012 Vilem Novak + +Functions to render, save, convert and analyze image data. +""" from math import ( acos, diff --git a/scripts/addons/cam/involute_gear.py b/scripts/addons/cam/involute_gear.py index bca9579c0..17d71ab92 100644 --- a/scripts/addons/cam/involute_gear.py +++ b/scripts/addons/cam/involute_gear.py @@ -1,67 +1,66 @@ +"""BlenderCAM 'involute_gear.py' Ported by Alain Pelletier Jan 2022 + +from: +Public Domain Parametric Involute Spur Gear (and involute helical gear and involute rack) +version 1.1 +by Leemon Baird, 2011, Leemon@Leemon.com +http:www.thingiverse.com/thing:5505 + +This file is public domain. Use it for any purpose, including commercial +applications. Attribution would be nice, but is not required. There is +no warranty of any kind, including its correctness, usefulness, or safety. + +This is parameterized involute spur (or helical) gear. It is much simpler and less powerful than +others on Thingiverse. But it is public domain. I implemented it from scratch from the +descriptions and equations on Wikipedia and the web, using Mathematica for calculations and testing, +and I now release it into the public domain. + + http:en.wikipedia.org/wiki/Involute_gear + http:en.wikipedia.org/wiki/Gear + http:en.wikipedia.org/wiki/List_of_gear_nomenclature + http:gtrebaol.free.fr/doc/catia/spur_gear.html + http:www.cs.cmu.edu/~rapidproto/mechanisms/chpt7.html + +The module gear() gives an involute spur gear, with reasonable defaults for all the parameters. +Normally, you should just choose the first 4 parameters, and let the rest be default values. +The module gear() gives a gear in the XY plane, centered on the origin, with one tooth centered on +the positive Y axis. The various functions below it take the same parameters, and return various +measurements for the gear. The most important is pitch_radius, which tells how far apart to space +gears that are meshing, and adendum_radius, which gives the size of the region filled by the gear. +A gear has a "pitch circle", which is an invisible circle that cuts through the middle of each +tooth (though not the exact center). In order for two gears to mesh, their pitch circles should +just touch. So the distance between their centers should be pitch_radius() for one, plus pitch_radius() +for the other, which gives the radii of their pitch circles. + +In order for two gears to mesh, they must have the same mm_per_tooth and pressure_angle parameters. +mm_per_tooth gives the number of millimeters of arc around the pitch circle covered by one tooth and one +space between teeth. The pitch angle controls how flat or bulged the sides of the teeth are. Common +values include 14.5 degrees and 20 degrees, and occasionally 25. Though I've seen 28 recommended for +plastic gears. Larger numbers bulge out more, giving stronger teeth, so 28 degrees is the default here. + +The ratio of number_of_teeth for two meshing gears gives how many times one will make a full +revolution when the the other makes one full revolution. If the two numbers are coprime (i.e. +are not both divisible by the same number greater than 1), then every tooth on one gear +will meet every tooth on the other, for more even wear. So coprime numbers of teeth are good. + +The module rack() gives a rack, which is a bar with teeth. A rack can mesh with any +gear that has the same mm_per_tooth and pressure_angle. + +Some terminology: +The outline of a gear is a smooth circle (the "pitch circle") which has mountains and valleys +added so it is toothed. So there is an inner circle (the "root circle") that touches the +base of all the teeth, an outer circle that touches the tips of all the teeth, +and the invisible pitch circle in between them. There is also a "base circle", which can be smaller than +all three of the others, which controls the shape of the teeth. The side of each tooth lies on the path +that the end of a string would follow if it were wrapped tightly around the base circle, then slowly unwound. +That shape is an "involute", which gives this type of gear its name. + +An involute spur gear, with reasonable defaults for all the parameters. +Normally, you should just choose the first 4 parameters, and let the rest be default values. +Meshing gears must match in mm_per_tooth, pressure_angle, and twist, +and be separated by the sum of their pitch radii, which can be found with pitch_radius(). """ -////////////////////////////////////////////////////////////////////////////////////////////// -// Public Domain Parametric Involute Spur Gear (and involute helical gear and involute rack) -// version 1.1 -// by Leemon Baird, 2011, Leemon@Leemon.com -//http://www.thingiverse.com/thing:5505 -// -// This file is public domain. Use it for any purpose, including commercial -// applications. Attribution would be nice, but is not required. There is -// no warranty of any kind, including its correctness, usefulness, or safety. -// -// This is parameterized involute spur (or helical) gear. It is much simpler and less powerful than -// others on Thingiverse. But it is public domain. I implemented it from scratch from the -// descriptions and equations on Wikipedia and the web, using Mathematica for calculations and testing, -// and I now release it into the public domain. -// -// http://en.wikipedia.org/wiki/Involute_gear -// http://en.wikipedia.org/wiki/Gear -// http://en.wikipedia.org/wiki/List_of_gear_nomenclature -// http://gtrebaol.free.fr/doc/catia/spur_gear.html -// http://www.cs.cmu.edu/~rapidproto/mechanisms/chpt7.html -// -// The module gear() gives an involute spur gear, with reasonable defaults for all the parameters. -// Normally, you should just choose the first 4 parameters, and let the rest be default values. -// The module gear() gives a gear in the XY plane, centered on the origin, with one tooth centered on -// the positive Y axis. The various functions below it take the same parameters, and return various -// measurements for the gear. The most important is pitch_radius, which tells how far apart to space -// gears that are meshing, and adendum_radius, which gives the size of the region filled by the gear. -// A gear has a "pitch circle", which is an invisible circle that cuts through the middle of each -// tooth (though not the exact center). In order for two gears to mesh, their pitch circles should -// just touch. So the distance between their centers should be pitch_radius() for one, plus pitch_radius() -// for the other, which gives the radii of their pitch circles. -// -// In order for two gears to mesh, they must have the same mm_per_tooth and pressure_angle parameters. -// mm_per_tooth gives the number of millimeters of arc around the pitch circle covered by one tooth and one -// space between teeth. The pitch angle controls how flat or bulged the sides of the teeth are. Common -// values include 14.5 degrees and 20 degrees, and occasionally 25. Though I've seen 28 recommended for -// plastic gears. Larger numbers bulge out more, giving stronger teeth, so 28 degrees is the default here. -// -// The ratio of number_of_teeth for two meshing gears gives how many times one will make a full -// revolution when the the other makes one full revolution. If the two numbers are coprime (i.e. -// are not both divisible by the same number greater than 1), then every tooth on one gear -// will meet every tooth on the other, for more even wear. So coprime numbers of teeth are good. -// -// The module rack() gives a rack, which is a bar with teeth. A rack can mesh with any -// gear that has the same mm_per_tooth and pressure_angle. -// -// Some terminology: -// The outline of a gear is a smooth circle (the "pitch circle") which has mountains and valleys -// added so it is toothed. So there is an inner circle (the "root circle") that touches the -// base of all the teeth, an outer circle that touches the tips of all the teeth, -// and the invisible pitch circle in between them. There is also a "base circle", which can be smaller than -// all three of the others, which controls the shape of the teeth. The side of each tooth lies on the path -// that the end of a string would follow if it were wrapped tightly around the base circle, then slowly unwound. -// That shape is an "involute", which gives this type of gear its name. -// -////////////////////////////////////////////////////////////////////////////////////////////// - -//An involute spur gear, with reasonable defaults for all the parameters. -//Normally, you should just choose the first 4 parameters, and let the rest be default values. -//Meshing gears must match in mm_per_tooth, pressure_angle, and twist, -//and be separated by the sum of their pitch radii, which can be found with pitch_radius(). """ - -# ported to Blendercam by Alain Pelletier Jan 2022 + from math import ( acos, cos, diff --git a/scripts/addons/cam/joinery.py b/scripts/addons/cam/joinery.py index 9857e99c8..eb86b130b 100644 --- a/scripts/addons/cam/joinery.py +++ b/scripts/addons/cam/joinery.py @@ -1,23 +1,7 @@ -# blender CAM ops.py (c) 2021 Alain Pelletier -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'joinery.py' © 2021 Alain Pelletier + +Functions to create various woodworking joints - mortise, finger etc. +""" # blender operators definitions are in this file. They mostly call the functions from utils.py from math import ( diff --git a/scripts/addons/cam/machine_settings.py b/scripts/addons/cam/machine_settings.py index 58178bace..d8094bf5b 100644 --- a/scripts/addons/cam/machine_settings.py +++ b/scripts/addons/cam/machine_settings.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'machine_settings.py' + +All CAM machine properties. +""" + from bpy.props import ( BoolProperty, EnumProperty, diff --git a/scripts/addons/cam/numba_wrapper.py b/scripts/addons/cam/numba_wrapper.py index 0da9255db..e21756a74 100644 --- a/scripts/addons/cam/numba_wrapper.py +++ b/scripts/addons/cam/numba_wrapper.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'numba_wrapper.py' + +Patch to ensure functions will run if numba is unavailable. +""" + try: from numba import jit, prange print("numba: yes") diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index e99f5425f..58c83722d 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'oclSample.py' + +Functions to sample mesh or curve data for OpenCAMLib processing. +""" + from math import ( radians, tan diff --git a/scripts/addons/cam/opencamlib/opencamlib.py b/scripts/addons/cam/opencamlib/opencamlib.py index d84e2680d..9e74a4ac2 100644 --- a/scripts/addons/cam/opencamlib/opencamlib.py +++ b/scripts/addons/cam/opencamlib/opencamlib.py @@ -1,4 +1,8 @@ -# used by OpenCAMLib sampling +"""BlenderCAM 'oclSample.py' + +Functions used by OpenCAMLib sampling. +""" + import os from subprocess import call import tempfile diff --git a/scripts/addons/cam/ops.py b/scripts/addons/cam/ops.py index f96132a99..3714048e0 100644 --- a/scripts/addons/cam/ops.py +++ b/scripts/addons/cam/ops.py @@ -1,25 +1,9 @@ -# blender CAM ops.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# blender operators definitions are in this file. They mostly call the functions from utils.py +"""BlenderCAM 'ops.py' © 2012 Vilem Novak + +Blender Operator definitions are in this file. +They mostly call the functions from 'utils.py' +""" + import os import subprocess import textwrap diff --git a/scripts/addons/cam/pack.py b/scripts/addons/cam/pack.py index 28114681e..cda5f0e4e 100644 --- a/scripts/addons/cam/pack.py +++ b/scripts/addons/cam/pack.py @@ -1,23 +1,12 @@ -# blender CAM pack.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'pack.py' © 2012 Vilem Novak + +Takes all selected curves, converts them to polygons, offsets them by the pre-set margin +then chooses a starting location possibly inside the already occupied area and moves and rotates the +polygon out of the occupied area if one or more positions are found where the poly doesn't overlap, +it is placed and added to the occupied area - allpoly +Very slow and STUPID, a collision algorithm would be much much faster... +""" + from math import pi import random import time @@ -50,15 +39,6 @@ ) -# this algorithm takes all selected curves, -# converts them to polygons, -# offsets them by the pre-set margin -# then chooses a starting location possibly inside the allready occupied area and moves and rotates the -# polygon out of the occupied area if one or more positions are found where the poly doesn't overlap, -# it is placed and added to the occupied area - allpoly -# this algorithm is very slow and STUPID, a collision algorithm would be much much faster... - - def srotate(s, r, x, y): ncoords = [] e = Euler((0, 0, r)) diff --git a/scripts/addons/cam/parametric.py b/scripts/addons/cam/parametric.py index b2d60048e..e04b7af2b 100644 --- a/scripts/addons/cam/parametric.py +++ b/scripts/addons/cam/parametric.py @@ -1,34 +1,35 @@ -# MIT License -# -# Copyright (c) 2019 Devon (Gorialis) R -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# -# -# -# Create a Blender curve from a 3D parametric function. -# This allows for a 3D plot to be made of the function, which can be converted into a mesh. -# -# I have documented the inner workings here, but if you're not interested and just want to -# suit this to your own function, scroll down to the bottom and edit the `f(t)` function and -# the iteration count to your liking. -# -# This code has been checked to work on Blender 2.92. +"""BlenderCAM 'parametric.py' © 2019 Devon (Gorialis) R + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + +Summary: +Create a Blender curve from a 3D parametric function. +This allows for a 3D plot to be made of the function, which can be converted into a mesh. + +I have documented the inner workings here, but if you're not interested and just want to +suit this to your own function, scroll down to the bottom and edit the `f(t)` function and +the iteration count to your liking. + +This code has been checked to work on Blender 2.92. +""" + from math import pow import bpy diff --git a/scripts/addons/cam/pattern.py b/scripts/addons/cam/pattern.py index b3f5e37e8..b45044d45 100644 --- a/scripts/addons/cam/pattern.py +++ b/scripts/addons/cam/pattern.py @@ -1,23 +1,8 @@ -# blender CAM pattern.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'pattern.py' © 2012 Vilem Novak + +Functions to read CAM path patterns and return CAM path chunks. +""" + from math import ( ceil, floor, diff --git a/scripts/addons/cam/pie_menu/active_op/pie_area.py b/scripts/addons/cam/pie_menu/active_op/pie_area.py index c9657b93a..6afa7401f 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_area.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_area.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_area.py' + +'Operation Area' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_cutter.py b/scripts/addons/cam/pie_menu/active_op/pie_cutter.py index 0c8ba245c..9752763c7 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_cutter.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_cutter.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_cutter.py' + +'Operation Cutter' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py b/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py index 6cd2cadde..6e4cc74b2 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_feedrate.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_feedrate.py' + +'Operation Feedrate' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_gcode.py b/scripts/addons/cam/pie_menu/active_op/pie_gcode.py index 6e4b88cb8..6554b4ccd 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_gcode.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_gcode.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_gcode.py' + +'Operation G-code' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_movement.py b/scripts/addons/cam/pie_menu/active_op/pie_movement.py index 9d60f276e..3c645aad0 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_movement.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_movement.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_movement.py' + +'Operation Movement' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_operation.py b/scripts/addons/cam/pie_menu/active_op/pie_operation.py index 6c289911b..a55c1c45d 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_operation.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_operation.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_operation.py' + +'Active Operation' Pie Menu - Parent to all active_op Pie Menus +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py b/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py index 9b564dd79..a46fd5533 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_optimisation.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_optimisation.py' + +'Operation Optimisation' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/active_op/pie_setup.py b/scripts/addons/cam/pie_menu/active_op/pie_setup.py index a35c56d7a..d9308c676 100644 --- a/scripts/addons/cam/pie_menu/active_op/pie_setup.py +++ b/scripts/addons/cam/pie_menu/active_op/pie_setup.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_setup.py' + +'Operation Setup' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_cam.py b/scripts/addons/cam/pie_menu/pie_cam.py index 1648d6efb..a8185f65d 100644 --- a/scripts/addons/cam/pie_menu/pie_cam.py +++ b/scripts/addons/cam/pie_menu/pie_cam.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_cam.py' + +'BlenderCAM' Pie Menu - Parent to all other CAM Pie Menus +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_chains.py b/scripts/addons/cam/pie_menu/pie_chains.py index 103b0c3fc..75dc0efbe 100644 --- a/scripts/addons/cam/pie_menu/pie_chains.py +++ b/scripts/addons/cam/pie_menu/pie_chains.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_chains.py' + +'Operations & Chains' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_curvecreators.py b/scripts/addons/cam/pie_menu/pie_curvecreators.py index dbc534f56..41cd7ccdb 100644 --- a/scripts/addons/cam/pie_menu/pie_curvecreators.py +++ b/scripts/addons/cam/pie_menu/pie_curvecreators.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_curvecreators.py' + +'Curve Creators' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_curvetools.py b/scripts/addons/cam/pie_menu/pie_curvetools.py index cf23955cd..13d0050bf 100644 --- a/scripts/addons/cam/pie_menu/pie_curvetools.py +++ b/scripts/addons/cam/pie_menu/pie_curvetools.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_curvetools.py' + +'Curve Tools' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_info.py b/scripts/addons/cam/pie_menu/pie_info.py index 548ac1889..333724f0f 100644 --- a/scripts/addons/cam/pie_menu/pie_info.py +++ b/scripts/addons/cam/pie_menu/pie_info.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_info.py' + +'Info' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_machine.py b/scripts/addons/cam/pie_menu/pie_machine.py index ea331ea3e..32b6ae738 100644 --- a/scripts/addons/cam/pie_menu/pie_machine.py +++ b/scripts/addons/cam/pie_menu/pie_machine.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_machine.py' + +'Machine Settings' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_material.py b/scripts/addons/cam/pie_menu/pie_material.py index 34863cfb8..ee32ff652 100644 --- a/scripts/addons/cam/pie_menu/pie_material.py +++ b/scripts/addons/cam/pie_menu/pie_material.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_material.py' + +'Material Size & Position' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py b/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py index 693f424fe..8600c5b09 100644 --- a/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py +++ b/scripts/addons/cam/pie_menu/pie_pack_slice_relief.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pie_pack_slice_relief.py' + +'Pack, Slice and Bas Relief' Pie Menu +""" + import bpy from bpy.types import Menu diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index 06ebc53ac..14ce3dcea 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -1,23 +1,7 @@ -# blender CAM polygon_utils_cam.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'polygon_utils_cam.py' © 2012 Vilem Novak + +Functions to handle shapely operations and conversions - curve, coords, polygon +""" from math import pi diff --git a/scripts/addons/cam/preferences.py b/scripts/addons/cam/preferences.py index b2a67c797..553315fa0 100644 --- a/scripts/addons/cam/preferences.py +++ b/scripts/addons/cam/preferences.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'preferences.py' + +Class to store all Addon preferences. +""" + from bpy.props import ( BoolProperty, EnumProperty, diff --git a/scripts/addons/cam/preset_managers.py b/scripts/addons/cam/preset_managers.py index 751925ddf..6d06b73d8 100644 --- a/scripts/addons/cam/preset_managers.py +++ b/scripts/addons/cam/preset_managers.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'preset_managers.py' + +Operators and Menus for CAM Machine, Cutter and Operation Presets. +""" + import bpy from bl_operators.presets import AddPresetBase from bpy.types import ( diff --git a/scripts/addons/cam/puzzle_joinery.py b/scripts/addons/cam/puzzle_joinery.py index f547b19c3..91ee1c29b 100644 --- a/scripts/addons/cam/puzzle_joinery.py +++ b/scripts/addons/cam/puzzle_joinery.py @@ -1,25 +1,8 @@ -# blender CAM ops.py (c) 2021 Alain Pelletier -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# blender operators definitions are in this file. They mostly call the functions from curvecamcreate.py +"""BlenderCAM 'puzzle_joinery.py' © 2021 Alain Pelletier + +Functions to add various puzzle joints as curves. +""" + from math import ( cos, degrees, diff --git a/scripts/addons/cam/simple.py b/scripts/addons/cam/simple.py index b75460839..388324a0c 100644 --- a/scripts/addons/cam/simple.py +++ b/scripts/addons/cam/simple.py @@ -1,23 +1,8 @@ -# blender CAM simple.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'simple.py' © 2012 Vilem Novak + +Various helper functions, less complex than those found in the 'utils' files. +""" + from math import ( hypot, pi, diff --git a/scripts/addons/cam/simulation.py b/scripts/addons/cam/simulation.py index dbdba25d1..088804671 100644 --- a/scripts/addons/cam/simulation.py +++ b/scripts/addons/cam/simulation.py @@ -1,25 +1,8 @@ -# blender CAM utils.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. +"""BlenderCAM 'simulation.py' © 2012 Vilem Novak + +Functions to generate a mesh simulation from CAM Chain / Operation data. +""" + import math import time diff --git a/scripts/addons/cam/slice.py b/scripts/addons/cam/slice.py index 1cc9493fc..5db0a8419 100644 --- a/scripts/addons/cam/slice.py +++ b/scripts/addons/cam/slice.py @@ -1,26 +1,8 @@ -# blender CAM slice.py (c) 2021 Alain Pelletier -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# very simple slicing for 3d meshes, useful for plywood cutting. -# completely rewritten April 2021 +"""BlenderCAM 'slice.py' © 2021 Alain Pelletier + +Very simple slicing for 3D meshes, useful for plywood cutting. +Completely rewritten April 2021. +""" import bpy from bpy.props import ( diff --git a/scripts/addons/cam/strategy.py b/scripts/addons/cam/strategy.py index ccadaffcb..c8c7b9f99 100644 --- a/scripts/addons/cam/strategy.py +++ b/scripts/addons/cam/strategy.py @@ -1,25 +1,9 @@ -# blender CAM strategy.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# here is the strategy functionality of Blender CAM. The functions here are called with operators defined in ops.py. +"""BlenderCAM 'strategy.py' © 2012 Vilem Novak + +Strategy functionality of BlenderCAM - e.g. Cutout, Parallel, Spiral, Waterline +The functions here are called with operators defined in 'ops.py' +""" + from math import ( ceil, pi, diff --git a/scripts/addons/cam/testing.py b/scripts/addons/cam/testing.py index b5ebcfd37..73e7d288d 100644 --- a/scripts/addons/cam/testing.py +++ b/scripts/addons/cam/testing.py @@ -1,23 +1,8 @@ -# blender CAM testing.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'testing.py' © 2012 Vilem Novak + +Functions for automated testing. +""" + import bpy from .gcodepath import getPath diff --git a/scripts/addons/cam/ui.py b/scripts/addons/cam/ui.py index 8f8cc786a..b77d295f0 100644 --- a/scripts/addons/cam/ui.py +++ b/scripts/addons/cam/ui.py @@ -1,23 +1,8 @@ -# blender CAM ui.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** +"""BlenderCAM 'ui.py' © 2012 Vilem Novak + +Panels displayed in the 3D Viewport - Curve Tools, Creators and Import G-code +""" + from bpy_extras.io_utils import ImportHelper from bpy.props import ( BoolProperty, diff --git a/scripts/addons/cam/ui_panels/area.py b/scripts/addons/cam/ui_panels/area.py index af6cf76f2..4d3165e36 100644 --- a/scripts/addons/cam/ui_panels/area.py +++ b/scripts/addons/cam/ui_panels/area.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'area.py' + +'CAM Operation Area' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/buttons_panel.py b/scripts/addons/cam/ui_panels/buttons_panel.py index 07dfc7973..c943cdf2f 100644 --- a/scripts/addons/cam/ui_panels/buttons_panel.py +++ b/scripts/addons/cam/ui_panels/buttons_panel.py @@ -1,3 +1,9 @@ +"""BlenderCAM 'buttons_panel.py' + +Parent (Mixin) class for all panels in 'ui_panels' +Sets up polling and operations to show / hide panels based on Interface Level +""" + import inspect import bpy diff --git a/scripts/addons/cam/ui_panels/chains.py b/scripts/addons/cam/ui_panels/chains.py index 4c42aa36e..612a54dbb 100644 --- a/scripts/addons/cam/ui_panels/chains.py +++ b/scripts/addons/cam/ui_panels/chains.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'chains.py' + +'CAM Chains' panel in Properties > Render +""" + import bpy from bpy.types import UIList, Panel diff --git a/scripts/addons/cam/ui_panels/cutter.py b/scripts/addons/cam/ui_panels/cutter.py index 1a2ddc207..68bb544ad 100644 --- a/scripts/addons/cam/ui_panels/cutter.py +++ b/scripts/addons/cam/ui_panels/cutter.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'cutter.py' + +'CAM Cutter' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/feedrate.py b/scripts/addons/cam/ui_panels/feedrate.py index 84d1a3877..598b8777d 100644 --- a/scripts/addons/cam/ui_panels/feedrate.py +++ b/scripts/addons/cam/ui_panels/feedrate.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'feedrate.py' + +'CAM Feedrate' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/gcode.py b/scripts/addons/cam/ui_panels/gcode.py index 95991cbf9..b275a9a03 100644 --- a/scripts/addons/cam/ui_panels/gcode.py +++ b/scripts/addons/cam/ui_panels/gcode.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'gcode.py' + +'CAM G-code Options' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/info.py b/scripts/addons/cam/ui_panels/info.py index cbab7838d..952ffd4c8 100644 --- a/scripts/addons/cam/ui_panels/info.py +++ b/scripts/addons/cam/ui_panels/info.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'info.py' + +'CAM Info & Warnings' properties and panel in Properties > Render +""" + import bpy from bpy.props import ( StringProperty, diff --git a/scripts/addons/cam/ui_panels/interface.py b/scripts/addons/cam/ui_panels/interface.py index 61ba69410..881a699b6 100644 --- a/scripts/addons/cam/ui_panels/interface.py +++ b/scripts/addons/cam/ui_panels/interface.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'interface.py' + +'Interface' properties and panel in Properties > Render +""" + import bpy from bpy.props import EnumProperty from bpy.types import ( diff --git a/scripts/addons/cam/ui_panels/machine.py b/scripts/addons/cam/ui_panels/machine.py index b34c97c68..c789bd5c5 100644 --- a/scripts/addons/cam/ui_panels/machine.py +++ b/scripts/addons/cam/ui_panels/machine.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'machine.py' + +'CAM Machine' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/material.py b/scripts/addons/cam/ui_panels/material.py index dd3075508..fde835961 100644 --- a/scripts/addons/cam/ui_panels/material.py +++ b/scripts/addons/cam/ui_panels/material.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'material.py' + +'CAM Material' properties and panel in Properties > Render +""" + import bpy from bpy.props import ( BoolProperty, diff --git a/scripts/addons/cam/ui_panels/movement.py b/scripts/addons/cam/ui_panels/movement.py index 3dc111c26..c76bc85d6 100644 --- a/scripts/addons/cam/ui_panels/movement.py +++ b/scripts/addons/cam/ui_panels/movement.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'movement.py' + +'CAM Movement' properties and panel in Properties > Render +""" + from math import pi import bpy diff --git a/scripts/addons/cam/ui_panels/op_properties.py b/scripts/addons/cam/ui_panels/op_properties.py index ef1e2f066..ccc04a0ec 100644 --- a/scripts/addons/cam/ui_panels/op_properties.py +++ b/scripts/addons/cam/ui_panels/op_properties.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'op_properties.py' + +'CAM Operation Setup' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/operations.py b/scripts/addons/cam/ui_panels/operations.py index d5d8ca5c9..10506cb90 100644 --- a/scripts/addons/cam/ui_panels/operations.py +++ b/scripts/addons/cam/ui_panels/operations.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'operations.py' + +'CAM Operations' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/optimisation.py b/scripts/addons/cam/ui_panels/optimisation.py index 39bcdd416..d23b9ec68 100644 --- a/scripts/addons/cam/ui_panels/optimisation.py +++ b/scripts/addons/cam/ui_panels/optimisation.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'optimisation.py' + +'CAM Optimisation' properties and panel in Properties > Render +""" + import bpy from bpy.props import ( BoolProperty, diff --git a/scripts/addons/cam/ui_panels/pack.py b/scripts/addons/cam/ui_panels/pack.py index 105c30923..e13df89ea 100644 --- a/scripts/addons/cam/ui_panels/pack.py +++ b/scripts/addons/cam/ui_panels/pack.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'pack.py' + +'Pack Curves on Sheet' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/ui_panels/slice.py b/scripts/addons/cam/ui_panels/slice.py index 8a1daebb7..8a0db9570 100644 --- a/scripts/addons/cam/ui_panels/slice.py +++ b/scripts/addons/cam/ui_panels/slice.py @@ -1,3 +1,8 @@ +"""BlenderCAM 'slice.py' + +'Slice Model to Plywood Sheets' panel in Properties > Render +""" + import bpy from bpy.types import Panel diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 750573de9..6d8fdba41 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -1,26 +1,9 @@ +"""BlenderCAM 'utils.py' © 2012 Vilem Novak + +Main functionality of BlenderCAM. +The functions here are called with operators defined in 'ops.py' +""" -# blender CAM utils.py (c) 2012 Vilem Novak -# -# ***** BEGIN GPL LICENSE BLOCK ***** -# -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ***** END GPL LICENCE BLOCK ***** - -# here is the main functionality of Blender CAM. The functions here are called with operators defined in ops.py. from math import ( ceil, pi diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index c39d9ae94..11d95adc4 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1,6 @@ -__version__=(1,0,19) \ No newline at end of file +"""BlenderCAM 'version.py' + +Addon version, read and incremented by Github Actions +""" + +__version__ = (1, 0, 19) diff --git a/scripts/addons/cam/voronoi.py b/scripts/addons/cam/voronoi.py index 32f985d18..800075ac5 100644 --- a/scripts/addons/cam/voronoi.py +++ b/scripts/addons/cam/voronoi.py @@ -1,66 +1,63 @@ -# -*- coding: utf-8 -*- +"""BlenderCAM 'voronoi.py' + +Voronoi diagram calculator/ Delaunay triangulator + +- Voronoi Diagram Sweepline algorithm and C code by Steven Fortune, 1987, http://ect.bell-labs.com/who/sjf/ +- Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ +- Additional changes for QGIS by Carson Farmer added November 2010 +- 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com + +Calculate Delaunay triangulation or the Voronoi polygons for a set of +2D input points. + +Derived from code bearing the following notice: + +The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T +Bell Laboratories. +Permission to use, copy, modify, and distribute this software for any +purpose without fee is hereby granted, provided that this entire notice +is included in all copies of any software which is or includes a copy +or modification of this software and in all copies of the supporting +documentation for such software. +THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + +Comments were incorporated from Shane O'Sullivan's translation of the +original code into C++ (http://mapviewer.skynet.ie/voronoi.html) + +Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html + + +For programmatic use two functions are available: + +computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False) : +Takes : + - a list of point objects (which must have x and y fields). + - x and y buffer values which are the expansion percentages of the bounding box rectangle including all input points. + Returns : + - With default options : + A list of 2-tuples, representing the two points of each Voronoi diagram edge. + Each point contains 2-tuples which are the x,y coordinates of point. + if formatOutput is True, returns : + - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. + - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram. + v1 and v2 are the indices of the vertices at the end of the edge. + - If polygonsOutput option is True, returns : + A dictionary of polygons, keys are the indices of the input points, + values contains n-tuples representing the n points of each Voronoi diagram polygon. + Each point contains 2-tuples which are the x,y coordinates of point. + if formatOutput is True, returns : + - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. + - and a dictionary of input points indices. Values contains n-tuples representing the n points of each Voronoi diagram polygon. + Each tuple contains the vertex indices of the polygon vertices. + +computeDelaunayTriangulation(points): + Takes a list of point objects (which must have x and y fields). + Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle. +""" -############################################################################# -# -# Voronoi diagram calculator/ Delaunay triangulator -# -# - Voronoi Diagram Sweepline algorithm and C code by Steven Fortune, 1987, http://ect.bell-labs.com/who/sjf/ -# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ -# - Additional changes for QGIS by Carson Farmer added November 2010 -# - 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com -# -# Calculate Delaunay triangulation or the Voronoi polygons for a set of -# 2D input points. -# -# Derived from code bearing the following notice: -# -# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T -# Bell Laboratories. -# Permission to use, copy, modify, and distribute this software for any -# purpose without fee is hereby granted, provided that this entire notice -# is included in all copies of any software which is or includes a copy -# or modification of this software and in all copies of the supporting -# documentation for such software. -# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED -# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY -# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY -# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. -# -# Comments were incorporated from Shane O'Sullivan's translation of the -# original code into C++ (http://mapviewer.skynet.ie/voronoi.html) -# -# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html -# -# -# -# For programmatic use two functions are available: -# -# computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False) : -# Takes : -# - a list of point objects (which must have x and y fields). -# - x and y buffer values which are the expansion percentages of the bounding box rectangle including all input points. -# Returns : -# - With default options : -# A list of 2-tuples, representing the two points of each Voronoi diagram edge. -# Each point contains 2-tuples which are the x,y coordinates of point. -# if formatOutput is True, returns : -# - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. -# - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram. -# v1 and v2 are the indices of the vertices at the end of the edge. -# - If polygonsOutput option is True, returns : -# A dictionary of polygons, keys are the indices of the input points, -# values contains n-tuples representing the n points of each Voronoi diagram polygon. -# Each point contains 2-tuples which are the x,y coordinates of point. -# if formatOutput is True, returns : -# - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. -# - and a dictionary of input points indices. Values contains n-tuples representing the n points of each Voronoi diagram polygon. -# Each tuple contains the vertex indices of the polygon vertices. -# -# computeDelaunayTriangulation(points): -# Takes a list of point objects (which must have x and y fields). -# Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle. -# -############################################################################# import math import sys From 8d376e2b7524d7f21f31a5f774f8a0305eb4f90f Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:14:32 -0400 Subject: [PATCH 079/100] Update README.md --- README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c4599ece..4363bb85c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@
-[About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-disclaimer) +[About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer)
@@ -138,6 +138,15 @@ If you are a developer who would like to contribute to the project, please fork ### Additional Contributors & Acknowledgements Hirutso Enni, Kurt Jensen, Dan Falck, Dan Heeks, Brad Collette, Michael Haberler, dhull, jonathanwin, Leemon Baird, Devon (Gorialis) R, Steven Fortune, Bill Simons, Carson Farmer, domlysz +## 🪪 License +BlenderCAM is licensed under GPLv3, __UNLESS OTHERWISE INDICATED__. + +Most files have been written specifically for this addon, though some use code from other sources, see the file docstring a the top of each file for attribution and license information. + +If you wish to contribute to the addon, your code must be GPL or a more permissive license (e.g.: MIT, Public Domain). + +Please ensure that you read and abide by the license terms given for each file. + ## 🤕 DISCLAIMER > [!WARNING] THE AUTHORS OF THIS SOFTWARE ACCEPT ABSOLUTELY NO LIABILITY FOR @@ -154,5 +163,3 @@ from all motors, etc, before persons enter any danger area. machinery must be designed to comply with local and national safety codes, and the authors of this software can not, and do not, take any responsibility for such compliance. - -This software is released under the GPLv2. From a331ef695a34ca36dd065ff162837997eaaee21d Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:17:24 -0400 Subject: [PATCH 080/100] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4363bb85c..f7321359a 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,11 @@ [![Chat on Matrix](https://img.shields.io/matrix/blendercam:matrix.org?label=Chat%20on%20Matrix)](https://riot.im/app/#/room/#blendercam:matrix.org) [![Chat on Freenode](https://img.shields.io/badge/chat-on%20freenode-brightgreen.svg)](http://webchat.freenode.net/?channels=%23blendercam) -[![Chat on Freenode](https://img.shields.io/github/issues/vilemduha/blendercam)](https://github.com/vilemduha/blendercam) + +[![Issues](https://img.shields.io/github/issues/vilemduha/blendercam)](https://github.com/vilemduha/blendercam) ![Last commit](https://img.shields.io/github/last-commit/vilemduha/blendercam) ![Contributors](https://img.shields.io/github/contributors/vilemduha/blendercam) + ![Size](https://img.shields.io/github/repo-size/vilemduha/blendercam) ![License](https://img.shields.io/github/license/vilemduha/blendercam) From a75d58eaf41315b55f2a7c93e63aa2484a9854db Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:34:50 -0400 Subject: [PATCH 081/100] Update README.md --- README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f7321359a..230d4b400 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,8 @@ - - - - ### An Open Source Solution for Artistic or Industrial CAM with Blender 3D - - [![Chat on Matrix](https://img.shields.io/matrix/blendercam:matrix.org?label=Chat%20on%20Matrix)](https://riot.im/app/#/room/#blendercam:matrix.org) [![Chat on Freenode](https://img.shields.io/badge/chat-on%20freenode-brightgreen.svg)](http://webchat.freenode.net/?channels=%23blendercam) @@ -19,22 +16,22 @@ ![Size](https://img.shields.io/github/repo-size/vilemduha/blendercam) ![License](https://img.shields.io/github/license/vilemduha/blendercam) -
- - -[About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer) +- - - +### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer) • -
+- - - ![BlenderCAM](documentation/images/suzanne.gif) +- - - + ## 👁️ About BlenderCAM is an add-on for the free open-source [Blender 3D package](https://www.blender.org/). -It offers an open source solution for [CAM _(Computer Aided Machining)_](https://en.wikipedia.org/wiki/Computer-aided_manufacturing) toolpath generation, simulation and [G-code](https://en.wikipedia.org/wiki/G-code) export. +It offers an open source solution for [CAM _(Computer Aided Machining)_](https://en.wikipedia.org/wiki/Computer-aided_manufacturing) toolpath creation, simulation and [G-code](https://en.wikipedia.org/wiki/G-code) generation and export. It has been used for many milling projects _(artistic, personal, commercial and industrial)_ since its creation in 2012, and is actively developed. @@ -127,6 +124,8 @@ BlenderCAM works on Windows or Linux. Originally created by [Vilem Novak](https://github.com/vilemduha), the addon is currently maintained by [Alain Pelletier](https://github.com/pppalain) and a team of contributors. +If you wish to contribute to the addon, your code must be GPL or a more permissive license (e.g.: MIT, Public Domain). + If you are a developer who would like to contribute to the project, please fork and open pull requests. > [!TIP] @@ -143,11 +142,10 @@ Hirutso Enni, Kurt Jensen, Dan Falck, Dan Heeks, Brad Collette, Michael Haberler ## 🪪 License BlenderCAM is licensed under GPLv3, __UNLESS OTHERWISE INDICATED__. -Most files have been written specifically for this addon, though some use code from other sources, see the file docstring a the top of each file for attribution and license information. - -If you wish to contribute to the addon, your code must be GPL or a more permissive license (e.g.: MIT, Public Domain). - -Please ensure that you read and abide by the license terms given for each file. +> [!NOTE] +> Some files in this addon use code from other sources, see the file docstring a the top of each file for attribution and license information. +> +> Please ensure that you read and abide by the license terms given for each file. ## 🤕 DISCLAIMER > [!WARNING] From 7ab8e87f6541e8715d5f4980d3a4c3ff087bb0e0 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:36:49 -0400 Subject: [PATCH 082/100] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 230d4b400..5ea9ecbd4 100644 --- a/README.md +++ b/README.md @@ -124,10 +124,10 @@ BlenderCAM works on Windows or Linux. Originally created by [Vilem Novak](https://github.com/vilemduha), the addon is currently maintained by [Alain Pelletier](https://github.com/pppalain) and a team of contributors. -If you wish to contribute to the addon, your code must be GPL or a more permissive license (e.g.: MIT, Public Domain). - If you are a developer who would like to contribute to the project, please fork and open pull requests. +If you wish to contribute to the addon, your code must be GPL or a more permissive license (e.g.: MIT, Public Domain). + > [!TIP] > _If you need help or want to discuss about BlenderCAM you can join the [Chat Room #BlenderCAM:matrix.org on Matrix](https://riot.im/app/#/room/#blendercam:matrix.org)._ From 8b3ea167a6fde53ec8dd1f4139f2ce893906c169 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:46:51 -0400 Subject: [PATCH 083/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ea9ecbd4..e5b01cfa7 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - - - -### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer) • +### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer) • - - - From 299fc9f06b58f23f79c09d5cb66c5f2f7da012f3 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:50:19 -0400 Subject: [PATCH 084/100] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e5b01cfa7..70e7735bc 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - - - -### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contributing](#-contributing) • [License](#-license) • [Disclaimer](#-disclaimer) • +### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contribute](#-contribute) • [License](#-license) • [Disclaimer](#-disclaimer) • - - - @@ -119,7 +119,7 @@ BlenderCAM works on Windows or Linux. -## 🤝 Contributing +## 🤝 Contribute #### BlenderCAM is in active development. Originally created by [Vilem Novak](https://github.com/vilemduha), the addon is currently maintained by [Alain Pelletier](https://github.com/pppalain) and a team of contributors. @@ -143,9 +143,9 @@ Hirutso Enni, Kurt Jensen, Dan Falck, Dan Heeks, Brad Collette, Michael Haberler BlenderCAM is licensed under GPLv3, __UNLESS OTHERWISE INDICATED__. > [!NOTE] -> Some files in this addon use code from other sources, see the file docstring a the top of each file for attribution and license information. +> _Some files in this addon use code from other sources, see the file docstring a the top of each file for attribution and license information._ > -> Please ensure that you read and abide by the license terms given for each file. +> _Please ensure that you read and abide by the license terms given for each file._ ## 🤕 DISCLAIMER > [!WARNING] From 042e3cc70be4023b88a9cedcd79aabb5d900917c Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:50:56 -0400 Subject: [PATCH 085/100] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70e7735bc..880b5fbbd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - - - -### • [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contribute](#-contribute) • [License](#-license) • [Disclaimer](#-disclaimer) • +### [About](#About) • [How to Use](#-how-to-use-wiki) • [Features](#-features) • [Post-Processors](#-post-processors) • [Files](#-files-organisation) • [Contribute](#-contribute) • [License](#-license) • [Disclaimer](#-disclaimer) - - - From 520f89800a871541e6b9ec0834e2725a1e07b208 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Mon, 15 Apr 2024 10:58:44 -0400 Subject: [PATCH 086/100] Update README.md From f293cb745950a221f6b086cec70878aa6ebe7384 Mon Sep 17 00:00:00 2001 From: abosafia Date: Wed, 24 Apr 2024 17:56:01 +0200 Subject: [PATCH 087/100] fix overcut --- scripts/addons/cam/curvecamtools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 6773322c6..64b7eb9f9 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -277,7 +277,7 @@ def execute(self, context): v.normalize() p = p - v * diameter / 2 if abs(a) < pi / 2: - shape = utils.Circle(diameter / 2, 64) + shape = polygon_utils_cam.Circle(diameter / 2, 64) shape = shapely.affinity.translate( shape, p.x, p.y) else: @@ -390,7 +390,7 @@ def addOvercut(a): print("abs(a)", abs(a)) if abs(a) <= pi / 2 + 0.0001: print("<=pi/2") - shape = utils.Circle(radius, 64) + shape = polygon_utils_cam.Circle(radius, 64) shape = shapely.affinity.translate(shape, pos.x, pos.y) else: # elongate overcut circle to make sure tool bit can fit into slot print(">pi/2") From 6f73e7fc5553739da0caf91c7b99084e2e4248c0 Mon Sep 17 00:00:00 2001 From: abosafia Date: Thu, 25 Apr 2024 09:21:49 +0200 Subject: [PATCH 088/100] reduce the remove_ double_ distance --- scripts/addons/cam/curvecamtools.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 64b7eb9f9..0ca3fc9d7 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -586,15 +586,13 @@ def poll(cls, context): def execute(self, context): obs = bpy.context.selected_objects - #bpy.context.object.data.dimensions = '3D' - #bpy.context.object.data.resolution_u = 32 for ob in obs: bpy.context.view_layer.objects.active = ob if bpy.context.mode == 'OBJECT': bpy.ops.object.editmode_toggle() bpy.ops.curve.select_all() - bpy.ops.curve.decimate(ratio=1) - bpy.ops.curve.remove_double(distance=0.0001) + bpy.ops.curve.spline_type_set(type='BEZIER') + bpy.ops.curve.remove_double(distance=0.00001) bpy.ops.object.editmode_toggle() return {'FINISHED'} From 2591989a7945d168c4a7a530f23d2ed3c94552ed Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 25 Apr 2024 17:05:00 -0400 Subject: [PATCH 089/100] Curve Addons, Version fix Added a few lines to the end of the register function in the __init__ file to ensure that the required curve addons are activated when the addon is installed. Also removed the docstring from version.py as I believe it interfered with the regex in the Github Action --- scripts/addons/cam/__init__.py | 14 +++++++++++++- scripts/addons/cam/version.py | 7 +------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index c14b4100c..65179264d 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 19), + "version": (1, 0, 20), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", @@ -397,6 +397,18 @@ def register() -> None: kmi.properties.name = 'VIEW3D_MT_PIE_CAM' kmi.active = True + addons = bpy.context.preferences.addons + + modules = [ + "curve_tools", + "curve_simplify", + "add_curve_extra_objects", + ] + + for module in modules: + if module not in addons: + bpy.ops.preferences.addon_enable(module=module) + def unregister() -> None: for cls in classes: diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 11d95adc4..e58cdfa4f 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1,6 +1 @@ -"""BlenderCAM 'version.py' - -Addon version, read and incremented by Github Actions -""" - -__version__ = (1, 0, 19) +__version__ = (1, 0, 20) From 0e25faad348a0b30f1f3cdc3be8231d73ba57316 Mon Sep 17 00:00:00 2001 From: SpectralVectors Date: Thu, 25 Apr 2024 17:24:12 -0400 Subject: [PATCH 090/100] Curve Addon activation moved Blender restricts access to context (required to read and update the addons in preferences) during registration, so the initial implementation would not work. This update moves the logic into the utils file in the `check_operations_on_load` function. --- scripts/addons/cam/__init__.py | 12 ------------ scripts/addons/cam/utils.py | 13 +++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 65179264d..02f70dba1 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -397,18 +397,6 @@ def register() -> None: kmi.properties.name = 'VIEW3D_MT_PIE_CAM' kmi.active = True - addons = bpy.context.preferences.addons - - modules = [ - "curve_tools", - "curve_simplify", - "add_curve_extra_objects", - ] - - for module in modules: - if module not in addons: - bpy.ops.preferences.addon_enable(module=module) - def unregister() -> None: for cls in classes: diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 6d8fdba41..8b2e97981 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -2085,6 +2085,19 @@ def update_zbuffer_image(self, context): @bpy.app.handlers.persistent def check_operations_on_load(context): """Checks Any Broken Computations on Load and Reset Them.""" + + addons = bpy.context.preferences.addons + + modules = [ + "curve_tools", + "curve_simplify", + "add_curve_extra_objects", + ] + + for module in modules: + if module not in addons: + bpy.ops.preferences.addon_enable(module=module) + s = bpy.context.scene for o in s.cam_operations: if o.computing: From 8105a535276a7fe75646d6bd9937e9111d41382c Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 26 Apr 2024 13:05:47 +0000 Subject: [PATCH 091/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 02f70dba1..22a68133d 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version": (1, 0, 20), + "version":(1,0,21), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index e58cdfa4f..ddc5ee686 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__ = (1, 0, 20) +__version__=(1,0,21) \ No newline at end of file From ef47457fbcd0c87126eba016ea69850c3176aac0 Mon Sep 17 00:00:00 2001 From: palain Date: Wed, 22 May 2024 13:22:14 -0300 Subject: [PATCH 092/100] fixed silhouette and silhouette offset --- scripts/addons/cam/curvecamtools.py | 4 ++-- scripts/addons/cam/polygon_utils_cam.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 0ca3fc9d7..347021b52 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -752,7 +752,7 @@ def poll(cls, context): # this is almost same as getobjectoutline, just without the need of operation data def execute(self, context): - bpy.ops.object.curve_remove_doubles() + # bpy.ops.object.curve_remove_doubles() ob = context.active_object if self.opencurve and ob.type == 'CURVE': bpy.ops.object.duplicate() @@ -805,7 +805,7 @@ def execute(self, context): 'OBJECTS', objects=bpy.context.selected_objects) bpy.context.scene.cursor.location = (0, 0, 0) # smp=sgeometry.asMultiPolygon(self.silh) - for smp in self.silh: + for smp in self.silh.geoms: polygon_utils_cam.shapelyToCurve( ob.name + '_silhouette', smp, 0) # # bpy.ops.object.convert(target='CURVE') diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index 14ce3dcea..dc237ea1a 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -49,15 +49,15 @@ def shapelyRemoveDoubles(p, optimize_threshold): def shapelyToMultipolygon(anydata): - if anydata.type == 'MultiPolygon': + if anydata.geom_type == 'MultiPolygon': return anydata - elif anydata.type == 'Polygon': + elif anydata.geom_type == 'Polygon': if not anydata.is_empty: return shapely.geometry.MultiPolygon([anydata]) else: return sgeometry.MultiPolygon() else: - print(anydata.type, 'Shapely Conversion Aborted') + print(anydata.geom_type, 'Shapely Conversion Aborted') return sgeometry.MultiPolygon() From f5493479b9dc0ae9a8bfcf51a65631c9609a7adf Mon Sep 17 00:00:00 2001 From: palain Date: Wed, 22 May 2024 13:32:32 -0300 Subject: [PATCH 093/100] fixed silhouette and silhouette offset --- scripts/addons/cam/curvecamtools.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index 347021b52..fdd607dda 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -809,6 +809,7 @@ def execute(self, context): polygon_utils_cam.shapelyToCurve( ob.name + '_silhouette', smp, 0) # # bpy.ops.object.convert(target='CURVE') + simple.join_multiple(ob.name + '_silhouette') bpy.context.scene.cursor.location = ob.location bpy.ops.object.origin_set(type='ORIGIN_CURSOR') return {'FINISHED'} From 4021fe6d6b3036cfde8376f1a13ca6376ac71e3f Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 22 May 2024 16:37:14 +0000 Subject: [PATCH 094/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 22a68133d..58d4f6460 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,21), + "version":(1,0,22), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index ddc5ee686..04c6e3e84 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,21) \ No newline at end of file +__version__=(1,0,22) \ No newline at end of file From 7655cf0a31d491c903417c8d111c0e6755ebcb7c Mon Sep 17 00:00:00 2001 From: palain Date: Fri, 24 May 2024 08:51:14 -0300 Subject: [PATCH 095/100] fixed silhouette and silhouette offset --- scripts/addons/cam/curvecamtools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/addons/cam/curvecamtools.py b/scripts/addons/cam/curvecamtools.py index fdd607dda..7d9b10115 100644 --- a/scripts/addons/cam/curvecamtools.py +++ b/scripts/addons/cam/curvecamtools.py @@ -726,7 +726,7 @@ class CamOffsetSilhouete(Operator): mitrelimit: FloatProperty( name="Mitre Limit", default=.003, - min=0.0, + min=0.00000001, max=20, precision=4, unit="LENGTH", @@ -812,6 +812,7 @@ def execute(self, context): simple.join_multiple(ob.name + '_silhouette') bpy.context.scene.cursor.location = ob.location bpy.ops.object.origin_set(type='ORIGIN_CURSOR') + bpy.ops.object.curve_remove_doubles() return {'FINISHED'} # --------------------------------------------------- From 09c37860f2502f7f6e26ff3bb2015abbe010bb83 Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 24 May 2024 11:52:14 +0000 Subject: [PATCH 096/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 58d4f6460..102d9f7f2 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,22), + "version":(1,0,23), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 04c6e3e84..65e5986b6 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,22) \ No newline at end of file +__version__=(1,0,23) \ No newline at end of file From 4db7bbc9f546eb1166bbb6a1ebf982c2cca24c3e Mon Sep 17 00:00:00 2001 From: palain Date: Fri, 26 Jul 2024 13:11:51 -0300 Subject: [PATCH 097/100] fixed import errors for blender 4.2. but may cause problems. still all work on blender 4.1.1 --- scripts/addons/cam/image_utils.py | 6 +++++- scripts/addons/cam/opencamlib/oclSample.py | 6 +++++- scripts/addons/cam/polygon_utils_cam.py | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/addons/cam/image_utils.py b/scripts/addons/cam/image_utils.py index 5ed83458c..a7e2d010a 100644 --- a/scripts/addons/cam/image_utils.py +++ b/scripts/addons/cam/image_utils.py @@ -20,7 +20,11 @@ import numpy import bpy -import curve_simplify +try: + import curve_simplify +except ImportError: + pass + from mathutils import ( Euler, Vector, diff --git a/scripts/addons/cam/opencamlib/oclSample.py b/scripts/addons/cam/opencamlib/oclSample.py index 58c83722d..9bdcbafda 100644 --- a/scripts/addons/cam/opencamlib/oclSample.py +++ b/scripts/addons/cam/opencamlib/oclSample.py @@ -16,7 +16,11 @@ except ImportError: pass -from io_mesh_stl import blender_utils +try: + from io_mesh_stl import blender_utils +except ImportError: + pass + import mathutils from ..simple import activate diff --git a/scripts/addons/cam/polygon_utils_cam.py b/scripts/addons/cam/polygon_utils_cam.py index dc237ea1a..337fdee82 100644 --- a/scripts/addons/cam/polygon_utils_cam.py +++ b/scripts/addons/cam/polygon_utils_cam.py @@ -10,7 +10,11 @@ from shapely import geometry as sgeometry from mathutils import Euler, Vector -import curve_simplify +try: + import curve_simplify +except ImportError: + pass + SHAPELY = True From cdb0a7e42ccc73bb6c6759e7419078813fed219c Mon Sep 17 00:00:00 2001 From: Release robot Date: Fri, 26 Jul 2024 16:16:14 +0000 Subject: [PATCH 098/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 102d9f7f2..7151206e8 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,23), + "version":(1,0,24), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 65e5986b6..235ad3156 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,23) \ No newline at end of file +__version__=(1,0,24) \ No newline at end of file From 1e11fa9b8c2e99e51e33a3af95eb5ffa8e0a4825 Mon Sep 17 00:00:00 2001 From: palain Date: Wed, 14 Aug 2024 10:01:31 -0300 Subject: [PATCH 099/100] fixed import errors for blender 4.2. but may cause problems. still all work on blender 4.1.1 --- scripts/addons/cam/__init__.py | 12 ++++++------ scripts/addons/cam/engine.py | 8 ++++---- scripts/addons/cam/utils.py | 5 +++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 7151206e8..25b87762d 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -72,7 +72,7 @@ CamObjectSilhouete, ) from .engine import ( - BLENDERCAM_ENGINE, + CNCCAM_ENGINE, get_panels, ) from .machine_settings import machineSettings @@ -234,7 +234,7 @@ CamObjectSilhouete, # .engine - BLENDERCAM_ENGINE, + CNCCAM_ENGINE, # .machine_settings machineSettings, @@ -382,7 +382,7 @@ def register() -> None: ) for panel in get_panels(): - panel.COMPAT_ENGINES.add("BLENDERCAM_RENDER") + panel.COMPAT_ENGINES.add("CNCCAM_RENDER") wm = bpy.context.window_manager addon_kc = wm.keyconfigs.addon @@ -418,12 +418,12 @@ def unregister() -> None: del scene.cam_slice for panel in get_panels(): - if 'BLENDERCAM_RENDER' in panel.COMPAT_ENGINES: - panel.COMPAT_ENGINES.remove('BLENDERCAM_RENDER') + if 'CNCCAM_RENDER' in panel.COMPAT_ENGINES: + panel.COMPAT_ENGINES.remove('CNCCAM_RENDER') wm = bpy.context.window_manager active_kc = wm.keyconfigs.active for key in active_kc.keymaps['Object Mode'].keymap_items: if (key.idname == 'wm.call_menu' and key.properties.name == 'VIEW3D_MT_PIE_CAM'): - active_kc.keymaps['Object Mode'].keymap_items.remove(key) + active_kc.keymaps['Object Mode'].keymap_items.remove(key) \ No newline at end of file diff --git a/scripts/addons/cam/engine.py b/scripts/addons/cam/engine.py index 1876c0120..ec92eba11 100644 --- a/scripts/addons/cam/engine.py +++ b/scripts/addons/cam/engine.py @@ -1,4 +1,4 @@ -"""BlenderCAM 'engine.py' +"""CNCCAM 'engine.py' Engine definition, options and panels. """ @@ -28,9 +28,9 @@ from .ui_panels.slice import CAM_SLICE_Panel -class BLENDERCAM_ENGINE(RenderEngine): - bl_idname = "BLENDERCAM_RENDER" - bl_label = "BlenderCAM" +class CNCCAM_ENGINE(RenderEngine): + bl_idname = "CNCCAM_RENDER" + bl_label = "CNC CAM" bl_use_eevee_viewport = True diff --git a/scripts/addons/cam/utils.py b/scripts/addons/cam/utils.py index 8b2e97981..1cdd017b7 100644 --- a/scripts/addons/cam/utils.py +++ b/scripts/addons/cam/utils.py @@ -1849,8 +1849,9 @@ def updateOperation(self, context): def isValid(o, context): valid = True if o.geometry_source == 'OBJECT': - if o.object_name not in bpy.data.objects: - valid = False + if not o.object_name.endswith('_cut_bridges'): # let empty bridge cut be valid + if o.object_name not in bpy.data.objects: + valid = False if o.geometry_source == 'COLLECTION': if o.collection_name not in bpy.data.collections: valid = False From 9744c434b19812232ad23200db1313f3207c5720 Mon Sep 17 00:00:00 2001 From: Release robot Date: Wed, 14 Aug 2024 13:04:58 +0000 Subject: [PATCH 100/100] Version number --- scripts/addons/cam/__init__.py | 2 +- scripts/addons/cam/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/addons/cam/__init__.py b/scripts/addons/cam/__init__.py index 25b87762d..7c650b690 100644 --- a/scripts/addons/cam/__init__.py +++ b/scripts/addons/cam/__init__.py @@ -183,7 +183,7 @@ bl_info = { "name": "BlenderCAM - G-code Generation Tools", "author": "Vilem Novak & Contributors", - "version":(1,0,24), + "version":(1,0,25), "blender": (3, 6, 0), "location": "Properties > render", "description": "Generate Machining Paths for CNC", diff --git a/scripts/addons/cam/version.py b/scripts/addons/cam/version.py index 235ad3156..a7b14f72f 100644 --- a/scripts/addons/cam/version.py +++ b/scripts/addons/cam/version.py @@ -1 +1 @@ -__version__=(1,0,24) \ No newline at end of file +__version__=(1,0,25) \ No newline at end of file