"""Fabex 'simple_utils.py' © 2012 Vilem Novak
Various helper functions, less complex than those found in the 'utils' files.
"""
from math import (
hypot,
pi,
)
import os
import string
import sys
import time
from shapely.geometry import Polygon
import bpy
from mathutils import Vector
from ..constants import BULLET_SCALE
[docs]
def tuple_add(t, t1): # add two tuples as Vectors
"""Add two tuples as vectors.
This function takes two tuples, each representing a vector in three-
dimensional space, and returns a new tuple that is the element-wise sum
of the two input tuples. It assumes that both tuples contain exactly
three numeric elements.
Args:
t (tuple): A tuple containing three numeric values representing the first vector.
t1 (tuple): A tuple containing three numeric values representing the second vector.
Returns:
tuple: A tuple containing three numeric values that represent the sum of the
input vectors.
"""
return t[0] + t1[0], t[1] + t1[1], t[2] + t1[2]
[docs]
def tuple_subtract(t, t1): # sub two tuples as Vectors
"""Subtract two tuples element-wise.
This function takes two tuples of three elements each and performs an
element-wise subtraction, treating the tuples as vectors. The result is
a new tuple containing the differences of the corresponding elements
from the input tuples.
Args:
t (tuple): A tuple containing three numeric values.
t1 (tuple): A tuple containing three numeric values.
Returns:
tuple: A tuple containing the results of the element-wise subtraction.
"""
return t[0] - t1[0], t[1] - t1[1], t[2] - t1[2]
[docs]
def tuple_multiply(t, c): # multiply two tuples with a number
"""Multiply each element of a tuple by a given number.
This function takes a tuple containing three elements and a numeric
value, then multiplies each element of the tuple by the provided number.
The result is returned as a new tuple containing the multiplied values.
Args:
t (tuple): A tuple containing three numeric values.
c (numeric): A number by which to multiply each element of the tuple.
Returns:
tuple: A new tuple containing the results of the multiplication.
"""
return t[0] * c, t[1] * c, t[2] * c
[docs]
def tuple_length(t): # get length of vector, but passed in as tuple.
"""Get the length of a vector represented as a tuple.
This function takes a tuple as input, which represents the coordinates
of a vector, and returns its length by creating a Vector object from the
tuple. The length is calculated using the appropriate mathematical
formula for vector length.
Args:
t (tuple): A tuple representing the coordinates of the vector.
Returns:
float: The length of the vector.
"""
return Vector(t).length
# timing functions for optimisation purposes...
[docs]
def timing_init():
"""Initialize timing metrics.
This function sets up the initial state for timing functions by
returning a list containing two zero values. These values can be used to
track elapsed time or other timing-related metrics in subsequent
operations.
Returns:
list: A list containing two zero values, representing the
initial timing metrics.
"""
return [0, 0]
[docs]
def timing_start(tinf):
"""Start timing by recording the current time.
This function updates the second element of the provided list with the
current time in seconds since the epoch. It is useful for tracking the
start time of an operation or process.
Args:
tinf (list): A list where the second element will be updated
with the current time.
"""
t = time.time()
tinf[1] = t
[docs]
def timing_add(tinf):
"""Update the timing information.
This function updates the first element of the `tinf` list by adding the
difference between the current time and the second element of the list.
It is typically used to track elapsed time in a timing context.
Args:
tinf (list): A list where the first element is updated with the
"""
t = time.time()
tinf[0] += t - tinf[1]
[docs]
def timing_print(tinf):
"""Print the timing information.
This function takes a tuple containing timing information and prints it
in a formatted string. It specifically extracts the first element of the
tuple, which is expected to represent time, and appends the string
'seconds' to it before printing.
Args:
tinf (tuple): A tuple where the first element is expected to be a numeric value
representing time.
Returns:
None: This function does not return any value; it only prints output to the
console.
"""
print("time " + str(tinf[0]) + "seconds")
[docs]
def progress(text, n=None):
"""Report progress during script execution.
This function outputs a progress message to the standard output. It is
designed to work for background operations and provides a formatted
string that includes the specified text and an optional numeric progress
value. If the numeric value is provided, it is formatted as a
percentage.
Args:
text (str): The message to display as progress.
n (float?): A float representing the progress as a
fraction (0.0 to 1.0). If not provided, no percentage will
be displayed.
Returns:
None: This function does not return a value; it only prints
to the standard output.
"""
text = str(text)
if n is None:
n = ""
else:
n = str(int(n * 1000) / 1000) + "%"
sys.stdout.write(f"Progress: {text}{n}\n")
sys.stdout.flush()
[docs]
def activate(o):
"""Makes an object active in Blender.
This function sets the specified object as the active object in the
current Blender scene. It first deselects all objects, then selects the
given object and makes it the active object in the view layer. This is
useful for operations that require a specific object to be active, such
as transformations or modifications.
Args:
o (bpy.types.Object): The Blender object to be activated.
"""
s = bpy.context.scene
bpy.ops.object.select_all(action="DESELECT")
o.select_set(state=True)
s.objects[o.name].select_set(state=True)
bpy.context.view_layer.objects.active = o
[docs]
def distance_2d(v1, v2):
"""Calculate the distance between two points in 2D space.
This function computes the Euclidean distance between two points
represented by their coordinates in a 2D plane. It uses the Pythagorean
theorem to calculate the distance based on the differences in the x and
y coordinates of the points.
Args:
v1 (tuple): A tuple representing the coordinates of the first point (x1, y1).
v2 (tuple): A tuple representing the coordinates of the second point (x2, y2).
Returns:
float: The Euclidean distance between the two points.
"""
return hypot((v1[0] - v2[0]), (v1[1] - v2[1]))
[docs]
def delete_object(ob):
"""Delete an object in Blender for multiple uses.
This function activates the specified object and then deletes it using
Blender's built-in operations. It is designed to facilitate the deletion
of objects within the Blender environment, ensuring that the object is
active before performing the deletion operation.
Args:
ob (Object): The Blender object to be deleted.
"""
activate(ob)
bpy.ops.object.delete(use_global=False)
[docs]
def duplicate_object(o, pos):
"""Helper function for visualizing cutter positions in bullet simulation.
This function duplicates the specified object and resizes it according
to a predefined scale factor. It also removes any existing rigidbody
properties from the duplicated object and sets its location to the
specified position. This is useful for managing multiple cutter
positions in a bullet simulation environment.
Args:
o (Object): The object to be duplicated.
pos (Vector): The new position to place the duplicated object.
"""
activate(o)
bpy.ops.object.duplicate()
s = 1.0 / BULLET_SCALE
bpy.ops.transform.resize(
value=(s, s, s),
constraint_axis=(False, False, False),
orient_type="GLOBAL",
mirror=False,
use_proportional_edit=False,
proportional_edit_falloff="SMOOTH",
proportional_size=1,
)
o = bpy.context.active_object
bpy.ops.rigidbody.object_remove()
o.location = pos
[docs]
def add_to_group(ob, groupname):
"""Add an object to a specified group in Blender.
This function activates the given object and checks if the specified
group exists in Blender's data. If the group does not exist, it creates
a new group with the provided name. If the group already exists, it
links the object to that group.
Args:
ob (Object): The object to be added to the group.
groupname (str): The name of the group to which the object will be added.
"""
activate(ob)
if bpy.data.groups.get(groupname) is None:
bpy.ops.group.create(name=groupname)
else:
bpy.ops.object.group_link(group=groupname)
[docs]
def compare(v1, v2, vmiddle, e):
"""Comparison for optimization of paths.
This function compares two vectors and checks if the distance between a
calculated vector and a reference vector is less than a specified
threshold. It normalizes the vector difference and scales it by the
length of another vector to determine if the resulting vector is within
the specified epsilon value.
Args:
v1 (Vector): The first vector for comparison.
v2 (Vector): The second vector for comparison.
vmiddle (Vector): The middle vector used for calculating the
reference vector.
e (float): The threshold value for comparison.
Returns:
bool: True if the distance is less than the threshold,
otherwise False.
"""
# e=0.0001
v1 = Vector(v1)
v2 = Vector(v2)
vmiddle = Vector(vmiddle)
vect1 = v2 - v1
vect2 = vmiddle - v1
vect1.normalize()
vect1 *= vect2.length
v = vect2 - vect1
if v.length < e:
return True
return False
[docs]
def is_vertical_limit(v1, v2, limit):
"""Test Path Segment on Verticality Threshold for protect_vertical option.
This function evaluates the verticality of a path segment defined by two
points, v1 and v2, based on a specified limit. It calculates the angle
between the vertical vector and the vector formed by the two points. If
the angle is within the defined limit, it adjusts the vertical position
of either v1 or v2 to ensure that the segment adheres to the verticality
threshold.
Args:
v1 (tuple): A 3D point represented as a tuple (x, y, z).
v2 (tuple): A 3D point represented as a tuple (x, y, z).
limit (float): The angle threshold for determining verticality.
Returns:
tuple: The adjusted 3D points v1 and v2 after evaluating the verticality.
"""
z = abs(v1[2] - v2[2])
# verticality=0.05
# this will be better.
#
# print(a)
if z > 0:
v2d = Vector((0, 0, -1))
v3d = Vector((v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]))
a = v3d.angle(v2d)
if a > pi / 2:
a = abs(a - pi)
# print(a)
if a < limit:
# print(abs(v1[0]-v2[0])/z)
# print(abs(v1[1]-v2[1])/z)
if v1[2] > v2[2]:
v1 = (v2[0], v2[1], v1[2])
return v1, v2
else:
v2 = (v1[0], v1[1], v2[2])
return v1, v2
return v1, v2
[docs]
def get_cache_path(o):
"""Get the cache path for a given object.
This function constructs a cache path based on the current Blender
file's filepath and the name of the provided object. It retrieves the
base name of the file, removes the last six characters, and appends a
specified directory and the object's name to create a complete cache
path.
Args:
o (Object): The Blender object for which the cache path is being generated.
Returns:
str: The constructed cache path as a string.
"""
fn = bpy.data.filepath
l = len(bpy.path.basename(fn))
bn = bpy.path.basename(fn)[:-6]
print("Folder:", fn[:-l])
print("File:", bn)
iname = fn[:-l] + "temp_cam" + os.sep + bn + "_" + o.name
return iname
[docs]
def get_simulation_path():
"""Get the simulation path for temporary camera files.
This function retrieves the file path of the current Blender project and
constructs a new path for temporary camera files by appending 'temp_cam'
to the directory of the current file. The constructed path is returned
as a string.
Returns:
str: The path to the temporary camera directory.
"""
fn = bpy.data.filepath
l = len(bpy.path.basename(fn))
iname = fn[:-l] + "temp_cam" + os.sep
return iname
[docs]
def safe_filename(name): # for export gcode
"""Generate a safe file name from the given string.
This function takes a string input and removes any characters that are
not considered valid for file names. The valid characters include
letters, digits, and a few special characters. The resulting string can
be used safely as a file name for exporting purposes.
Args:
name (str): The input string to be sanitized into a safe file name.
Returns:
str: A sanitized version of the input string that contains only valid
characters for a file name.
"""
valid_chars = "-_.()%s%s" % (string.ascii_letters, string.digits)
filename = "".join(c for c in name if c in valid_chars)
return filename
[docs]
def unit_value_to_string(x, precision=5):
"""Convert a value to a string representation in the current unit system.
This function takes a numeric value and converts it to a string
formatted according to the unit system set in the Blender context. If
the unit system is metric, the value is converted to millimeters. If the
unit system is imperial, the value is converted to inches. The precision
of the output can be specified.
Args:
x (float): The numeric value to be converted.
precision (int?): The number of decimal places to round to.
Defaults to 5.
Returns:
str: The string representation of the value in the appropriate units.
"""
if bpy.context.scene.unit_settings.system == "METRIC":
return str(round(x * 1000, precision)) + " mm "
elif bpy.context.scene.unit_settings.system == "IMPERIAL":
return str(round(x * 1000 / 25.4, precision)) + "'' "
else:
return str(x)
# select multiple object starting with name
[docs]
def select_multiple(name):
"""Select multiple objects in the scene based on their names.
This function deselects all objects in the current Blender scene and
then selects all objects whose names start with the specified prefix. It
iterates through all objects in the scene and checks if their names
begin with the given string. If they do, those objects are selected;
otherwise, they are deselected.
Args:
name (str): The prefix used to select objects in the scene.
"""
scene = bpy.context.scene
bpy.ops.object.select_all(action="DESELECT")
for ob in scene.objects: # join pocket curve calculations
if ob.name.startswith(name):
ob.select_set(True)
else:
ob.select_set(False)
# join multiple objects starting with 'name' renaming final object as 'name'
[docs]
def join_multiple(name):
"""Join multiple objects and rename the final object.
This function selects multiple objects in the Blender context, joins
them into a single object, and renames the resulting object to the
specified name. It is assumed that the objects to be joined are already
selected in the Blender interface.
Args:
name (str): The new name for the joined object.
"""
select_multiple(name)
bpy.ops.object.join()
bpy.context.active_object.name = name # rename object
# remove multiple objects starting with 'name'.... useful for fixed name operation
[docs]
def remove_multiple(name):
"""Remove multiple objects from the scene based on their name prefix.
This function deselects all objects in the current Blender scene and
then iterates through all objects. If an object's name starts with the
specified prefix, it selects that object and deletes it from the scene.
This is useful for operations that require removing multiple objects
with a common naming convention.
Args:
name (str): The prefix of the object names to be removed.
"""
scene = bpy.context.scene
bpy.ops.object.select_all(action="DESELECT")
for ob in scene.objects:
if ob.name.startswith(name):
ob.select_set(True)
bpy.ops.object.delete()
[docs]
def deselect():
"""Deselect all objects in the current Blender context.
This function utilizes the Blender Python API to deselect all objects in
the current scene. It is useful for clearing selections before
performing other operations on objects. Raises: None
"""
bpy.ops.object.select_all(action="DESELECT")
# makes the object with the name active
[docs]
def make_active(name):
"""Make an object active in the Blender scene.
This function takes the name of an object and sets it as the active
object in the current Blender scene. It first deselects all objects,
then selects the specified object and makes it active, allowing for
further operations to be performed on it.
Args:
name (str): The name of the object to be made active.
"""
ob = bpy.context.scene.objects[name]
bpy.ops.object.select_all(action="DESELECT")
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
# change the name of the active object
[docs]
def active_name(name):
"""Change the name of the active object in Blender.
This function sets the name of the currently active object in the
Blender context to the specified name. It directly modifies the `name`
attribute of the active object, allowing users to rename objects
programmatically.
Args:
name (str): The new name to assign to the active object.
"""
bpy.context.active_object.name = name
# renames and makes active name and makes it active
[docs]
def rename(name, name2):
"""Rename an object and make it active.
This function renames an object in the Blender context and sets it as
the active object. It first calls the `make_active` function to ensure
the object is active, then updates the name of the active object to the
new name provided.
Args:
name (str): The current name of the object to be renamed.
name2 (str): The new name to assign to the active object.
"""
make_active(name)
bpy.context.active_object.name = name2
# boolean union of objects starting with name result is object name.
# all objects starting with name will be deleted and the result will be name
[docs]
def union(name):
"""Perform a boolean union operation on objects.
This function selects multiple objects that start with the given name,
performs a boolean union operation on them using Blender's operators,
and then renames the resulting object to the specified name. After the
operation, it removes the original objects that were used in the union
process.
Args:
name (str): The base name of the objects to be unioned.
"""
select_multiple(name)
bpy.ops.object.curve_boolean(boolean_type="UNION")
active_name("unionboolean")
remove_multiple(name)
rename("unionboolean", name)
[docs]
def intersect(name):
"""Perform an intersection operation on a curve object.
This function selects multiple objects based on the provided name and
then executes a boolean operation to create an intersection of the
selected objects. The resulting intersection is then named accordingly.
Args:
name (str): The name of the object(s) to be selected for the intersection.
"""
select_multiple(name)
bpy.ops.object.curve_boolean(boolean_type="INTERSECT")
active_name("intersection")
# 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
[docs]
def difference(name, basename):
"""Perform a boolean difference operation on objects.
This function selects a series of objects specified by `name` and
performs a boolean difference operation with the object specified by
`basename`. After the operation, the resulting object is renamed to
'booleandifference'. The original objects specified by `name` are
deleted after the operation.
Args:
name (str): The name of the series of objects to select for the operation.
basename (str): The name of the base object to perform the boolean difference with.
"""
# name is the series to select
# basename is what the base you want to cut including name
select_multiple(name)
bpy.context.view_layer.objects.active = bpy.data.objects[basename]
bpy.ops.object.curve_boolean(boolean_type="DIFFERENCE")
active_name("booleandifference")
remove_multiple(name)
rename("booleandifference", basename)
# duplicate active object or duplicate move
# if x or y not the default, duplicate move will be executed
[docs]
def duplicate(x=0.0, y=0.0):
"""Duplicate an active object or move it based on the provided coordinates.
This function duplicates the currently active object in Blender. If both
x and y are set to their default values (0), the object is duplicated in
place. If either x or y is non-zero, the object is duplicated and moved
by the specified x and y offsets.
Args:
x (float): The x-coordinate offset for the duplication.
Defaults to 0.
y (float): The y-coordinate offset for the duplication.
Defaults to 0.
"""
if x == 0.0 and y == 0.0:
bpy.ops.object.duplicate()
else:
bpy.ops.object.duplicate_move(
OBJECT_OT_duplicate={"linked": False, "mode": "TRANSLATION"},
TRANSFORM_OT_translate={"value": (x, y, 0.0)},
)
# Mirror active object along the x axis
[docs]
def mirror_x():
"""Mirror the active object along the x-axis.
This function utilizes Blender's operator to mirror the currently active
object in the 3D view along the x-axis. It sets the orientation to
global and applies the transformation based on the specified orientation
matrix and constraint axis.
"""
bpy.ops.transform.mirror(
orient_type="GLOBAL",
orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
orient_matrix_type="GLOBAL",
constraint_axis=(True, False, False),
)
# mirror active object along y axis
[docs]
def mirror_y():
"""Mirror the active object along the Y axis.
This function uses Blender's operator to perform a mirror transformation
on the currently active object in the scene. The mirroring is done with
respect to the global coordinate system, specifically along the Y axis.
This can be useful for creating symmetrical objects or for correcting
the orientation of an object in a 3D environment. Raises: None
"""
bpy.ops.transform.mirror(
orient_type="GLOBAL",
orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)),
orient_matrix_type="GLOBAL",
constraint_axis=(False, True, False),
)
# move active object and apply translation
[docs]
def move(x=0.0, y=0.0):
"""Move the active object in the 3D space by applying a translation.
This function translates the active object in Blender's 3D view by the
specified x and y values. It uses Blender's built-in operations to
perform the translation and then applies the transformation to the
object's location.
Args:
x (float): The distance to move the object along the x-axis. Defaults to 0.0.
y (float): The distance to move the object along the y-axis. Defaults to 0.0.
"""
bpy.ops.transform.translate(value=(x, y, 0.0))
bpy.ops.object.transform_apply(location=True)
# Rotate active object and apply rotation
[docs]
def rotate(angle):
"""Rotate the active object by a specified angle.
This function modifies the rotation of the currently active object in
the Blender context by setting its Z-axis rotation to the given angle.
After updating the rotation, it applies the transformation to ensure
that the changes are saved to the object's data.
Args:
angle (float): The angle in radians to rotate the active object
around the Z-axis.
"""
bpy.context.object.rotation_euler[2] = angle
bpy.ops.object.transform_apply(rotation=True)
# remove doubles
[docs]
def remove_doubles():
"""Remove duplicate vertices from the selected curve object.
This function utilizes the Blender Python API to remove duplicate
vertices from the currently selected curve object in the Blender
environment. It is essential for cleaning up geometry and ensuring that
the curve behaves as expected without unnecessary complexity.
"""
bpy.ops.object.curve_remove_doubles()
# Add overcut to active object
[docs]
def add_overcut(diametre, overcut=True):
"""Add overcut to the active object.
This function adds an overcut to the currently active object in the
Blender context. If the `overcut` parameter is set to True, it performs
a series of operations including creating a curve overcut with the
specified diameter, deleting the original object, and renaming the new
object to match the original. The function also ensures that any
duplicate vertices are removed from the resulting object.
Args:
diametre (float): The diameter to be used for the overcut.
overcut (bool): A flag indicating whether to apply the overcut. Defaults to True.
"""
if overcut:
name = bpy.context.active_object.name
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()
rename(overcut_name, name)
remove_doubles()
# add bounding rectangtle to curve
[docs]
def add_bound_rectangle(xmin, ymin, xmax, ymax, name="bounds_rectangle"):
"""Add a bounding rectangle to a curve.
This function creates a rectangle defined by the minimum and maximum x
and y coordinates provided as arguments. The rectangle is added to the
scene at the center of the defined bounds. The resulting rectangle is
named according to the 'name' parameter.
Args:
xmin (float): The minimum x-coordinate of the rectangle.
ymin (float): The minimum y-coordinate of the rectangle.
xmax (float): The maximum x-coordinate of the rectangle.
ymax (float): The maximum y-coordinate of the rectangle.
name (str): The name of the resulting rectangle object. Defaults to
'bounds_rectangle'.
"""
xsize = xmax - xmin
ysize = ymax - ymin
bpy.ops.curve.simple(
align="WORLD",
location=(xmin + xsize / 2, ymin + ysize / 2, 0),
rotation=(0, 0, 0),
Simple_Type="Rectangle",
Simple_width=xsize,
Simple_length=ysize,
use_cyclic_u=True,
edit_mode=False,
shape="3D",
)
bpy.ops.object.transform_apply(location=True)
active_name(name)
[docs]
def add_rectangle(width, height, center_x=True, center_y=True):
"""Add a rectangle to the scene.
This function creates a rectangle in the 3D space using the specified
width and height. The rectangle can be centered at the origin or offset
based on the provided parameters. If `center_x` or `center_y` is set to
True, the rectangle will be positioned at the center of the specified
dimensions; otherwise, it will be positioned based on the offsets.
Args:
width (float): The width of the rectangle.
height (float): The height of the rectangle.
center_x (bool?): If True, centers the rectangle along the x-axis. Defaults to True.
center_y (bool?): If True, centers the rectangle along the y-axis. Defaults to True.
"""
x_offset = width / 2
y_offset = height / 2
if center_x:
x_offset = 0
if center_y:
y_offset = 0
bpy.ops.curve.simple(
align="WORLD",
location=(x_offset, y_offset, 0),
rotation=(0, 0, 0),
Simple_Type="Rectangle",
Simple_width=width,
Simple_length=height,
use_cyclic_u=True,
edit_mode=False,
shape="3D",
)
bpy.ops.object.transform_apply(location=True)
active_name("simple_rectangle")
# Returns coords from active object
[docs]
def active_to_coords():
"""Convert the active object to a list of its vertex coordinates.
This function duplicates the currently active object in the Blender
context, converts it to a mesh, and extracts the X and Y coordinates of
its vertices. After extracting the coordinates, it removes the temporary
mesh object created during the process. The resulting list contains
tuples of (x, y) coordinates for each vertex in the active object.
Returns:
list: A list of tuples, each containing the X and Y coordinates of the
vertices from the active object.
"""
bpy.ops.object.duplicate()
obj = bpy.context.active_object
bpy.ops.object.convert(target="MESH")
active_name("_tmp_mesh")
coords = []
for v in obj.data.vertices: # extract X,Y coordinates from the vertices data
coords.append((v.co.x, v.co.y))
remove_multiple("_tmp_mesh")
return coords
# returns shapely polygon from active object
[docs]
def active_to_shapely_poly():
"""Convert the active object to a Shapely polygon.
This function retrieves the coordinates of the currently active object
and converts them into a Shapely Polygon data structure. It is useful
for geometric operations and spatial analysis using the Shapely library.
Returns:
Polygon: A Shapely Polygon object created from the active object's coordinates.
"""
# convert coordinates to shapely Polygon datastructure
return Polygon(active_to_coords())
# checks for curve splines shorter than three points and subdivides if necessary
[docs]
def subdivide_short_lines(co):
"""Subdivide all polylines to have at least three points.
This function iterates through the splines of a curve, checks if they are not bezier
and if they have less or equal to two points. If so, each spline is subdivided to get
at least three points.
Args:
co (Object): A curve object to be analyzed and modified.
"""
bpy.ops.object.mode_set(mode="EDIT")
for sp in co.data.splines:
if len(sp.points) == 2 and sp.type != "BEZIER":
bpy.ops.curve.select_all(action="DESELECT")
for pt in sp.points:
pt.select = True
bpy.ops.curve.subdivide()
bpy.ops.object.editmode_toggle()
bpy.ops.object.select_all(action="SELECT")