Merge remote-tracking branch 'origin/master'

pull/218/head
palain 2022-05-10 11:43:27 -03:00
commit 9d50b82402
11 zmienionych plików z 269 dodań i 178 usunięć

Wyświetl plik

@ -685,7 +685,7 @@ class camOperation(bpy.types.PropertyGroup):
# 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="Step down", default=0.01, min=0.00001, max=32, precision=PRECISION,
stepdown: bpy.props.FloatProperty(name="", description="Layer height", default=0.01, min=0.00001, max=32, precision=PRECISION,
unit="LENGTH", update=updateRest)
first_down: bpy.props.BoolProperty(name="First down",
description="First go down on a contour, then go to the next one",
@ -1527,7 +1527,7 @@ def unregister():
for p in classes:
bpy.utils.unregister_class(p)
s = bpy.types.Scene
# cam chains are defined hardly now.
del s.cam_chains
del s.cam_active_chain

Wyświetl plik

@ -46,9 +46,8 @@ def getCutterBullet(o):
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=(0 , 0, o.cutter_diameter / 2),
align='WORLD', enter_editmode=False, location = CUTTER_OFFSET,
rotation=(0, 0, 0))
#bpy.ops.object.duplicate() # show bit
cutter = bpy.context.active_object
cutter.scale *= BULLET_SCALE
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
@ -62,8 +61,7 @@ def getCutterBullet(o):
# 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=(0, 0, o.cutter_diameter / 2), rotation=(0, 0, 0))
#bpy.ops.object.duplicate() # show bit
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)
@ -81,9 +79,8 @@ def getCutterBullet(o):
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 = (0, 0, cone_d / 2),
align='WORLD', enter_editmode=False, location = CUTTER_OFFSET,
rotation=(math.pi, 0, 0))
#bpy.ops.object.duplicate() # show bit
cutter = bpy.context.active_object
cutter.scale *= BULLET_SCALE
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
@ -100,8 +97,7 @@ def getCutterBullet(o):
radius2 = o.cylcone_diameter / 2,
depth = cylcone_d, end_fill_type='NGON',
align = 'WORLD', enter_editmode = False,
location = (0 , 0 , cylcone_d / 2),rotation = (math.pi, 0, 0))
#bpy.ops.object.duplicate() # show bit
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)
@ -109,6 +105,7 @@ def getCutterBullet(o):
bpy.ops.rigidbody.object_add(type='ACTIVE')
cutter = bpy.context.active_object
cutter.rigid_body.collision_shape = 'CONVEX_HULL'
cutter.location = CUTTER_OFFSET
elif type == 'BALLCONE':
angle = math.radians(o.cutter_tip_angle)/2
cutter_R = o.cutter_diameter/2
@ -137,8 +134,7 @@ def getCutterBullet(o):
ob_scr.merge_threshold = 0
ob_scr.use_merge_vertices = True
bpy.ops.object.modifier_apply(modifier='scr')
bpy.data.objects['BallConeTool'].select_set(True)
#bpy.ops.object.duplicate() # show bit
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)
@ -146,7 +142,7 @@ def getCutterBullet(o):
bpy.ops.rigidbody.object_add(type='ACTIVE')
cutter.location = CUTTER_OFFSET
cutter.rigid_body.collision_shape = 'CONVEX_HULL'
cutter.location = CUTTER_OFFSET
elif type == 'CUSTOM':
cutob = bpy.data.objects[o.cutter_object_name]
activate(cutob)

Wyświetl plik

@ -591,21 +591,26 @@ class CamOperationAdd(bpy.types.Operator):
s = bpy.context.scene
fixUnits()
if s.objects.get('CAM_machine') is None:
utils.addMachineAreaObject()
# if len(s.cam_material)==0:
# s.cam_material.add()
s.cam_operations.add()
o = s.cam_operations[-1]
s.cam_active_operation = len(s.cam_operations) - 1
o.name = 'Operation_' + str(s.cam_active_operation + 1)
o.filename = o.name
ob = bpy.context.active_object
if ob is not None:
o.object_name = ob.name
minx, miny, minz, maxx, maxy, maxz = utils.getBoundsWorldspace([ob])
o.minz = minz
else:
# FIXME Creating an operation without any object selected
# This should actually display a modal dialog
# and cancel the operation creation
o.object_name = "none"
o.minz = 0
s.cam_active_operation = len(s.cam_operations) - 1
o.name = f"Op_{o.object_name}_{s.cam_active_operation + 1}"
o.filename = o.name
if s.objects.get('CAM_machine') is None:
utils.addMachineAreaObject()
return {'FINISHED'}

