mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-14 06:12:28 +00:00
Merge main into #2200
This commit is contained in:
commit
31f014286e
119 files changed
+7247
-253
No files matched your search
@@ -24,8 +24,8 @@ use instance::{detach, make_slot, push_changes, remove_slot, reset_instance, res
|
||||
pub(crate) use instance::{swap_at, swap_instance};
|
||||
use props::{add_prop, bind_prop, delete_prop, edit_prop, set_props};
|
||||
pub(crate) use props::{apply_prop_bindings, check_prop_binding, describe_props};
|
||||
use variants::{add_variant, combine};
|
||||
pub use variants::{can_add_variant, can_combine_variants};
|
||||
use variants::{add_variant, combine, create_set};
|
||||
pub use variants::{can_add_variant, can_combine_variants, can_create_set};
|
||||
|
||||
/// `component.hideWhenPublishing`: the leading `.` of the names of this file's components and
|
||||
/// sets (those `ids` are or are in), put on or taken off.
|
||||
@@ -72,6 +72,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
s.set_selection(out.iter().filter_map(id_of).collect());
|
||||
Ok(json!({"ids": out}))
|
||||
}),
|
||||
spec!(
|
||||
"component.createSet",
|
||||
"Create component set",
|
||||
"{ids? (layers, not components, sets or instances)} (each becomes a component, wrapped in place in a component set: variants \"Property 1=<name>\" left to right, 20 px around)",
|
||||
true,
|
||||
create_set
|
||||
),
|
||||
spec!(
|
||||
"component.combineVariants",
|
||||
"Combine as variants",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::{
|
||||
BTreeMap, BTreeSet, Document, EngineError, Node, NodeId, NodeKind, Result, Session, Value, bad, inside_instance, json, owning_instance,
|
||||
req_targets, roots, set_name_from, string, targets, variant_name, variant_options, variant_props, variant_set, z_sorted,
|
||||
BTreeMap, BTreeSet, Document, EngineError, Node, NodeId, NodeKind, Result, Session, Value, bad, create, id_of, inside_instance, json,
|
||||
owning_instance, req_targets, roots, set_name_from, string, targets, variant_name, variant_options, variant_props, variant_set, z_sorted,
|
||||
};
|
||||
|
||||
/// Wrap components in a new component set (named after the first) placed around them.
|
||||
@@ -94,6 +94,75 @@ pub(super) fn combine(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Ok(json!({"id": set}))
|
||||
}
|
||||
|
||||
/// Why `ids` (distinct) can't each become a component of a new set (`component.createSet`), or
|
||||
/// `None` when they can: layers inside an instance, library copies, instances, components and
|
||||
/// component sets, and a layer inside another one of `ids`.
|
||||
fn create_set_refusal(doc: &Document, ids: &[NodeId]) -> Option<&'static str> {
|
||||
if ids.iter().any(|i| inside_instance(doc, *i)) {
|
||||
Some("layers inside an instance can't become components")
|
||||
} else if ids.iter().any(|i| doc.is_remote(*i)) {
|
||||
Some(crate::library::READ_ONLY)
|
||||
} else if ids.iter().any(|i| {
|
||||
doc.node(*i).is_some_and(|n| matches!(n.kind, NodeKind::Instance { .. } | NodeKind::Component { .. } | NodeKind::ComponentSet { .. }))
|
||||
}) {
|
||||
Some("select layers that aren't components, component sets or instances")
|
||||
} else if roots(doc, ids).len() != ids.len() {
|
||||
Some("a layer can't be in a set with one inside it")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether "Create component set" is on offer for `ids`: at least one layer that
|
||||
/// [`create_set_refusal`] lets through.
|
||||
pub fn can_create_set(doc: &Document, ids: &[NodeId]) -> bool {
|
||||
let distinct: Vec<NodeId> = ids.iter().copied().collect::<BTreeSet<_>>().into_iter().collect();
|
||||
!distinct.is_empty() && distinct.iter().all(|i| doc.contains(*i)) && create_set_refusal(doc, &distinct).is_none()
|
||||
}
|
||||
|
||||
/// `component.createSet`: each target becomes a component (as `component.createMultiple` makes
|
||||
/// them) and all are wrapped in place in a component set, 20 px around, named as Figma names them:
|
||||
/// variants "Property 1=<layer name>" in left-to-right order (a name holding `=` is kept); layers
|
||||
/// that all share a name give a set of that name with "Property 1=Default" variants; otherwise the
|
||||
/// set is "Component N".
|
||||
pub(super) fn create_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "component.createSet";
|
||||
let mut seen = BTreeSet::new();
|
||||
let ids: Vec<NodeId> = req_targets(cmd, s, p)?.into_iter().filter(|i| seen.insert(*i)).collect();
|
||||
let doc = s.doc()?;
|
||||
if let Some(why) = create_set_refusal(doc, &ids) {
|
||||
return Err(EngineError::Disabled(cmd.into(), why.into()));
|
||||
}
|
||||
let mut made = Vec::new();
|
||||
for i in roots(doc, &ids) {
|
||||
let r = create(s, &json!({"ids": [i]}))?;
|
||||
made.push(id_of(&r["id"]).ok_or_else(|| bad(cmd, "component creation failed"))?);
|
||||
}
|
||||
let doc = s.doc_mut()?;
|
||||
// Left to right by world x, ties by y: the variants' order, and the values' order.
|
||||
let mut by_x: Vec<(NodeId, f64, f64)> = made.iter().map(|i| doc.world_bounds(*i).map_or((*i, 0.0, 0.0), |b| (*i, b.x0, b.y0))).collect();
|
||||
by_x.sort_by(|a, b| a.1.total_cmp(&b.1).then(a.2.total_cmp(&b.2)));
|
||||
let names: Vec<String> = by_x.iter().map(|(i, ..)| doc.node(*i).map(|n| n.name.clone()).unwrap_or_default()).collect();
|
||||
let same = names.windows(2).all(|w| w.first() == w.get(1)) && names.first().is_some_and(|n| !n.is_empty() && !n.contains('='));
|
||||
let set_name = match names.first() {
|
||||
Some(n) if same => n.clone(),
|
||||
_ => doc.next_name("Component"),
|
||||
};
|
||||
for ((i, ..), name) in by_x.iter().zip(&names) {
|
||||
let node = doc.get_mut(*i)?;
|
||||
if same {
|
||||
node.name = "Property 1=Default".into();
|
||||
} else if !name.contains('=') {
|
||||
node.name = format!("Property 1={name}");
|
||||
}
|
||||
}
|
||||
let ordered = z_sorted(doc, &made);
|
||||
let set = wrap_in_set(cmd, doc, &ordered)?;
|
||||
doc.get_mut(set)?.name = set_name;
|
||||
s.set_selection(vec![set]);
|
||||
Ok(json!({"id": set}))
|
||||
}
|
||||
|
||||
/// Grow a component set so it wraps its variants with 20 px of padding (right and bottom).
|
||||
pub(super) fn fit_set(doc: &mut Document, set: NodeId) {
|
||||
let Some(kids) = doc.node(set).map(|n| n.children.clone()) else { return };
|
||||
|
||||
@@ -254,7 +254,7 @@ pub(super) fn stroke_brush(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
pub(super) fn reparent(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids = req_targets("node.reparent", s, p)?;
|
||||
let parent = req_id("node.reparent", p, "parent")?;
|
||||
let index = num(p, "index").map(|i| i.max(0.0) as usize);
|
||||
let index = opt_index("node.reparent", p, "index")?;
|
||||
let doc = s.doc_mut()?;
|
||||
let ids = z_sorted(doc, &roots(doc, &ids));
|
||||
// `index` is where the first moved layer ends up among the parent's children (bottom first),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::load::{ext, write};
|
||||
use super::save::thumbnail;
|
||||
use super::*;
|
||||
use crate::params::f64_of;
|
||||
use crate::params::{f64_of, req_targets};
|
||||
|
||||
/// Encode `ids` (or the page) in `format` at `scale`.
|
||||
pub fn export_bytes(doc: &Document, ids: &[NodeId], format: ExportFormat, scale: f64) -> Result<Vec<u8>> {
|
||||
@@ -86,7 +86,7 @@ pub(super) fn export(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
_ => {}
|
||||
}
|
||||
let doc = s.doc()?;
|
||||
let mut ids = targets(s, p);
|
||||
let mut ids = export_targets(cmd, s, p)?;
|
||||
if ids.is_empty() {
|
||||
ids = doc.get(s.page()?)?.children.clone();
|
||||
}
|
||||
@@ -325,11 +325,22 @@ pub fn exportable_layers(doc: &Document, page: NodeId) -> Vec<NodeId> {
|
||||
doc.subtree(page).into_iter().skip(1).filter(|i| doc.node(*i).is_some_and(|n| !n.exports.is_empty())).collect()
|
||||
}
|
||||
|
||||
/// Named layers (`ids` / `id`), checked like other commands; empty when none are named, so the
|
||||
/// caller can fall back to its default instead of silently dropping a bad id.
|
||||
fn export_targets(cmd: &str, s: &Session, p: &Value) -> Result<Vec<NodeId>> {
|
||||
let named = match get(p, "ids") {
|
||||
Some(Value::Array(a)) if !a.is_empty() => true,
|
||||
Some(Value::Array(_) | Value::Null) | None => get(p, "id").is_some_and(|v| !v.is_null()),
|
||||
Some(_) => true,
|
||||
};
|
||||
if named { req_targets(cmd, s, p) } else { Ok(targets(s, p)) }
|
||||
}
|
||||
|
||||
pub(super) fn export_layers(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "file.exportLayers";
|
||||
let doc = s.doc()?;
|
||||
let assets = boolean(p, "assets").unwrap_or(false);
|
||||
let mut ids = targets(s, p);
|
||||
let mut ids = export_targets(cmd, s, p)?;
|
||||
if ids.is_empty() {
|
||||
ids = if assets { vec![s.page()?] } else { exportable_layers(doc, s.page()?) };
|
||||
}
|
||||
|
||||
@@ -399,11 +399,19 @@ pub(crate) fn load_fig(bytes: &[u8], name: &str) -> Option<Result<figcraft_forma
|
||||
}))
|
||||
}
|
||||
|
||||
/// The most layers all of a Penpot file's raw SVG placeholders may expand to together.
|
||||
const MAX_PENPOT_SVG_LAYERS: usize = 100_000;
|
||||
|
||||
/// Turn Penpot raw SVG placeholders into editable FigCraft SVG content, using the
|
||||
/// normal SVG importer. The parser (figcraft-format) only stores bounded XML so it
|
||||
/// does not need to depend on the SVG crate; the engine does this second pass.
|
||||
/// Any failure leaves the original placeholder and XML data intact for later use.
|
||||
fn expand_penpot_raw_svgs(doc: &mut Document, notes: &mut Vec<String>) {
|
||||
expand_penpot_raw_svgs_within(doc, notes, MAX_PENPOT_SVG_LAYERS);
|
||||
}
|
||||
|
||||
/// [`expand_penpot_raw_svgs`] with at most `room` layers made in all.
|
||||
fn expand_penpot_raw_svgs_within(doc: &mut Document, notes: &mut Vec<String>, mut room: usize) {
|
||||
let layers = doc
|
||||
.nodes()
|
||||
.filter_map(|node| {
|
||||
@@ -413,12 +421,24 @@ fn expand_penpot_raw_svgs(doc: &mut Document, notes: &mut Vec<String>) {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut ok_count = 0usize;
|
||||
// The SVG importer caps its own layers per file; `room` is the cap over all of a file's placeholders.
|
||||
for (id, name, width, height, xml, empty) in layers {
|
||||
if room == 0 {
|
||||
if let Some(n) = doc.node_mut(id) {
|
||||
n.plugin_data.insert("penpot.svgRawStatus".into(), "not-expanded".into());
|
||||
}
|
||||
if notes.len() < 100 {
|
||||
notes.push(format!("Penpot SVG '{name}': not converted, the file's SVG layers are over the limit; the original markup is preserved"));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if !empty {
|
||||
notes.push(format!("Penpot SVG '{name}': has separate child layers; the original tree was preserved to prevent duplicate rendering"));
|
||||
continue;
|
||||
}
|
||||
match figcraft_svg::import_svg_with_notes(doc, xml.as_bytes(), id, 0.0, 0.0, &name) {
|
||||
let before = doc.node_count();
|
||||
let imported = figcraft_svg::import_svg_with_notes(doc, xml.as_bytes(), id, 0.0, 0.0, &name);
|
||||
match imported {
|
||||
Ok(imported) => {
|
||||
// An SVG can be syntactically valid yet contain no supported drawable
|
||||
// elements. Do not claim success or leave an empty extra frame.
|
||||
@@ -432,6 +452,21 @@ fn expand_penpot_raw_svgs(doc: &mut Document, notes: &mut Vec<String>) {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Charged once the empty ones are gone; one that doesn't fit is taken out again.
|
||||
let added = doc.node_count().saturating_sub(before);
|
||||
if added > room {
|
||||
let _ = doc.remove(imported.frame);
|
||||
if let Some(n) = doc.node_mut(id) {
|
||||
n.plugin_data.insert("penpot.svgRawStatus".into(), "not-expanded".into());
|
||||
}
|
||||
if notes.len() < 100 {
|
||||
notes.push(format!(
|
||||
"Penpot SVG '{name}': not converted, the file's SVG layers are over the limit; the original markup is preserved"
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
room -= added;
|
||||
let size = doc.node(imported.frame).map(|n| (n.width, n.height));
|
||||
if let Some((svg_w, svg_h)) = size
|
||||
&& svg_w.is_finite()
|
||||
@@ -470,6 +505,28 @@ fn expand_penpot_raw_svgs(doc: &mut Document, notes: &mut Vec<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether some of the first `len` characters are in none of `runs`. Where runs start and stop
|
||||
/// covering is summed once, so runs overlapping any number of times cost no more than their count.
|
||||
fn has_uncovered(len: usize, runs: &[figcraft_doc::StyleRun]) -> bool {
|
||||
let mut change = vec![0i64; len + 1];
|
||||
for run in runs {
|
||||
let (start, end) = (run.start.min(len), run.end.min(len));
|
||||
if start < end {
|
||||
if let Some(c) = change.get_mut(start) {
|
||||
*c += 1;
|
||||
}
|
||||
if let Some(c) = change.get_mut(end) {
|
||||
*c -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut open = 0i64;
|
||||
change.iter().take(len).any(|c| {
|
||||
open += c;
|
||||
open <= 0
|
||||
})
|
||||
}
|
||||
|
||||
/// Non-destructive font diagnostic for the action palette, CLI and control API.
|
||||
/// Includes styled runs, not just the default typeface of each text layer.
|
||||
pub(super) fn font_report(doc: &Document) -> Value {
|
||||
@@ -485,21 +542,15 @@ fn font_report_with_families(doc: &Document, installed: &[String]) -> Value {
|
||||
if text.characters.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut covered = vec![false; text.characters.chars().count().min(100_000)];
|
||||
let len = text.characters.chars().count().min(100_000);
|
||||
let mut used = BTreeSet::new();
|
||||
for run in &text.runs {
|
||||
let start = run.start.min(covered.len());
|
||||
let end = run.end.min(covered.len());
|
||||
if start < end {
|
||||
if !run.style.font_family.trim().is_empty() {
|
||||
used.insert(run.style.font_family.clone());
|
||||
}
|
||||
for entry in covered.iter_mut().take(end).skip(start) {
|
||||
*entry = true;
|
||||
}
|
||||
if run.start.min(len) < run.end.min(len) && !run.style.font_family.trim().is_empty() {
|
||||
used.insert(run.style.font_family.clone());
|
||||
}
|
||||
}
|
||||
if covered.iter().any(|entry| !entry) {
|
||||
let uncovered = has_uncovered(len, &text.runs);
|
||||
if uncovered {
|
||||
used.insert(text.style.font_family.clone());
|
||||
}
|
||||
for family in used {
|
||||
@@ -599,19 +650,14 @@ fn missing_penpot_font_warnings(doc: &Document, families: &[String]) -> Vec<Stri
|
||||
if text_len == 0 {
|
||||
continue;
|
||||
}
|
||||
let mut covered = vec![false; text_len.min(100_000)];
|
||||
let len = text_len.min(100_000);
|
||||
let mut used = BTreeSet::new();
|
||||
for run in &text.runs {
|
||||
let start = run.start.min(covered.len());
|
||||
let end = run.end.min(covered.len());
|
||||
if start < end {
|
||||
if run.start.min(len) < run.end.min(len) {
|
||||
used.insert(run.style.font_family.clone());
|
||||
for flag in covered.iter_mut().take(end).skip(start) {
|
||||
*flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if covered.iter().any(|present| !present) {
|
||||
if has_uncovered(len, &text.runs) {
|
||||
used.insert(text.style.font_family.clone());
|
||||
}
|
||||
for family in used {
|
||||
@@ -652,8 +698,10 @@ fn link_instances(doc: &mut Document) {
|
||||
let mut insts: Vec<(usize, NodeId)> =
|
||||
doc.nodes().filter(|n| matches!(n.kind, NodeKind::Instance { .. })).map(|n| (doc.ancestors(n.id).len(), n.id)).collect();
|
||||
insts.sort_by_key(|a| std::cmp::Reverse(a.0));
|
||||
// Rebasing leaves the mains alone, so each main's layer paths are worked out once.
|
||||
let mut mains = crate::components::MainPaths::new();
|
||||
for (_, i) in insts {
|
||||
crate::components::rebase_instance(doc, i);
|
||||
crate::components::rebase_instance_with(doc, i, &mut mains);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -790,6 +838,47 @@ mod penpot_svg_v10_tests {
|
||||
use super::*;
|
||||
use figcraft_doc::TextData;
|
||||
|
||||
#[test]
|
||||
fn placeholders_share_one_layer_allowance() {
|
||||
let mut doc = Document::new("Two SVGs");
|
||||
let page = doc.first_page().unwrap();
|
||||
let svg = r#"<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="5" height="5" fill="red"/><rect x="5" width="5" height="5" fill="blue"/></svg>"#;
|
||||
let ids: Vec<NodeId> = (0..2)
|
||||
.map(|i| {
|
||||
let id = doc.create(page, NodeKind::Frame, &format!("S{i}"), 0.0, 0.0, 10.0, 10.0).unwrap();
|
||||
let layer = doc.node_mut(id).unwrap();
|
||||
layer.plugin_data.insert("penpot.svgRawXml".into(), svg.into());
|
||||
layer.plugin_data.insert("penpot.svgRawStatus".into(), "pending".into());
|
||||
id
|
||||
})
|
||||
.collect();
|
||||
let mut warnings = Vec::new();
|
||||
let nodes = doc.node_count();
|
||||
expand_penpot_raw_svgs_within(&mut doc, &mut warnings, 4);
|
||||
assert!(doc.node_count() - nodes <= 4, "never more layers than the allowance");
|
||||
let status = |i: usize| doc.node(ids[i]).unwrap().plugin_data.get("penpot.svgRawStatus").cloned();
|
||||
assert_eq!(status(0).as_deref(), Some("expanded"), "the first uses the allowance");
|
||||
assert_eq!(status(1).as_deref(), Some("not-expanded"));
|
||||
assert!(doc.node(ids[1]).unwrap().children.is_empty());
|
||||
assert!(warnings.iter().any(|w| w.contains("over the limit")), "{warnings:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_overlapping_style_runs_are_summed_once() {
|
||||
use figcraft_doc::StyleRun;
|
||||
let mut doc = Document::new("Runs");
|
||||
let page = doc.first_page().unwrap();
|
||||
let mut t = TextData::new("x".repeat(100_000));
|
||||
t.style.font_family = "Unused Base".into();
|
||||
let mut run = t.style.clone();
|
||||
run.font_family = "Run Font".into();
|
||||
t.runs = (0..50_000).map(|_| StyleRun { start: 0, end: 100_000, style: run.clone() }).collect();
|
||||
doc.create(page, NodeKind::Text(t), "Long", 0.0, 0.0, 100.0, 30.0).unwrap();
|
||||
let report = font_report_with_families(&doc, &[]);
|
||||
assert_eq!(report["fonts"].as_array().map(Vec::len), Some(1), "{report}");
|
||||
assert_eq!(report["fonts"][0]["family"], "Run Font");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expands_inline_svg_into_editable_shapes_without_discarding_source() {
|
||||
let mut doc = Document::new("SVG fixture");
|
||||
|
||||
@@ -5,7 +5,7 @@ use figcraft_doc::{Document, ImagePreset, Node};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{CommandSpec, ok};
|
||||
use crate::params::{f64_of, get, num, req_index, req_string, req_targets, string, targets};
|
||||
use crate::params::{f64_of, get, req_index, req_string, req_targets, string, targets};
|
||||
use crate::{EngineError, Result, Session, bad, spec};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
@@ -236,7 +236,10 @@ fn save_preset(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Some(v) => with_values(cmd, ImageFilters::NONE, v)?,
|
||||
None => {
|
||||
let stroke = strokes(cmd, p)?;
|
||||
let index = num(p, "index").filter(|k| k.is_finite() && *k >= 0.0).map(|k| k as usize);
|
||||
let index = match get(p, "index") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(_) => Some(req_index(cmd, p, "index")?),
|
||||
};
|
||||
let doc = s.doc()?;
|
||||
targets(s, p)
|
||||
.iter()
|
||||
|
||||
@@ -799,7 +799,7 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
WindingRule::NonZero
|
||||
};
|
||||
let path = drawn.fill_path();
|
||||
let path = figcraft_geom::kurbo::simplify::simplify_bezpath(path.iter(), tol, &Default::default());
|
||||
let path = figcraft_geom::simplify_path(&path, tol);
|
||||
let (path, output_winding) = match figcraft_pathops::simplify_with_rule(&path, winding == WindingRule::EvenOdd) {
|
||||
Ok(path) => (path, WindingRule::EvenOdd),
|
||||
Err(_) => (path, winding),
|
||||
@@ -809,7 +809,7 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
} else {
|
||||
for (index, region) in source.regions.iter().enumerate() {
|
||||
let Some(path) = drawn.region_path(index) else { continue };
|
||||
let path = figcraft_geom::kurbo::simplify::simplify_bezpath(path.iter(), tol, &Default::default());
|
||||
let path = figcraft_geom::simplify_path(&path, tol);
|
||||
let path = figcraft_pathops::simplify_with_rule(&path, region.winding == WindingRule::EvenOdd).unwrap_or(path);
|
||||
append_region_path(&mut net, &path, region, region.winding);
|
||||
}
|
||||
@@ -826,7 +826,7 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
open.regions.clear();
|
||||
if !open.segments.is_empty() {
|
||||
let path = open.stroke_path();
|
||||
let simplified = figcraft_geom::kurbo::simplify::simplify_bezpath(path.iter(), tol, &Default::default());
|
||||
let simplified = figcraft_geom::simplify_path(&path, tol);
|
||||
let mut loose = VectorNetwork::from_path(&simplified);
|
||||
loose.regions.clear();
|
||||
append_network(&mut net, loose);
|
||||
@@ -834,7 +834,7 @@ fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
if source.regions.is_empty() {
|
||||
let path = source.stroke_path();
|
||||
let simplified = figcraft_geom::kurbo::simplify::simplify_bezpath(path.iter(), tol, &Default::default());
|
||||
let simplified = figcraft_geom::simplify_path(&path, tol);
|
||||
net = VectorNetwork::from_path(&simplified);
|
||||
net.regions.clear();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user