Skip to content

Commit

Permalink
0.1.2
Browse files Browse the repository at this point in the history
Added the voronoi_mesh operation from toxicblend
  • Loading branch information
eadf committed Nov 6, 2023
1 parent dc53637 commit 038cd82
Show file tree
Hide file tree
Showing 15 changed files with 1,289 additions and 28 deletions.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ boostvoronoi = { version = "0.11.0" }
rayon = "1.8.0"
itertools = "0.11.0"
vob = "3.0.3"
earcutr = "0.4.3"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
![license](https://img.shields.io/crates/l/hallr)
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/eadf)

# Hallr
![hallr](img/hallr.png)

Experimental Blender addon written in Rust. Work in progress, expect wildly fluctuating API:s.

## Usage
Expand Down
51 changes: 50 additions & 1 deletion blender_addon/hallr_mesh_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,53 @@ def execute(self, context):
return {'FINISHED'}


# Voronoi mesh operator
class Hallr_Voronoi_Mesh(bpy.types.Operator):
bl_idname = "mesh.hallr_meshtools_voronoi_mesh"
bl_label = "Voronoi Mesh"
bl_description = "Calculate voronoi diagram and add mesh, the geometry must be flat and on a plane intersecting origin."
bl_options = {'REGISTER', 'UNDO'}

distance: bpy.props.FloatProperty(
name="Discretization distance",
description="Discretization distance as a percentage of the total AABB size. This value is used when sampling "
"parabolic arc edges. Smaller value gives a finer step distance.",
default=0.005,
min=0.0001,
max=4.9999,
precision=6,
subtype='PERCENTAGE'
)

@classmethod
def poll(cls, context):
ob = context.active_object
return ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH'

def execute(self, context):
obj = context.active_object

if obj.type != 'MESH':
self.report({'ERROR'}, "Active object is not a mesh!")
return {'CANCELLED'}

# Ensure the object is in object mode
bpy.ops.object.mode_set(mode='OBJECT')

config = {"command": "voronoi_mesh",
"DISTANCE": str(math.degrees(self.distance)),
}
# Call the Rust function
vertices, indices, config_out = hallr_ffi_utils.call_rust_direct(config, obj, use_line_chunks=True)
hallr_ffi_utils.handle_received_object_replace_active(obj, config_out, vertices, indices)

return {'FINISHED'}

def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)


# menu containing all tools
class VIEW3D_MT_edit_mesh_hallr_meshtools(bpy.types.Menu):
bl_label = "Hallr meshtools"
Expand All @@ -340,6 +387,7 @@ def draw(self, context):
layout.operator("mesh.hallr_meshtools_select_vertices_until_intersection")
layout.operator("mesh.hallr_meshtools_select_intersection_vertices")
layout.operator("mesh.hallr_knife_intersect_2d")
layout.operator("mesh.hallr_meshtools_voronoi_mesh")


# draw function for integration in menus
Expand All @@ -356,7 +404,8 @@ def menu_func(self, context):
Hallr_SelectVerticesUntilIntersection,
Hallr_SelectCollinearEdges,
MESH_OT_hallr_convex_hull_2d,
MESH_OT_hallr_knife_intersect
MESH_OT_hallr_knife_intersect,
Hallr_Voronoi_Mesh
)


Expand Down
Binary file added img/hallr.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 18 additions & 7 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod cmd_delaunay_triangulation_2d;
mod cmd_knife_intersect;
mod cmd_simplify_rdp;
pub mod cmd_surface_scan;
mod cmd_voronoi_mesh;
mod create_test;
mod impls;

Expand All @@ -15,10 +16,17 @@ use vector_traits::{glam::Vec3, GenericVector3};
/// The largest dimension of the voronoi input, totally arbitrarily selected.
const DEFAULT_MAX_VORONOI_DIMENSION: f64 = 200000.0;

/// The length of one 'step' for curved edges discretization as a percentage of the longest
/// AABB axis of the object.
const DEFAULT_VORONOI_DISCRETE_DISTANCE: f64 = 0.0001;

trait Options {
/// Will return an option parsed as a `T` or an Err
fn get_mandatory_parsed_option<T: std::str::FromStr>(&self, key: &str)
-> Result<T, HallrError>;
fn get_mandatory_parsed_option<T: std::str::FromStr>(
&self,
key: &str,
default: Option<T>,
) -> Result<T, HallrError>;

/// Will return an option parsed as a `T` or None.
/// If the option is missing None is returned, if it there but if it can't be parsed an error
Expand Down Expand Up @@ -84,8 +92,8 @@ pub fn validate_input_data<'a, T: GenericVector3>(
"No more than u32::MAX indices are supported".to_string(),
))?
}
let _ = config.get_mandatory_parsed_option::<usize>("first_vertex_model_0")?;
let _ = config.get_mandatory_parsed_option::<usize>("first_index_model_0")?;
let _ = config.get_mandatory_parsed_option::<usize>("first_vertex_model_0", None)?;
let _ = config.get_mandatory_parsed_option::<usize>("first_index_model_0", None)?;
Ok(())
}

Expand All @@ -106,9 +114,11 @@ pub fn collect_models<'a, T: GenericVector3>(
// Check if the keys exist in the config
if config.does_option_exist(&vertices_key)? {
// Retrieve the vertex and index data as strings
let vertices_idx: usize = config.get_mandatory_parsed_option(&vertices_key)?;
let indices_idx: usize = config
.get_mandatory_parsed_option(&format!("first_index_model_{}", model_counter))?;
let vertices_idx: usize = config.get_mandatory_parsed_option(&vertices_key, None)?;
let indices_idx: usize = config.get_mandatory_parsed_option(
&format!("first_index_model_{}", model_counter),
None,
)?;
let vertices_end_idx: usize = config
.get_parsed_option(&format!("first_vertex_model_{}", model_counter + 1))?
.unwrap_or(vertices.len());
Expand Down Expand Up @@ -156,6 +166,7 @@ pub(crate) fn process_command(
"centerline" => cmd_centerline::process_command::<T>(config, models)?,
"2d_outline" => cmd_2d_outline::process_command::<T>(config, models)?,
"knife_intersect" => cmd_knife_intersect::process_command::<T>(config, models)?,
"voronoi_mesh" => cmd_voronoi_mesh::process_command::<T>(config, models)?,
illegal_command => Err(HallrError::InvalidParameter(format!(
"Invalid command:{}",
illegal_command
Expand Down
4 changes: 2 additions & 2 deletions src/command/cmd_centerline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ where
println!("config{:?}", config);
}
// angle is supposed to be in degrees
let cmd_arg_angle: T::Scalar = config.get_mandatory_parsed_option("ANGLE")?;
let cmd_arg_angle: T::Scalar = config.get_mandatory_parsed_option("ANGLE", None)?;
if !(0.0.into()..=90.0.into()).contains(&cmd_arg_angle) {
return Err(HallrError::InvalidInputData(format!(
"The valid range of ANGLE is [0..90] :({})",
Expand All @@ -320,7 +320,7 @@ where
.get_parsed_option::<bool>("REMOVE_INTERNALS")?
.unwrap_or(true);

let cmd_arg_discrete_distance = config.get_mandatory_parsed_option("DISTANCE")?;
let cmd_arg_discrete_distance = config.get_mandatory_parsed_option("DISTANCE", None)?;
if !(0.004.into()..100.0.into()).contains(&cmd_arg_discrete_distance) {
return Err(HallrError::InvalidInputData(format!(
"The valid range of DISTANCE is [0.005..100[% :({:?})",
Expand Down
2 changes: 1 addition & 1 deletion src/command/cmd_simplify_rdp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ where
FFIVector3: ConvertTo<T>,
HashableVector2: From<T::Vector2>,
{
let epsilon: T::Scalar = config.get_mandatory_parsed_option("epsilon")?;
let epsilon: T::Scalar = config.get_mandatory_parsed_option("epsilon", None)?;
//println!("rust: vertices.len():{}", vertices.len());
//println!("rust: indices.len():{}", indices.len());
//println!("rust: indices:{:?}", indices);
Expand Down
28 changes: 17 additions & 11 deletions src/command/cmd_surface_scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,14 @@ where
let search_config = if config.does_option_exist("xy_sample_dist_multiplier")? {
SearchPatternConfig::<T, FFIVector3>::new(probe, minimum_z).with_adaptive_config(
AdaptiveSearchConfig::new(
config.get_mandatory_parsed_option::<T::Scalar>("xy_sample_dist_multiplier")?
config
.get_mandatory_parsed_option::<T::Scalar>("xy_sample_dist_multiplier", None)?
* step,
config.get_mandatory_parsed_option::<T::Scalar>("z_jump_threshold_multiplier")?
* step,
config.get_mandatory_parsed_option::<bool>("reduce_adaptive")?,
config.get_mandatory_parsed_option::<T::Scalar>(
"z_jump_threshold_multiplier",
None,
)? * step,
config.get_mandatory_parsed_option::<bool>("reduce_adaptive", None)?,
),
)
} else {
Expand Down Expand Up @@ -102,11 +105,14 @@ where
let search_config = if config.does_option_exist("xy_sample_dist_multiplier")? {
SearchPatternConfig::<T, FFIVector3>::new(probe, minimum_z).with_adaptive_config(
AdaptiveSearchConfig::new(
config.get_mandatory_parsed_option::<T::Scalar>("xy_sample_dist_multiplier")?
* step,
config.get_mandatory_parsed_option::<T::Scalar>("z_jump_threshold_multiplier")?
config
.get_mandatory_parsed_option::<T::Scalar>("xy_sample_dist_multiplier", None)?
* step,
config.get_mandatory_parsed_option::<bool>("reduce_adaptive")?,
config.get_mandatory_parsed_option::<T::Scalar>(
"z_jump_threshold_multiplier",
None,
)? * step,
config.get_mandatory_parsed_option::<bool>("reduce_adaptive", None)?,
),
)
} else {
Expand Down Expand Up @@ -148,9 +154,9 @@ where
let bounding_indices = bounding_shape.indices;
let bounding_vertices = bounding_shape.vertices;

let probe_radius = config.get_mandatory_parsed_option("probe_radius")?;
let minimum_z = config.get_mandatory_parsed_option("minimum_z")?;
let step = config.get_mandatory_parsed_option("step")?;
let probe_radius = config.get_mandatory_parsed_option("probe_radius", None)?;
let minimum_z = config.get_mandatory_parsed_option("minimum_z", None)?;
let step = config.get_mandatory_parsed_option("step", None)?;
let probe: Box<dyn Probe<T, FFIVector3>> = match config.get_mandatory_option("probe")? {
"SQUARE_END" => Box::new(SquareEndProbe::new(&mesh_analyzer, probe_radius)?),
"BALL_NOSE" => Box::new(BallNoseProbe::new(&mesh_analyzer, probe_radius)?),
Expand Down
Loading

0 comments on commit 038cd82

Please sign in to comment.