Wyświetl plik

@ -42,6 +42,9 @@ from cam.ui_panels.material import CAM_MATERIAL_Panel
from cam.ui_panels.chains import CAM_UL_operations, CAM_UL_chains, CAM_CHAINS_Panel
from cam.ui_panels.op_properties import CAM_OPERATION_PROPERTIES_Panel
from cam.ui_panels.movement import CAM_MOVEMENT_Panel
from cam.ui_panels.feedrate import CAM_FEEDRATE_Panel
from cam.ui_panels.optimisation import CAM_OPTIMISATION_Panel
from cam.ui_panels.area import CAM_AREA_Panel
class CAM_UL_orientations(UIList):
@ -55,139 +58,6 @@ class CAM_UL_orientations(UIList):
class CAM_FEEDRATE_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM feedrate panel"""
bl_label = "CAM feedrate"
bl_idname = "WORLD_PT_CAM_FEEDRATE"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
layout = self.layout
scene = bpy.context.scene
if len(scene.cam_operations) == 0:
layout.label(text='Add operation first')
if len(scene.cam_operations) > 0:
ao = scene.cam_operations[scene.cam_active_operation]
if ao.valid:
layout.prop(ao, 'feedrate')
layout.prop(ao, 'do_simulation_feedrate')
layout.prop(ao, 'plunge_feedrate')
layout.prop(ao, 'plunge_angle')
layout.prop(ao, 'spindle_rpm')
class CAM_OPTIMISATION_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM optimisation panel"""
bl_label = "CAM optimisation"
bl_idname = "WORLD_PT_CAM_OPTIMISATION"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
layout = self.layout
scene = bpy.context.scene
if len(scene.cam_operations) == 0:
layout.label(text='Add operation first')
if len(scene.cam_operations) > 0:
ao = scene.cam_operations[scene.cam_active_operation]
if ao.valid:
layout.prop(ao, 'optimize')
if ao.optimize:
layout.prop(ao, 'optimize_threshold')
if ao.geometry_source == 'OBJECT' or ao.geometry_source == 'COLLECTION':
exclude_exact = ao.strategy in ['MEDIAL_AXIS', 'POCKET', 'WATERLINE', 'CUTOUT', 'DRILL', 'PENCIL',
'CURVE']
if not exclude_exact:
if not ao.use_exact:
layout.prop(ao, 'use_exact')
layout.label(text="Exact mode must be set for opencamlib to work ")
if "ocl" in sys.modules:
layout.label(text="Opencamlib is available ")
layout.prop(ao, 'use_opencamlib')
else:
layout.label(text="Opencamlib is NOT available ")
layout.prop(ao, 'exact_subdivide_edges')
if exclude_exact or not ao.use_exact:
layout.prop(ao, 'pixsize')
layout.prop(ao, 'imgres_limit')
sx = ao.max.x - ao.min.x
sy = ao.max.y - ao.min.y
resx = int(sx / ao.pixsize)
resy = int(sy / ao.pixsize)
l = 'resolution: ' + str(resx) + ' x ' + str(resy)
layout.label(text=l)
layout.prop(ao, 'simulation_detail')
layout.prop(ao, 'circle_detail')
class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM operation area panel"""
bl_label = "CAM operation area "
bl_idname = "WORLD_PT_CAM_OPERATION_AREA"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
layout = self.layout
scene = bpy.context.scene
if len(scene.cam_operations) == 0:
layout.label(text='Add operation first')
if len(scene.cam_operations) > 0:
ao = scene.cam_operations[scene.cam_active_operation]
if ao.valid:
layout.prop(ao, 'use_layers')
if ao.use_layers:
layout.prop(ao, 'stepdown')
layout.prop(ao, 'ambient_behaviour')
if ao.ambient_behaviour == 'AROUND':
layout.prop(ao, 'ambient_radius')
layout.prop(ao, 'maxz') # experimental
if ao.maxz > ao.free_movement_height:
layout.prop(ao, 'free_movement_height')
layout.label(text='Depth start > Free movement')
layout.label(text='POSSIBLE COLLISION')
if ao.geometry_source in ['OBJECT', 'COLLECTION']:
if ao.strategy == 'CURVE':
layout.label(text="cannot use depth from object using CURVES")
if not ao.minz_from_ob:
if not ao.minz_from_material:
layout.prop(ao, 'minz')
layout.prop(ao, 'minz_from_material')
if not ao.minz_from_material:
layout.prop(ao, 'minz_from_ob')
else:
layout.prop(ao, 'source_image_scale_z')
layout.prop(ao, 'source_image_size_x')
if ao.source_image_name != '':
i = bpy.data.images[ao.source_image_name]
if i is not None:
sy = int((ao.source_image_size_x / i.size[0]) * i.size[1] * 1000000) / 1000
layout.label(text='image size on y axis: ' + strInUnits(sy, 8))
layout.separator()
layout.prop(ao, 'source_image_offset')
col = layout.column(align=True)
col.prop(ao, 'source_image_crop', text='Crop source image')
if ao.source_image_crop:
col.prop(ao, 'source_image_crop_start_x', text='start x')
col.prop(ao, 'source_image_crop_start_y', text='start y')
col.prop(ao, 'source_image_crop_end_x', text='end x')
col.prop(ao, 'source_image_crop_end_y', text='end y')
layout.prop(ao, 'use_limit_curve')
if ao.use_limit_curve:
layout.prop_search(ao, "limit_curve", bpy.data, "objects")
layout.prop(ao, "ambient_cutter_restrict")
class CAM_GCODE_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM operation g-code options panel"""
bl_label = "CAM g-code options "

Wyświetl plik

@ -0,0 +1,78 @@
import bpy
from cam.ui_panels.buttons_panel import CAMButtonsPanel
class CAM_AREA_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM operation area panel"""
bl_label = "CAM operation area "
bl_idname = "WORLD_PT_CAM_OPERATION_AREA"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
self.scene = bpy.context.scene
if not self.has_operations():
self.layout.label(text='Add operation first')
return
self.ao = self.active_operation()
if not self.ao.valid:
return
self.draw_z_limits()
self.draw_xy_limits()
# Draw layers option: use layers(y/n) and choose the stepdown
def draw_z_limits(self):
row = self.layout.row(align=True)
row.prop(self.ao, 'use_layers')
if self.ao.use_layers:
row.prop(self.ao, 'stepdown')
self.layout.prop(self.ao, 'maxz')
if self.ao.maxz > self.ao.free_movement_height:
self.layout.prop(self.ao, 'free_movement_height')
self.layout.label(text='Depth start > Free movement')
self.layout.label(text='POSSIBLE COLLISION')
if self.ao.geometry_source in ['OBJECT', 'COLLECTION']:
if self.ao.strategy == 'CURVE':
self.layout.label(text="cannot use depth from object using CURVES")
if not self.ao.minz_from_ob:
if not self.ao.minz_from_material:
self.layout.prop(self.ao, 'minz')
self.layout.prop(self.ao, 'minz_from_material')
if not self.ao.minz_from_material:
self.layout.prop(self.ao, 'minz_from_ob')
else:
self.layout.prop(self.ao, 'source_image_scale_z')
self.layout.prop(self.ao, 'source_image_size_x')
if self.ao.source_image_name != '':
i = bpy.data.images[self.ao.source_image_name]
if i is not None:
sy = int((self.ao.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.ao, 'source_image_offset')
col = self.layout.column(align=True)
col.prop(self.ao, 'source_image_crop', text='Crop source image')
if self.ao.source_image_crop:
col.prop(self.ao, 'source_image_crop_start_x', text='start x')
col.prop(self.ao, 'source_image_crop_start_y', text='start y')
col.prop(self.ao, 'source_image_crop_end_x', text='end x')
col.prop(self.ao, 'source_image_crop_end_y', text='end y')
def draw_xy_limits(self):
self.layout.prop(self.ao, 'ambient_behaviour')
if self.ao.ambient_behaviour == 'AROUND':
self.layout.prop(self.ao, 'ambient_radius')
self.layout.prop(self.ao, 'use_limit_curve')
if self.ao.use_limit_curve:
self.layout.prop_search(self.ao, "limit_curve", bpy.data, "objects")
self.layout.prop(self.ao, "ambient_cutter_restrict")

Wyświetl plik

@ -1,4 +1,5 @@
import bpy
import sys
# Panel definitions
class CAMButtonsPanel:
@ -16,6 +17,7 @@ class CAMButtonsPanel:
def __init__(self):
self.scene = bpy.context.scene
def active_operation_index(self):
return(self.scene.cam_active_operation)
@ -33,3 +35,10 @@ class CAMButtonsPanel:
def has_operations(self):
return (self.operations_count() > 0)
def opencamlib_version(self):
try:
import ocl
return(ocl.version())
except ImportError as e:
return

Wyświetl plik

@ -0,0 +1,23 @@
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"
bl_idname = "WORLD_PT_CAM_FEEDRATE"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
if not self.has_operations():
self.layout.label(text='Add operation first')
return
ao = self.active_operation()
if not ao.valid: return
self.layout.prop(ao, 'feedrate')
self.layout.prop(ao, 'do_simulation_feedrate')
self.layout.prop(ao, 'plunge_feedrate')
self.layout.prop(ao, 'plunge_angle')
self.layout.prop(ao, 'spindle_rpm')

Wyświetl plik

@ -1,6 +1,4 @@
import sys
import ocl
import bpy
from cam.simple import strInUnits
@ -27,10 +25,11 @@ class CAM_INFO_Panel(CAMButtonsPanel, bpy.types.Panel):
self.layout.label(text='No CAM operation created')
def draw_opencamlib_version(self):
if "ocl" in sys.modules:
self.layout.label(text = f"Opencamlib v{ocl.version()} installed")
else:
opencamlib_version = self.opencamlib_version()
if opencamlib_version is None:
self.layout.label(text = "Opencamlib is not installed")
else:
self.layout.label(text = f"Opencamlib v{opencamlib_version} installed")
def draw_active_op_warnings(self):
active_op = self.active_operation()

Wyświetl plik

@ -2,6 +2,14 @@
import bpy
from cam.ui_panels.buttons_panel import CAMButtonsPanel
# Operations panel
# This panel displays the list of operations created by the user
# Functionnalities are:
# - list Operations
# - create/delete/duplicate/reorder operations
# - display preset operations
#
# For each operation, generate the corresponding gcode and export the gcode file
class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM operations panel"""
@ -10,10 +18,38 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel):
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
# Main draw function
def draw(self, context):
layout = self.layout
self.context = context
self.draw_operations_list()
row = layout.row()
# FIXME: is this ever used ?
use_experimental = bpy.context.preferences.addons['cam'].preferences.experimental
if (not self.has_operations()): return
ao = self.active_operation()
if ao is None: return
self.draw_presets()
self.draw_output_buttons()
layout = self.layout
sub = layout.column()
sub.active = not ao.computing
# Draw operation name and filename
sub.prop(ao, 'name')
sub.prop(ao, 'filename')
self.draw_operation_source()
self.draw_operation_options()
# Draw the list of operations and the associated buttons:
# create, delete, duplicate, reorder
def draw_operations_list(self):
row = self.layout.row()
row.template_list("CAM_UL_operations", '', self.scene, "cam_operations", self.scene, 'cam_active_operation')
col = row.column(align=True)
col.operator("scene.cam_operation_add", icon='ADD', text="")
@ -23,18 +59,27 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel):
col.operator("scene.cam_operation_move", icon='TRIA_UP', text="").direction = 'UP'
col.operator("scene.cam_operation_move", icon='TRIA_DOWN', text="").direction = 'DOWN'
use_experimental = bpy.context.preferences.addons['cam'].preferences.experimental
if (not self.has_operations()): return
ao = self.active_operation()
if ao is None: return
row = layout.row(align=True)
# Draw the list of preset operations, and preset add and remove buttons
def draw_presets(self):
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
if not ao.computing:
# Draw buttons "Calculate path & export Gcode", "Export Gcode ", and "Simulate this operation"
def draw_output_buttons(self):
layout = self.layout
ao = self.active_operation()
# FIXME This does not seem to work - there is never a "Computing" label displayed
# while an operation is being calculated
if ao.computing:
row = layout.row(align=True)
row.label(text='computing')
row.operator('object.kill_calculate_cam_paths_background', text="", icon='CANCEL')
else:
if ao.valid:
layout.operator("object.calculate_cam_path", text="Calculate path & export Gcode")
if ao.name is not None:
@ -44,16 +89,14 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel):
layout.operator("object.cam_simulate", text="Simulate this operation")
else:
layout.label(text="operation invalid, can't compute")
else:
row = layout.row(align=True)
row.label(text='computing')
row.operator('object.kill_calculate_cam_paths_background', text="", icon='CANCEL')
sub = layout.column()
sub.active = not ao.computing
sub.prop(ao, 'name')
sub.prop(ao, '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):
layout = self.layout
ao = self.active_operation()
layout.prop(ao, 'geometry_source')
@ -75,18 +118,29 @@ class CAM_OPERATIONS_Panel(CAMButtonsPanel, bpy.types.Panel):
else:
layout.prop_search(ao, "source_image_name", bpy.data, "images")
if ao.strategy in ['CARVE', 'PROJECTED_CURVE']:
layout.prop_search(ao, "curve_object", bpy.data, "objects")
if ao.strategy == 'PROJECTED_CURVE':
layout.prop_search(ao, "curve_object1", bpy.data, "objects")
# Draw Operation options:
# Remove redundant points (optimizes operation)
# Use modifiers of the object
# Hide all other paths
# Parent path to object (?)
def draw_operation_options(self):
layout = self.layout
ao = self.active_operation()
# TODO This should be in some optimization menu
layout.prop(ao, 'remove_redundant_points')
if ao.remove_redundant_points:
layout.label(text='Revise your Code before running!')
layout.label(text='Quality will suffer if tolerance')
layout.label(text='is high')
layout.prop(ao, 'simplify_tol')
if ao.geometry_source in ['OBJECT', 'COLLECTION']:
layout.prop(ao, 'use_modifiers')
layout.prop(ao, 'hide_all_others')

Wyświetl plik

@ -0,0 +1,51 @@
import bpy
from cam.ui_panels.buttons_panel import CAMButtonsPanel
class CAM_OPTIMISATION_Panel(CAMButtonsPanel, bpy.types.Panel):
"""CAM optimisation panel"""
bl_label = "CAM optimisation"
bl_idname = "WORLD_PT_CAM_OPTIMISATION"
COMPAT_ENGINES = {'BLENDERCAM_RENDER'}
def draw(self, context):
layout = self.layout
scene = bpy.context.scene
if len(scene.cam_operations) == 0:
layout.label(text='Add operation first')
if len(scene.cam_operations) > 0:
ao = scene.cam_operations[scene.cam_active_operation]
if ao.valid:
layout.prop(ao, 'optimize')
if ao.optimize:
layout.prop(ao, 'optimize_threshold')
if ao.geometry_source == 'OBJECT' or ao.geometry_source == 'COLLECTION':
exclude_exact = ao.strategy in ['MEDIAL_AXIS', 'POCKET', 'WATERLINE', 'CUTOUT', 'DRILL', 'PENCIL',
'CURVE']
if not exclude_exact:
if not ao.use_exact:
layout.prop(ao, 'use_exact')
layout.label(text="Exact mode must be set for opencamlib to work ")
opencamlib_version = self.opencamlib_version()
if opencamlib_version is None:
layout.label(text="Opencamlib is NOT available ")
layout.prop(ao, 'exact_subdivide_edges')
else:
layout.label(text=f"Opencamlib v{opencamlib_version} installed")
layout.prop(ao, 'use_opencamlib')
if exclude_exact or not ao.use_exact:
layout.prop(ao, 'pixsize')
layout.prop(ao, 'imgres_limit')
sx = ao.max.x - ao.min.x
sy = ao.max.y - ao.min.y
resx = int(sx / ao.pixsize)
resy = int(sy / ao.pixsize)
l = 'resolution: ' + str(resx) + ' x ' + str(resy)
layout.label(text=l)
layout.prop(ao, 'simulation_detail')
layout.prop(ao, 'circle_detail')

Wyświetl plik

@ -1316,7 +1316,13 @@ def getObjectOutline(radius, o, Offset): # FIXME: make this one operation indep
join = 2
else:
join = 1
for p1 in polygons.geoms: # sort by size before this???
if isinstance(polygons, list):
polygon_list = polygons
else:
polygon_list = polygons.geoms
for p1 in polygon_list: # sort by size before this???
# print(p1.type, len(polygons))
i += 1
if radius > 0: