Add API to emit type-checking diagnostics

Summary
This PR introduces a new API to TypeInferenceBuilder to emit type-checking diagnostics.
The API is intentionally kept simple. The PR doesn't aim to resolve on how we represent rules/violations in Red Knot.
Design considerations
This PR wraps diagnostics in an Arc to make them cheap-cloneable. It's desired to have cheap-cloneable diagnostics
because we have to copy them between infer_expression -> infer_definition -> infer_scope...
An alternative design that I think is worth exploring once the Salsa tables refactor lands is to introduce a
TypeCheckDiagnosticIngredient that has a single field, the diagnostic. The advantage of this approach would be that
we get "Arena" allocation for the diagnostics. The downside is that diagnostic must be verified everytime the revision changes which
isn't entirely free.
Rule Selector
Long term, I think we want to have a rule_selector(file) -> RuleSelector query that resolves the enabled rules per file.
As said before, this PR doesn't try to resolve how and which rules are enabled per file. That's why I omitted this part for now but the idea is that
push_diagnostic would call the rule_selector instead of just calling is_file_open.
Example diagnostic
I implemented an example diagnostic that flags unresolved imports.
Why do we need is_file_open
The check for is_file_open is mainly an optimization to avoid generating a lot of diagnostics that will never be shown.
Test Plan
I added a small unit test. I also verified that running the CLI shows the diagnostic.

You also need to emit "unresolved import" diagnostics on from foo import bar imports where foo can be resolved to a module but there is no bar symbol inside the foo module. We do so on main, but this PR means that we now only emit "unresolved import" diagnostics on import foo imports or from foo import bar imports where the module foo cannot be resolved.

Yeah, I'm not aiming for parity. The goal here is to add the API and unresolved imports was the easiest example where I didn't had to make something up.

Yeah, I'm not aiming for parity with the lint rule. The goal here is to add the API, and unresolved imports were the easiest example where I didn't have to make something up.

Fair enough -- but parity with main isn't too hard here, and it also fixes the failing assertions you have currrently in the benchmarks. You just need to do this:
--- a/crates/red_knot_python_semantic/src/types/infer.rs
+++ b/crates/red_knot_python_semantic/src/types/infer.rs
@@ -943,7 +943,7 @@ impl<'db> TypeInferenceBuilder<'db> {
             ModuleName::new(module_name)
         };

-        let module_ty = self.module_ty_from_name(module_name, import_from.into());
+        let module_ty = self.module_ty_from_name(module_name.clone(), import_from.into());

         let ast::Alias {
             range: _,
@@ -960,6 +960,15 @@ impl<'db> TypeInferenceBuilder<'db> {
             .member(self.db, &Name::new(&name.id))
             .replace_unbound_with(self.db, Type::Unknown);

+        // We'll already have emitted a diagnostic if `module_name` is `None`
+        if let Some(module_name) = module_name {
+            if ty.is_unknown() {
+                if let Some(builder) = self.push_diagnostic(alias.into(), "reportMissingImport") {
+                    builder.finish(format!("Could not resolve import of {name} from {module_name}"));
+                }
+            }
+        }
+
         self.types.definitions.insert(definition, ty);
     }

Fair enough -- but parity with main isn't too hard here, and it also fixes the failing assertions you have currrently in the benchmarks. You just need to do this to your PR branch:

I didn't do it because I am unclear on whether the diagnostic shouldn't be created in ty.member. That's why I still prefer to not make this change as part of this PR

I didn't do it because I am unclear on whether the diagnostic shouldn't be created in ty.member.

(Definitely not, because we'll also use ty.member() for e.g. import foo; x = foo.bar errors where the foo module has no member bar -- but there it's an unresolved attribute access rather than an unresolved import, so it'll need a different error message and a different code)

(Definitely not, because we'll also use ty.member() for e.g. import foo; x = foo.bar errors where the foo module has no member bar -- but there it's an unresolved attribute access rather than an unresolved import, so it'll need a different error message and a different code)

Still, I don't know ;) Feels annoying having to test this on every call site


Use &mut self in NotificationProcessor::init

Since you are working in this area, would you be able to address the timeout cancellation issue? Essentially the timeout cancellation request is sent, but the timeout manager emits the timeout prior to receiving the cancellation request.
https://github.com/knox-networks/core/blob/cc4cea1579f6450aa0c9b6f8a820a315de54789e/transaction-manager/src/state_machine/asset_transfer.rs#L764-L771


Changes to get this to work (on linux, at least)

A) thank you so much for putting this together, i was really struggling to find any documentation on how to get VVISF to work with SDK_GLFW.
when i compile this out of the box, i dont know why, but the displayScene renders red over the whole window fine,
but the isfScene only renders over the top right quarter of the window.
furthermore, the isfScene renders only one texel across that quadrant, because targetTex->glReadySrcRect() is {(0,0),(1x1)}
if  i leave tmpRect unmodified when setting up the texs array, it works as expected (i.e. i can see the full shader, but still just on that top right quadrant)
i cant figure out why the isfScene is rendering over a quarter of the screen;  what i did was to use isfScene for everything (i.e s/displayScene/isfScene/g ) .
you still need to call render() again after rendering to a texture buffer, which doesnt feel right, (and kills my fps)
but as a workaround i save the callback lambda to a variable; and every frame i
targetTex = isfScene->createAndRenderABuffer(size);
setRenderCallback(renderCallback);
isfScene->render();

equivalently one could set targetTex to null, or maybe if one could access GLScene::_renderTarget from within the callback one could get away with a single invocation idk.
i dont know why these changes were necessary, i havent tried this on osx yet

Thanks!


zwave node names deleted

Home Assistant release (hass --version):
0.33.4
Python release (python3 --version):
Python 3.4.5
Component/platform:
zwave
Description of problem:
On two occasions a node name in my zwcfg file has been removed. For example:
<Node id="2" name="Study" location="" basic="4" generic="17" specific="4" type="Multilevel Scene Switch" listening="true" frequentListening="false" beaming="true" routing="true" max_baud_rate="40000" version="4" query_stage="Complete">
becomes
<Node id="2" name="" location="" basic="4" generic="17" specific="4" type="Multilevel Scene Switch" listening="true" frequentListening="false" beaming="true" routing="true" max_baud_rate="40000" version="4" query_stage="Complete">
Expected:
Node names remain persistent and are not deleted.
Additional info:
I have not yet worked out what event causes this to happen. Not all node names are deleted when the issue occurs.
Opening this issue for others who may have it to further narrow down the event that may be causing it.

Any more info on this, or others with same problem?

I haven't encountered this issue again, although I have not been making as many configuration changes/restarting hass as in the past.

@andrewfoster Could it be that you have started OZWCP and inconsistently using the same or another zwcfg file? I've messed up my configuration in a similar way, and I believe that was how I managed to do that.

@magicus I've only ever let hass start up ozwcp (I don't start it separately, or have anything else touching the zwcfg file used).
I haven't had this problem happen again since reporting it so don't have anything else to add to the problem report.

Closing this, because this is an issue with the way ozw saves the zwcfg, and something has gone wrong when stopping(the zwcfg gets saved when zwave_stop service is used) and starting the network inside the running ha instance.


Add native OpenDAL support

Add native OpenDAL support for ORC
See also: https://github.com/apache/opendal/issues/4962

It should be super simple to implement an AsyncChunkReader for opendal. I will give it a try.

It should be super simple to implement an AsyncChunkReader for opendal. I will give it a try.

Have fun 🤩

Hi, I didn't find AsyncWrite for orc. Seems we doesn't support write orc files yet?

Hi, I didn't find AsyncWrite for orc. Seems we doesn't support write orc files yet?

Yes, the feature still WIP #11

Thanks! We can add AsyncOpendalWriter once it's ready.

Implemented in https://github.com/datafusion-contrib/datafusion-orc/pull/117


Support resume-able upload via tus

Need help to test more scenarios (e.g. different browsers, different file types, various disconnection scenarios, etc.)
Found and fixed one claim-info error in the backend.  Been comparing the claim output between v1 and v2 endpoints -- looks ok to me now, but I might miss something in the process.
Test
KP test instance
Pre-merge Tasks

[ ] Update env:  increase limit to 10GB
[ ] Update env:  point to v2

Ticket
Closes #38 Handle resumable file upload
Known Issues

[Minor] The upload stalls for ~10s in the backend for every 100MB, then resumes.  Randy is aware.

Working really well! Do we control the 100mb chunks, or does the backend ? Might be good to bring that down if we can get the stalling thing fixed.
Provided some app crashes via Slack. Otherwise should be good to go tomorrow!

We control the chunk size, but lowering it doesn't proportionally lower the stall time at the backend, it seems. I tried something like 1M, 5MB, 10MB, 50MB ... and they all hover around 5s each, which is even worse now because we have more chunks.  Will ask Randy.

238021e to 1eb21a9

Fix file selector for Firefox Android complaining that the file is different.

Android seems to modify the timestamp (on selection?), so just exclude timestamp in the fingerprint for now.

Fix "Suspense" crash

A dormant bug from code-splitting

Improved mobile layout a bit.

1eb21a9 to 55e7bc8

Put back v1 code to support livestream replays.  While at it, also use v1 for:

obsolete browsers (previously, it just stops the upload with an error message)
publish previews (since the payload is small)

Remaining work:

[ ] Backend:  handle race issue with notify
[ ] Re-test (since many things have changed).  Get Miko to help uncover corner-cases?
[ ] Update production env: increase limit to 10GB
[ ] Update production env with addtional v2 endpoint
[ ] Push

Still working out a few kinks from testing. Miko also got a duplicate claim to happen once when refreshed, but hasn't been able to repro.
We may need to auto retry based on certain backend errors if the BE can't do it.

Retry is possible on client side, I think.  I currently just re-try from a "handle intermittent connection problems" standpoint, so only tried 3 attempts in short succession.  Can adjust.
      retryDelays: [0, 3000, 3000],

      onShouldRetry: (err, retryAttempt, options) => {
        window.store.dispatch(doUpdateUploadProgress({ params, status: 'retry' }));
        const FORBIDDEN_ERROR = 403;
        const status = err.originalResponse ? err.originalResponse.getStatus() : 0;
        return status !== FORBIDDEN_ERROR;
      },

Somethings noticed:
Editing publish clears old resumable uploads from pending uploads list.

Start upload, and don't let it finish.
Edit some publish(post or file) and submit upload.
First upload disappears from UI. (If it was active, it was still going in the background, based on network traffic)

Doesn't happen with completely new publishes.

"Success" pop-up doesn't show if upload is retried
For pop-up.

Start uploading a small file
Wait it to finish and you get pop-up

Other scenario for no pop-up

Start uploading a small file
Reload page and retry
Wait it to finish and no pop-up

Thanks @keikari!  The detailed description really made it easy to debug.
ac8ace9 to cc1a5f8

Fixed missing popup.
Fixed pending uploads cleared when editing another claim.


Update google-api from 1.17.0-rc to 1.22.0 to support KMS in other plugins

To use the KMS in conjunction with google-oauth, a newer version of the google-api, especially the google-http-client is needed.
The java client for KMS requires the UriTemplate class to support URIs like "v1/{+parent}/keyRings" correctly, which is not the case in 1.17.0-rc.

Resolved with version 0.5


Discussion: Parts, Assemblies and Instances

Right now we have two kinds of components Parts and Assemblies.
A part is an atomic component and an Assembly is a non-atomic component consisting of Parts and other assemblies.
This is inconvenient since if I am creating a table with four legs I need create four leg parts and enter the same dimensions four times for each leg and create four separate objects that should be identical. Not very DRY and not very memory efficient. This is also true when exporting for example GLTF.
To solve this I would like to introduce "Instances" to cqparts.
The four legs in the table assembly should be four instances of the same part. I want to enter the dimensions and create the part only once. The part should define the dimensions, shape, and color of the part.
The instance of the part should just define the position of each part-instance in the world and a reference to the defining part.
This means that an assembly should always consist of only Instances and not Parts and Assemblies.
Parts are instantiated when added to the assembly and assemblies are also instantiated when added to an assembly.
Some pseudo code for how this could look in practice for a four legged table. The interesting part is in the class Table() make_components() definition
import cadquery
import cqparts
from cqparts.component import Mate

class Leg(cqparts.Part):
    def make(self):
        return cadquery.Workplane('XY').box(1, 1, 10, centered=(True, True, False))

    @property
    def mate_top(self):
        return Mate(self, CoordSystem((0, 0, 10)))

class Tabletop(cqparts.Part):
    def make(self):
        return cadquery.Workplane('XY').box(10, 10, 1, centered=(True, True, False))

    @property
    def mate_leg_0(self):
        return Mate(self, CoordSystem((5, 5, 10)))

    @property
    def mate_leg_1(self):
        return Mate(self, CoordSystem((5, -5, 10)))

    @property
    def mate_leg_2(self):
        return Mate(self, CoordSystem((-5, -5, 10)))

    @property
    def mate_leg_3(self):
        return Mate(self, CoordSystem((-5, 5, 10)))

class Table(cqparts.Assembly):
    def make_components(self):
        # Only create the individual parts once
        leg = Leg()
        tabletop = Tabletop()

        # The components are instances of the parts, this would be the same for sub-assemblies as well
        components = dict()
        components["tabletop_instance_0"] = cqparts.Instance(tabletop)
        components["leg_instance_0"] = cqparts.Instance(leg)
        components["leg_instance_1"] = cqparts.Instance(leg)
        components["leg_instance_2"] = cqparts.Instance(leg)
        components["leg_instance_3"] = cqparts.Instance(leg)

        return components

    def make_constraints(self):
            constraints = [
                Fixed(self.components['tabletop_instance_0'].mate_origin,
                    CoordSystem((0,0,10), (1,0,0), (0,0,1))),
                Coincident(
                    self.components['leg_instance_0'].mate_top,
                    self.components['tabletop_instance_0'].mate_leg_0
                ),
                Coincident(
                    self.components['leg_instance_1'].mate_top,
                    self.components['tabletop_instance_0'].mate_leg_1
                ),
                Coincident(
                    self.components['leg_instance_2'].mate_top,
                    self.components['tabletop_instance_0'].mate_leg_2
                ),
                Coincident(
                    self.components['leg_instance_3'].mate_top,
                    self.components['tabletop_instance_0'].mate_leg_3
                ),
            ]

            return constraints

table = Table()

A sub-assembly is also always instantiated when inserted into an assembly.
Take for example your car-assembly example in the documentation. With this notion of Parts, Assemblies and Instances the structure would look something like this.
Parts

Chassis
Axle
Wheel

Assemblies

Wheel-axle

Wheel-instance-1
Wheel-instance-2
Axle-instance-1

Car

Chassis-instance-1
Wheel-axle-instance-1
Wheel-axle-instance-2

This has the added benefit as well that I can compare two instances to see if they are identical. Identical in this context is "based on the same part".

I'll start with, that table is really nice.
Because we are using python it means that you have a nifty inheritance system and about three ways to do iteration.
So...
Should a table to have to have each leg defined as built ?
Or should it yield an iterator of "LEG" points ?
Each "LEG" point could be attached to a leg, when the model is being built and an unattached leg raises an error.
leg = CoolLeg_7(height=200)
t = Table(radius=700,legs=17,leg=leg)
display(t)
would be a valid
:)

@gntech it's even worse than that... (I probably shouldn't admit that)
Each Part has 2 instances of the same geometry:

local_obj: defined at its origin coordinate system, the coord-system in which it was built, and
world_obj: is the local_obj moved to the part's world_coords

But in the table-legs use-case, this may be a good thing...
Each leg could use the same local_obj, but have its own world_coords and therefore world_obj.
Terminology
To stick with standard CAD terminology, would this feature be called cloning?...
I defer to @dcowden & @jmwright on things like that.
make_alterations
Assembly instances have a make_alterations method, also documented here.
This is where the local_obj geometry of a Part may be changed after it's been built into an Assembly.
If we're talking about cloning, then an alteration to a single part will effect all of their clones.
Does this sound right?

Just brainstorming here...
import cqparts

class PartClone(cqparts.Part):
    def __init__(self, cloned_part, *args, **kwargs):
        self._cloned_part = cloned_part
        return super(cqparts.Part, self).__init__(*args, **kwargs)

    def make(self):
        return self._cloned_part.local_obj

# add to cqparts.Part
class Part(...):
    def clone(self):
        return PartClone(self)

In CAD speak, this kind of thing would be called a pattern.  You would typically make one item, and then provide one reference point on the pattern, and then another liat of pattern points ( typically using a plane and a set if points on a circle, or a rectangular grid)
The expected behavior of a pattern is that they all change to match the original.
Though patterning objects is intuiitive, it's also common to pattern cuts and material removal as well. In that case, the pattern still matches the original, but the geometry produced at each place it us applied can be different.

@dcowden Yes, a pattern can be used to create many instances of a part. But an instance can be created without using patterns. An instance of a part is created the moment you insert a component into an assembly.
@fragmuffin You could call it clone but it is more correct to call it instance, I will try to explain in a Pythonic lingo below.
The part can be thought of as a class. The cadquery model and other defining data should belong solely to the class data. And be shared among all instances as shared class variable.
The placement coordinates in the world or in an subassembly belongs only to a certain instance of the class. Any alterations to a certain instance should also belong to the instance data space.
Right now in cqparts all data is kept in the instances data space. The class is then more of a part factory that creates unique parts every time and not a true definition of the part.
Do you see the difference? :)
Here is my take on this:
import cadquery as cq

class Leg(object):
    # The shared class definition of the parts geometry
    m = cq.Workplane('XY').box(1, 1, 10)
    material = "wood"

    def __init__(self, x, y ,z):
        # The instances coordinate in the world
        self.wx = x
        self.wy = y
        self.wz = z

        self.instance_m = cls.m

    def instance_cut(self, cut):
        # Do an alteration of the instance by cutting something out
        self.instance_m = self.instance_m - cut

# Create two legs with different placement in the world
leg_1 = Leg(0, 0, 0)
leg_2 = Leg(10, 0, 0)

# Do an alteration of one of the legs
leg_2.instance_cut("hole")

Regarding terminology. It differs a bit between CAD systems but functionality wise it is very similar.
I have used Catia V5 and Solidworks extensively at work and Siemens NX at Uni, and I would say that part, assembly and instances is the dominating terminology. (Catia uses Part, Product, Instance)
Component could be both part or assembly

If you want to have two legs with slightly different dimensions you can reuse the first Leg() by subclassing it to class Leg2(Leg) and override the dimensions you want to change.

@fragmuffin @dcowden @jmwright
Sorry for the rambling, but I consider this matter very important to get right
I realize it is a bit hard to explain by text what I mean but I have tried to simplify and refine the code below to show more precisely what I mean without excess stuff. See below, this is a working code example.
import cadquery as cq
import cqparts

# Current cqparts functinonality:
class Leg_cq(cqparts.Part):
    def make(self):
        return cq.Workplane('XY').box(1, 1, 10)

# Create two instances of the leg
leg_cq_1 = Leg_cq()
leg_cq_2 = Leg_cq()

# Check if the local object is the same between two instances
print(leg_cq_1.local_obj is leg_cq_2.local_obj) # False

# New proposed functionality
class Leg(object):
    # The shared class definition of the parts geometry
    m = cq.Workplane('XY').box(1, 1, 10)

leg_1 = Leg()
leg_2 = Leg()

# The model m for the two instances is the same shared object.
print(leg_1.m is leg_2.m) # This is True

This way the object in memory is shared and it is impossible two have two instances of Leg() that have different geometry. This is the desired behavior from an engineering perspective.
Real world example
Say I have a table with 4 legs and a table top. The bill of materials say

1 Tabletop()
4 Leg()

With the current implementation of cqparts parts the 4 Leg() are four different objects in memory and could also have different geometries since it is possible for the make function to use variables that is passed when each instance is created.
It means that to manufacture 4 legs I need to consider each leg as an unique part and create separate drawings for each of them.
With the new proposed functionality I can be certain that every instance of Leg shares the same geometry and I can manufacture 4 identical legs from one set of Leg-drawings.
What should be in the shared class data?
Basically the things that define the part.

The actual cadquery model object in local coordinates
Metadata such as material, Part number, etc.

What should each instance care about?

The placement in the world
Any alterations (cuts) made in the world that affects this instance

Regarding world placement
My impression is that world placement is working correct in cqparts as it is now. It is necessary that each instance has a copy of the local geometry that is transposed to the world coordinates to visualize the instance in the correct place in the world.
Summary
cqparts is very close to what I consider to be the desired behavior. It is just a matter of moving some data and responsibility from instance owned data to shared class data
So, what do you think? :)

@gntech I feel there's a huge aspect of cpqarts that would suffer from an implementation like this.
Part & Assembly as the "blueprint"
This is the core design behind the Part class:

class: a blueprint of a potentially existing geometry, or collection of geometries.
instance:  the blueprint with all parameters defined.
note: a Part instance does not build its geometry when instantiated.
built instance: only when local_obj of a Part is requested is the geometry built. This is also done when explicitly calling the build() method.

And this methodology is similar to the Assembly class, but I feel is not perfect (I digress).
Toy Car Example
This methodology extends to a nested Assembly. Using the toy car as an example of this:
>>> car = Car()
>>> wheel = car.find('front_axle.left_wheel')
>>> wheel.diameter
30.0

After these 3 lines of code, no geometry is built.
However, if you request wheel.local_obj, then the wheel's geometry is made and returned.
The way this works should be detailed in Part build cycle and Assembly build cycle
STEP Import
This also applies to imported components:
>>> obj = cqparts.Part.importer('step')('box.step')

obj is a Part instance, but the box.step file has not been imported, it hasn't even been opened yet.
Only when the geometry is required is the file imported.
See this code to see what I mean.
Why?
Imagine you've built a full complexity CNC machine, with all bearings, motor internals, right down to PCB components.
Perhaps you've built it so the PCB copper tracks are created accurately automatically so you can use a router to cut out the tracks, but the build automatically changes if you move a component's location on the PCB.
Now you want to query that Assembly for the geometry of a single spacer (eg: a cylinder with a hole through it). Or even just the length of that spacer.
With the current design this can be achieved without building the entire model... assuming it's been built without too many hacks.

@gntech However, all of that said...
The Component class (and all classes inheriting from it) could keep a hashed record of all instances of that class. Before an instance is created, it could look for an identical instance and share geometry.
However, that must be broken when the original geometry is "altered", otherwise all similarly sized table legs would get the same holes drilled into them, even if this isn't the design.
But if that is the design intent, then they can be linked as "instances of each other" as you've psudo-coded above.
That's where the PartClone class would fit in... but I would change its name to PartInstance... I imagine AssemblyInstance would be a bit more complex.

When I was first creating cq, I typically thought about how Solidworks operates. Within the last few years, OnShape is another reference.
In these CAD systems, multiple table legs are separate objects in memory.  If they share common geometry, a Feature or assembly feature is used to enforce the commonality, and that feature is what you make a drawing of.
One major weakness in cq 1.x is that there is no feature tree.  There's only the objects in memory. Consequently there is no abtraction to find patterned legs, parts, and assemblies.
Cqparts adds this layer nicely. In my view the abstractions there are correct. It also adds a feature tree of sorts... The tree is defined by the user as the components are assembled.
But it's still always been my plan to add a proper feature tree into cq 2.x.  then, it would be just like solidworks... You build a BOM. By associating part number and such with a Feature in the tree. If you pattern it, you multiply quantity. If you in import an object as step, you associate  for that thing.

@dcowden

You build a BOM. By associating part number and such with a Feature in the tree. If you pattern it, you multiply quantity. If you in import an object as step, you associate for that thing

I've wondered about building a BOM by empowering Component instances to determine their equality to another Component by defining __eq__ and __hash__.
parts_list = my_assemby.get_parts(recurse=True)
bom = {
    part: len(list(group))
    for (part, group) in itertools.groupby(parts_list)
}

You'd probably want to have something a bit smarter than just pulling out all nested parts.
For example: instead of adding 5 assembled bearings, the above code would add 5 inner rings, 5 outer rings, and many balls.
It could be as simple as adding a _bom_leaf boolean to components, Parts:Assemblies default to True:False respectively.
So it's a bit simplistic, but I still think it could work.

Certainly what you proposed would go a long way. In the most general case, I think humans have to define which components are a single item and which are BOM elements, and associate a part number to each.
There are also cases where the bom is totally different from the geometry. For example, large assemblies will often call for fasteners while referring to a hole feature... Yet all holes (even the same size)  may not be the same bom item.
That's why the feature tree approach works so well... I could create 4 holes, knowing they are for 1/4-20 bolts. Then separately I create another 4 identical holes for 1/4-28 bolts (using a different feature), allowing me to keep the bom separate.
I think this maps pretty well to cqparts.. the implication is that you might make another component for the express purpose of tracking the bom.

I am sorry if I am assertive in this matter but I really think this is really a key point for cqparts.
Technical drawings must be unambiguous

This is the core design behind the Part class:
class: a blueprint of a potentially existing geometry, or collection of geometries.
instance: the blueprint with all parameters defined.

I must disagree, a blueprint (technical drawing), is bound to per definition to be unambiguous. If it is not it is invalid.
https://en.wikipedia.org/wiki/Technical_drawing
Imagine handing a blueprint over to the factory and all dimensions are labeled as "this could be 34 or 54 or 67 and this edge could be round or straight" and say "Manufacture 4 of these".
The class right now is more of an ambiguous part template that can be instanced to pretty much anything. Every instance created from the so called "blueprint" could look totally different.
A blue print or technical drawing must yield the exact same result each time.
Lazy evaluation

However, if you request wheel.local_obj, then the wheel's geometry is made and returned.

I understand that it is very nice to be able to evaluate the geometry for each instance as needed, sort of lazy evaluation.
But you could achieve the same on a Part-basis with my idea. That means that All instances of one part would need to be evaluated at the same time. This is exactly as it works in solidworks for example.
Solidworks

In these CAD systems, multiple table legs are separate objects in memory. If they share common geometry, a Feature or assembly feature is used to enforce the commonality, and that feature is what you make a drawing of.

@dcowden As someone who works professionally with 3D-cad everyday I most strongly disagree. Tell me, If you where to model the car assembly in solidworks. Would you start by creating 4 separate solidworks part files for the wheels. Two solidworks part files for the axles and one partfile for the chassis? Then create two axle assemblies and one final car assembly?
If your design intent is that each wheel and axle is unique in someway then perhaps this is a good approach but otherwise it is not. This is pretty much what happens now in cqparts.
Instead in solidworks I would create one part file for the wheel and one for the axle and one for the chassis. Then create a assembly consisting of two wheel instances and on axle instance. Then create one assembly with one instance of the chassis and two instances of the axle assembly. Done
When I for example then change the diameter of the wheel in the wheel part file, and open the assembly again it will pop up a message saying "a part file has changed, do you want to rebuild the assembly?" If I press rebuild the assembly will be updated so that all wheel instances follow the dimension of the single defining part file. All instances are a perfect reproduction of the original unambiguous part file. There is only one source of information for the dimensions of the wheel and all wheels in the assembly are instances of this source.
And this is exactly how I would like cqparts to function.
If classes and instances are used this way than the BOM follows automatically from the structure of the assembly just as it should be.
If it would help I could record a simple screencast of the car assembly model in solidworks describing what I mean.

@gntech

@dcowden As someone who works professionally with 3D-cad everyday I most strongly disagree.

I'm Mechanical Engineer with > 10 years' experience in 3D printing, Unigraphics, Solidworks, Pro-engineer, and Onshape.  I don't use 3D-cad every day any more ( I'm in management), but I do understand the common workflows.  I suspect we both understand Solidworks quite well.
I think perhaps we are not on the same page with the definition of the word 'memory'. Perhaps you thought I meant 'on disc' when I meant 'in RAM memory'?

Instead in solidworks I would create one part file for the wheel and one for the axle and one for the chassis. Then create a assembly consisting of two wheel instances and on axle instance. Then create one assembly with one instance of the chassis and two instances of the axle assembly. Done

Of course, as would I. But when you load that assembly in solidworks and view it on screen, each axle, wheel, and such is a separate object in program memory:  IE, an instance of an object in the underlying  CAD kernel. It HAS to be-- you can't display 4 wheels on screen at different screen coordinates using the same object.  At the time the assembly is rendered, you MOST DEFINITELY have 4 objects in memory, one for each wheel.
CadQuery is a very thin layer above a CAD kernel. When you produce geometry, you produce an object ( as-in-- bytes in emphemeral computer storage).  a python cq script is analogous to a solidworks file. In solidworks, you use a GUI to design your intent-- it stores a file, and then can re-load that and create the geometry.  CQ is the same-- except that you must model your intent directly in program code. CQ's only abstractions to represent assemblies are program code.
CQParts adds abstractions for parts and assemblies. In that case, I would view a CQParts class similarly to a solidworks file-- it is a file stored on disk, which, when executed, produces a 3d object.
Type of thing                   Solidworks                                               cq/cqparts
---------------------------------------------------------------------------------------------------------
a re-usable part              A .sldprt file                                               a cqparts class, in a .py file
an assembly                   A .sldasm file                                            a cqparts assembly, in a py file
a picture on screen        A .sld* file, loaded, and executed,             a .py file, executed,
                                       with cad kernel objects in memory            with cad kernel objects in memory
                                       for every viewable item                             for every viewable item
parametric values          A .sldpart file which uses configuration     a py script that allows user to change
                                                                                parameter values when executing

CQParts and cq itself can collect user input to parameterize an object when it is created.  The solidworks equivalent to this would be a set of configurations

@gntech

And perhaps we must agree to disagree on this one, I will close this issue then :)
I shall not give up on building consensus that easily ! :)  Thanks for engaging in the discussion!

First, let me say I'm not a cqparts expert, and this is very much a cqparts question. However, i still want to understand your view.

Especially if you try to create drawings from your parts. You will see that there is no way of telling that two instances of a part share the same definition and therefore drawing.

This comment helps me to understand your point a bit better.  I think what you're saying is that there's a notion of identity that you'd like to preserve.  In solidworks, if you insert 4 of a part into an assembly, solidworks keeps track of the fact that they are all the same-- in part through the name of the part itself, which can't be changed. So you know that you only need to produce a drawing of 'leg'  once.
Lets consider this cqparts class <wheel.py>:
class Wheel(cqparts.Part):
   def __init__(self):
          #no parameters

Lets also imagine that <bicycle.py> does:
 class Bicycle(cqparts.Part):
    def init(self):
         wheel1 = Wheel()
         wheel2 = Wheel()

The question you're pondering is-- how do i know what parts represent distinct items from a manufacturing viewpoint? Here the answer is 3-- one bicycle and qty two of 'wheel'
But your anticipating this:
 class Bicycle(cqparts.Part):
    def init(self):
         wheel1 = Wheel(15)
         wheel2 = Wheel(14)

Which is an odd bicycle that requires two drawings, one each of wheel1 and wheel2. But there's no way to ask Bicycle the question "how many distinct types of wheels do i have"?
Am i getting your point?

Yes! That is the point! :D
And it could be solved easily if the definition of the wheel is kept in the class data space. And not in the instances data space. That way one definition is shared among all instances of that class.
A true configurable wheel class in the engineering sense would look like this (in my mind at least :))
class Wheel(object):
    # These are class variables that are shared among all instances of this class.
    _axle_hole = 20
    _thickness = 30

    # Lets say the diameter is configurable
    _diameter = dict()
    _diameter["A"] = 500
    _diameter["B"] = 800

    def __init__(self, config="A"):
        if config in self._diameter.keys():
            self.config = config # The chosen config is unique to each instance.
        else:
            raise ValueError # The chosen config is not available for this part.
        self.config = config

    def make(self):
        # This would be where the cadquery model is returned.
        return (self.config, self._axle_hole, self._thickness, self._diameter[self.config])

wheel_1 = Wheel()
wheel_2 = Wheel("B")

print(wheel_1.make())
print(wheel_2.make())

Every instance of this class have a single source of definition.
If we can agree on this, it is a very good starting point. :)

@gntech ok, good, I understand.
I don't think anything within cqparts would preclude writing objects as you describe above.  If you simply used the rule to author every cqparts class and pull configuration from a central point, you'd have a solution that meets your needs.
I'm fairly certain that @fragmuffin would not want to build this way of working into the framework,  however.  The reason is that in exchange for this safety, you lose some flexibility. Let me explain.
In Solidworks, you have the ability only to insert a part or not to insert it.  You can change your configurations, but you cannot change which and how many of a part are inserted due to arbitrarily complex logic. This is the constraint you're seeking to exploit when you generate a BOM.
Since we're building programming libraries, cq and cqparts contemplate the ability to apply arbitrarily complex logic to construct the part/assembly.  This is not a feature available in SW unless you write macros, so it is not commonly used.
Consider, for example, model of a bridge, with some number of supporting struts.  Further, consider that I wish to calculate the number of supports and their sizes not at design time, but based on some other factors, like some function of length/width and load.  This is possible when we have a full-fledged programming environment!
In this case, we intentionally do NOT want to fix the number of supports we want-- we want the code to figure it out and tell us at the end. For example, what if we want a script to generate a bicycle-- but we want to input the rider's height and the max speed the rider will go.  Then, we want the model to calculate the size and quantity of wheels for us?
Since cqpart is new, I dont know of any real world examples where this was used. But  this example is along the same lines-- the geometry of the nozzle is computed based on code that's evaluated at runtime.
In short, though its possible to make cqparts ( and cq) behave in the way you're after, it is limiting because it 'neuters' the power to produce truly arbitrary results based on the inputs-- which could certainly change things like the dimensions of parts or the number required.
I'd like to be able to say that cq and cqparts support both the use cases you have ( tell me the number of parts required ), and also the complex 'generative assembly' type of scenario i've described above.

As another example in keeping with wheel talk, suppose we wish to use cqparts to create a bicycle, but we want to input the rider's age and sex.  From that age, we'll compute two things:

the diamter of the wheels ( from a lookup table)
whether the frame has a horizontal bar
whether we should add training wheels ( for kids < 4 years old )

In this case, the number and configuration of wheels changes based on the inputs.  In keeping with @gntech 's concerns, but also the desire to allow this type of flexibility in the framework, the qquestion becomes:
how does one ask a cqpart or a cqassembly how many of each class were created?  @gntech points out that this is not possible, which seems like a valid criticism.  But I don't like solving it by neutering the framework.
@fragmuffin what are your thoughts?  it does seem like it woudl be cool to be able to query a cqpart or an assembly and ask the question "what parts did you make, and what were the parameters given for each one?

Consider, for example, model of a bridge, with some number of supporting struts. Further, consider that I wish to calculate the number of supports and their sizes not at design time, but based on some other factors, like some function of length/width and load. This is possible when we have a full-fledged programming environment!

Yes, this is awesome, my proposed method does not stop this. Any calculations or logic can be used to create the struts and to instantiate any number of struts. You dont need to have fixed values for lengths as in my example. The length, width, size, etc. could be calculated using extremely complex formulas and using external factors. However the calculation need to happen in the class definition so that the result is shared among all instances of that particular strut.
If you want to have 100 individual struts customized separately then you have would have 100 different struts and not 100 struts of one kind. I am talking about the later, 100 struts of one kind. All those struts should be identical.

As another example in keeping with wheel talk, suppose we wish to use cqparts to create a bicycle, but we want to input the rider's age and sex. From that age, we'll compute two things:

I think what you are referring to is top-down design and that design method is perfectly possible using my proposed method. The top down approach could be implemented by using inheritance. Say that we have a parent Bicycle class that is subclassed to Frame and Wheel and Seatpost classes. The value and the age is saved as instance variable for Bicycle class. And from those value deterministic Frame, Wheel and Seatpost is created and extra wheels added.
Here is an incomplete example
class Bicycle(object):
    # This is an example of top down design
    # The pipe size used for the frame and seatpost is defined in the parent class
    pipe_size = 20

    def __init__(self, gender="F", age="32"):
        self.gender = gender
        self.age = age

        frame = Frame()
        if age < 10:
            wheel_size = "A"
        elif age >= 10:
            wheel_size = "B"
        wheel_1 = Wheel(wheel_size)
        wheel_2 = Wheel(wheel_size)
        if age < 4:
            wheel_3 = Wheel(extra_size)
            wheel_4 = Wheel(extra_size)

class Frame(Bicycle):
    # Pipe size inheritted from Bicycle
    # Special sizes depending on age could be added as configurations.

    def __init__(self):
        # Parameters to the parent could be reached this way
        self.age = super.age
        self.gender = super.gender

        # The frame needs a seatpost
        self.seatpost = SeatPost()

    def make_male_frame(self):
        # The parameter pipe size is inherited from the Bicycle class
        return "male frame" + Frame.pipe_size

    def make_female_frame(self):
        # The parameter pipe size is inherited from the Bicycle class
        return "female frame" + Frame.pipe_size

    def make(self):
        if self.gender == "F":
            return self.make_female_frame()
        else:
            return self.make_male_frame()

class SeatPost(Bicycle):
    # Pipe size inheritted from Bicycle of from Frame
    def make(self):
        return SeatPost.pipe_size # This parameter is inherited from the Bicycle class

class Wheel(Bicycle):
    # These are class variables that are shared among all instances of this class.
    _axle_hole = 20
    _thickness = 30

    # Lets say the diameter is configurable
    _diameter = dict()
    _diameter["A"] = 500
    _diameter["B"] = 800

    def __init__(self, config="A"):
        if config in self._diameter.keys():
            self.config = config # The chosen config is unique to each instance.
        else:
            raise ValueError # The chosen config is not available for this part.
        self.config = config

    def make(self):
        return (self.config, Wheel._axle_hole, Wheel._thickness, Wheel._diameter[self.config]) # This would be where the cadquery model is returned.

bike = Bicycle(gender="M", age=8)

To reiterate, this design method does not prevent any advanced geometry to be created it just brings logic to how instances really are a true representation of the parts they claim to be.
As an added benefit the top-down design approach comes naturally from Pythonic object oriented inheritance.
As an extra added benefit. BOM (bill of materials) comes naturally from just
for components in all_components:
    print(type(component), component.config)

Wow, a lot of activity on this thread!
I'm sorry I'm about 20hrs behind, the time-zone difference doesn't help.

And perhaps we must agree to disagree on this one, I will close this issue then :)

I shall not give up on building consensus that easily ! :) Thanks for engaging in the discussion!

I totally agree :+1:, this is a great discussion! I'm still having trouble with the overall problem.
But that's partly because I think some aspects of cqparts haven't been discussed yet... I'll explain more below.

@dcowden I am sorry for doubting your experience

Well @gntech @dcowden you're both welcome to doubt my CAD experience; I have none :fearful:

a blueprint (technical drawing), is bound to per definition to be unambiguous. If it is not it is invalid.

@gntech I concede to this; my terminology was wrong.
I want go back a bit, I just want to clarify the problem, and suggest ways that they may be solved using existing aspects of cqparts, but also what needs to be improved
Problem: Part Equality & Duplicate Geometry
note: everything here is functional in the current release of cqparts; no psudo-code.
saved here: https://gist.github.com/fragmuffin/8889def56002184ab309e177c6777d3e
Problem: 2 identically build parts do not show equality
class Cube(cqparts.Part):
    size = PositiveFloat(1, doc="cube size")
    def make(self):
        return cadquery.Workplane('XY').box(self.size, self.size, self.size)

c1 = Cube(size=10)
c2 = Cube(size=10)
c3 = Cube(size=20)

eq = lambda a, b: (a == b, a.local_obj == b.local_obj)
print("c1 == c2: %r" % (eq(c1, c2),))
print("c1 == c3: %r" % (eq(c1, c3),))

would currently output
c1 == c2: (False, False)
c1 == c3: (False, False)

I agree that this should be fixed, it's clear that 2 objects were made to be identical.
Each of the Cube instances should initially share the same local_obj, with different world_coords, therefore different world_obj.
Potential Solution

And it could be solved easily if the definition of the wheel is kept in the class data space. And not in the instances data space. That way one definition is shared among all instances of that class.

@gntech that's what I think too... let me know if the following idea is what you're describing, or if I'm twisting what you're saying...
class AwesomePart(cqparts.Part):
    INSTANCES = {}

    def __init__(self, *args, **kwargs):
        super(AwesomePart, self).__init__(*args, **kwargs)
        self._key = tuple((k, v) for (k, v) in sorted(self.params(hidden=False).items()))
        if self._key in self.INSTANCES:
            self.make = lambda: self.INSTANCES[self._key].local_obj
        else:
            self.INSTANCES[self._key] = self

    def __eq__(self, other):
        return (type(self) == type(other)) and (self._key == other._key)

class AwesomeCube(AwesomePart):
    size = PositiveFloat(1, doc="cube size")
    def make(self):
        return cadquery.Workplane('XY').box(self.size, self.size, self.size)

c1 = AwesomeCube(size=10)
c2 = AwesomeCube(size=10)
c3 = AwesomeCube(size=20)

eq = lambda a, b: (a == b, a.local_obj == b.local_obj)
print("c1 == c2: %r" % (eq(c1, c2),))
print("c1 == c3: %r" % (eq(c1, c3),))

outputs:
c1 == c2: (True, True)
c1 == c3: (False, False)

So that's quite crudely done, and would break very easily... but that's the way I was thinking of solving the issue; to keep a hashed reference to all parts made in the current Python environment to serve as a lookup for identical instances.
The intent here would be to put the AwesomePart functionality into the core Part class to implicitly apply the same behaviour to all parts
Problem when Altering
One problem with this approach is each Part with identical parameters can share geometry to being with. But when one of them is altered c1.local_obj = c1.local_obj.faces('<Z').hole(1) then c1's geometry must be duplicated and separated from the borg (star trek joke there :space_invader:  ).
I also think this should be an optional feature, to instantiate a Part that's intentionally linked to another so that any change to it also effects the others.
But that behaviour should be explicit. That was the idea behind PartClone above.
Catalogues?
I noticed that nobody has mentioned the cqparts.catalogue module, @gntech your example of setting wheels based on their config as A or B would fit perfectly into a catalogue.

I'm fairly certain that @fragmuffin would not want to build this way of working into the framework, however. The reason is that in exchange for this safety, you lose some flexibility.

You're right @dcowden ; applying limits to parts is what I made the cqparts.catalogue module for.
I originally made catalogues with fasteners in mind; when building something in a purely mathematical mindset, it's easy to come up with a screw that's 14.572mm long... but nobody in the world sells a screw that's exactly that length, why would they?
This is explored in the 2nd iteration of the easy-install fastener.
Serializing
A strong part of cataloguing is in serializing components.
Any part can currently be turned into a dict, then back into a Component with:
car_data = car.serialize()
car2 = cqparts.Component.deserialize(car_data)

BOM

@fragmuffin what are your thoughts? it does seem like it woudl be cool to be able to query a cqpart or an assembly and ask the question "what parts did you make, and what were the parameters given for each one?

You can already do this... there's definitely room for improvement, but it's there
>>> car = Car()
>>> car.components.keys()
['front_axle', 'chassis', 'rear_axle']
>>> {name: obj.params(hidden=False) for (name, obj) in car.components.items()}
{'front_axle': {'right_diam': 30.0, 'left_width': 10.0, 'wheel_clearance': 3.0, 'right_width': 10.0, 'axle_diam': 10.0, 'axle_track': 60.0, 'left_diam': 30.0}, 'chassis': {'width': 60.0}, 'rear_axle': {'right_diam': 30.0, 'left_width': 10.0, 'wheel_clearance': 3.0, 'right_width': 10.0, 'axle_diam': 10.0, 'axle_track': 60.0, 'left_diam': 30.0}}

@gntech @dcowden am I presenting any new value here?, I'm a bit lost in the discussion above.
There's definitely a strong feeling of core optimising & functionality that's missing, but I'm a little confused because I think the above examples show that a lot of the features already exist, they're just not yet utilised to implicitly define mature Part features.
I feel I should also point out, I still strongly consider this library in alpha
Tasks
At this point, this is what I would change based on this discussion...
(I would split these into a few separate issues)

implicit Part instances:

[ ] share local_obj geometry for identical Part instances
[ ] copy local_obj to python instance before altering
[ ] update gltf export to only create geometry once for multiple instances

[ ] implement robust Part comparison (__eq__ & __ne__)
BOM features

[ ] define BOM flag for components
[ ] simplify method of extracting hierarchy of BOM components from an Assembly

Please feel free to disagree! I'm totally expecting that I've missed the point in some ways :stuck_out_tongue_winking_eye:

@fragmuffin

@gntech @dcowden am I presenting any new value here?, I'm a bit lost in the discussion above.
There's definitely a strong feeling of core optimising & functionality that's missing, but I'm a little confused because I think the above examples show that a lot of the features already exist, they're just not yet utilised to implicitly define mature Part features.

I didnt know about the ability to query the list of components. That serves the need to build a BOM from my viewpoint. With that in place, I feel like cqparts can handle the needs posed.

@fragmuffin Thanks for your hard work with cqparts! I think the solution with keeping a list of instances in class definition is good.

I didnt know about the ability to query the list of components. That serves the need to build a BOM from my viewpoint. With that in place, I feel like cqparts can handle the needs posed.

Yes, the BOM solution looks good! :)

Problem when Altering
One problem with this approach is each Part with identical parameters can share geometry to being with. But when one of them is altered c1.local_obj = c1.local_obj.faces('<Z').hole(1) then c1's geometry must be duplicated and separated from the borg (star trek joke there space_invader ).

Actually, instances of the same part should ALWAYS share the local_obj :) Let me explain:
Consider the following: "Make alterations" should be split into two separate cases Make part alteration and make instance alteration
Make part alteration (part feature with external reference)
An alteration with some external reference from the assembly that affects the local_obj on part level - That is it affects all instances. This is the same as a part feature that has external references in solidworks. A great example is the cutout in the top of the legs for my coffee table. This cutout is based on the location and thickness of the glass top but affects the leg on the part level so that all legs get the same cutout. And when I query the Leg.export_drawings() the cutout is visible on the drawing. When I manufacture the leg I do the cutout right there in the manufacturing step. Not in the assembly step. I just used external reference from the assembly to get the right shape for the cutout. @fragmuffin Your car chassis alteration is in fact an example where this makes the most sense.
Make instance alteration (modify instances in an assembly)
This is an alteration of a certain instance in an assembly. The equivalent of an assembly feature in Solidworks. This alteration affects only an instance world_obj and is only visible in the context of the assembly. A good example of this is precision assembly of for example an air frame. Two parts are joined together with rivets but there is only a pilot hole in one of the parts when the parts are manufactured. The parts are aligned in an assembly jig and the pilot hole is used to drill a bigger hole through both parts. The holes could not be drilled on the part level because at assembly they would never align due to manufacturing and assembly tolerance. As you would imagine, those holes are not visible on the local_objects. But they are visible on the world_obj.
Assembly.export_drawing() will show the holes on the parts in context with the assembly. You could also do Instance.export_drawing() to get drawings of that particular instance with its alterations (world_obj).
Do you see the distinction I am trying to make.

Instances of the same part will always share the same local_obj.
The world_obj is always unique and is translated and perhaps modified. The modification on world_obj always go on top the local_obj.

This is not an opinion really, it is by definition. The definition of two instances being instances is that they share the same local_obj :)
You see, in the case where you feel you need to separate local_obj since you want to modify one of them. Then they are not instances of the same part anymore. And that is okay but they should then be considered two different part instead. Which is totally okay!
Just to be clear, there is nothing wrong with constructing assemblies out of individual parts - parts that have just one instance each in the assembly.
BUT, since part reuse is VERY beneficial, admirable and common in manufacturing I want to make certain that when we work with several instances of a single part the logic between them is right.

Thanks for your hard work with cqparts

My pleasure. I'm sorry I've been a bit absent / slow-to-respond lately, I've started a new job and it's been challenging in a few ways, but I'm still very passionate about cadquery and cqparts.
@gntech ok, I think I really like what you've suggested... I'm just trying to wrap my head around it.
There's a beautiful simplicity to it, and I love that.
Now, please bare with me; because you have a lot of experience with mature CAD systems, I feel I'm slowly catching up.
I believe the following are examples may be what you're describing, but I think there's a bit of compromise in there too...
Implicit cqpart.Part behaviour
>>> c1 = Cube(size=10)
>>> c2 = Cube(size=10)
>>> c1.local_obj is c2.local_obj
True
>>> c1.local_obj = c1.local_obj.faces('>Z').hole(1)
>>> c1.local_obj is c2.local_obj
False

The implicit behaviour of a cqparts.Part instance would reference the same geometry unless one was modified.

Actually, instances of the same part should ALWAYS share the local_obj :)

I strongly disagree, but I think this is simply semantics:

CAD - I think you speak of an instance in CAD, in which case I trust you're absolutely right.
OOP - An instance in object-orientated programming is different; a change to one object's instance must be independent of another.

>>> c = Cube(size=10)
>>> c.world_coords = CoordSystem(origin=(1,2,3))
>>> c.world_obj = c.world_obj.faces('>Z').hole(2)
>>> display(c.local_obj)  # a cube
>>> display(c.world_obj)  # a cube with a 2mm hole

The world_obj takes its form from local_obj (translated to 1,2,3), but the geometry is not copied back to local_obj when changed.
However...
>>> c = Cube(size=10)
>>> c.world_coords = CoordSystem(origin=(1,2,3))
>>> c.world_obj = c.world_obj.faces('>Z').hole(2)
>>> c.local_obj = c.local_obj.faces('>Z').hole(1)  # exception? (see below)
>>> display(c.local_obj)  # a cube with a 1mm hole
>>> display(c.world_obj)  # a cube with a 1mm hole

If the local_obj is modified, the world_obj must be re-defined based on that change.
In the above example, the 2mm hole modification is lost.
This would just have to be documented to explain why to finalise the local_obj before modifying the world_obj.
Actually... I can raise an exception if it's done in the wrong order to force compliance.
Explicit cqpart.Part instance modifier
>>> c1 = Cube(size=10)
>>> c2 = c1.instance()
>>> c1.local_obj is c2.local_obj
True
>>> c1.local_obj = c1.local_obj.faces('>Z').hole(1)
>>> # OR
>>> #c2.local_obj = c2.local_obj.faces('>Z').hole(1)
>>> c1.local_obj is c2.local_obj
True

c2 is explicitly defined as a CAD instance of c1, allowing them to share geometry no matter how it's altered, or which object is altered (c1 or c2)
>>> c1 = Cube(size=10)
>>> c2 = c1.instance()
>>> c1.world_coords = CoordSystem(origin=(10,0,0))
>>> c2.world_coords = CoordSystem(origin=(-10,0,0))
>>> c1.world_obj = c1.world_obj.faces('>X').hole(1)
>>> c2.world_obj = c2.world_obj.faces('>Y').hole(1)
>>> c1.local_obj is c2.local_obj
True
>>> c1.world_obj is c2.world_obj  # would never be True
False
>>> display(c1.local_obj)  # cube
>>> display(c2.local_obj)  # cube
>>> display(c1.world_obj)  # cube with hole along X axis
>>> display(c2.world_obj)  # cube with hole along Y axis

This alteration affects only an instance world_obj and is only visible in the context of the assembly. A good example of this is precision assembly of for example an air frame. Two parts are joined together with rivets but there is only a pilot hole in one of the parts when the parts are manufactured

This sounds a little like the code I wrote for easy-install Fastener, look at the implementation of EasyInstallFastener.Applicator.apply_alterations(). There's a lot of mess there to move parts back from their world_coords back to a geometry's local coordinates, just so I can perform some cuts.
But if I resign the idea of local_obj and world_obj being the same geometry in a different place, then I can just effect the world_obj directly.
@gntech is this slowly becoming what you'd expect to see?
It's starting to look a lot more elegant than the current cqparts release.
I really appreciate the work you're putting in to explain your point of view :heart:

>>> c1 = Cube(size=10)
>>> c2 = c1.instance()
>>> c1.local_obj is c2.local_obj True

Actually, this is something that is called Draft Clone in Freecad.
Just adding my 2c here as I see that using the same names for things seems to be the difficult part here. It took me a really long time to find it since I had no idea about a possible name of this operation.

just to add my $0.02 here.-- for the purposes of this comment:
(a) i when i say 'instance' i mean 'a pythonOCC object in memory at a particular address'
(b) this is only tangentially related to the above discussion.
When I first wrote cq, i worked REALLY hard to make cq as intituite as possible from the viewpoint of a person experienced with CAD. among other things, this produced the decision that all references to an object that is modifed will subsequently point to the modified object. For example:
wp = Workplane('XY')
c = wp.circle(10).extrude();
d = c.faces(">Z").circle(2).cutThruAll()

In this snippet, d points to an object that is a tube.  But what does 'c' point to?
In cq 1.x, both c and d point to the same object. The idea was that most you nearly never actually WANT to use an older, unmodified version of a solid in a later step ( and in fact in solidworks you have to try really hard to do so, because the feature tree is linear).
In conversations with the community ( including @fragmuffin  ) , i subsequently realized this was a HUGE mistake. Not only does it complicate the code, but it generates tons of non-intuitive behavior? Why does c point to this after this but not that, etc.  In short, I learned that its important that the library do what PROGAMMERS expect, when you are writing a library, regardless of what CAD users expect.
In the above code, programmers expect that c points to a cylinder, and that d points to a tube-- and that if i start using c, i'll do nothing to d, but restart my steps with c.
I point this out because the conversation above is extremely  nuanced. 99% of all users wont explore the problem to nearly this depth.  After tricking myself it writing really complex code, it turned out that the simple 'principle of least surprise' was the best way to drive the design.
Based on how hard the terminology is to get right, and the level of detail of these discussions, i think there's a risk that the solution might end up violating the principle of least surpise. For example:
c = Cube(size=1)
d  = Cube(size=1)

I'f i'm following right, there's an argument that these should point to the same actual object instance. I think this is a mistake. @fragmuffin points out that after something is done to 'c', it would necessarily HAVE to be a separate object instance, because it is not the same thing.  But why have it start out the same, and generate all kinds of questions from programmers about the magicaly method in which (after the above code) c and d are actually the same? It really just isnt what anyone expects.
If i run the above code, and i render it on the screen, i expect to have two cubes, laying on top of each other, not one.  There may be an explanation for why it should be one not two, but it will mystify most users and will not be consistent with what most users will expect.
And i've learned my lesson on that!

I'f i'm following right, there's an argument that these should point to the same actual object instance. I think this is a mistake.

@dcowden Yes I have realized this now as well. I think my error was that I tried to map CAD terminology to OOP terminology 1:1 in a way that didnt work. Sorry for the confusion.
@fragmuffin have presented a solution to my issues above that I find very good.
Thanks for the discussion and for everybody keeping a good tone and good spirit through out ! Much appreciated and I look forward to the future of cadquery / cqparts eco system :+1:

Thanks for the discussion and for everybody keeping a good tone and good spirit through out ! Much appreciated and I look forward to the future of cadquery / cqparts eco system 👍

@gntech no worries at all! This is a REALLY hard problem-- and what's exciting is that this is probably literally the only place in the world people are trying to solve it.
To put some perspective on that, I followed pythonOCC closely for many years, and they frequently talked about a 'high level api' that makes it easier to script the kernel.  It never really materialized, not due to lack of trying. cq was my attempt, but its not perfect either.
Do you know about Onshape? It was started by some of the original founders of Solidworks.  They support scripted operations as a first-class feature, called "FeatureScript". I've written several popular modules for Onshape using that framework.
I mention that because they also struggled, and still struggle, with these topics. Their programming framework is extremely hard to learn.  They have created a number of abstractions that try to present these problems in a sane way. They do some things that are really cool ( and that i want CQ to do eventually-- for example, to run queries that find which faces were modified  on a generated solid).  They do some things that we're contemplating ( like modeling operations as first-class objects).  But they do a several other weird things that are a complete mystery.
Experience with trying to learn Onshape's framework has taught me several lessons in the importance of clarity as well.

@dcowden :

I'f i'm following right, there's an argument that these should point to the same actual object instance. I think this is a mistake

@gntech :

I really like what you describe in the Explicit cqparts.Part instance modifier! That is exactly how I would want it to work :)
...
@fragmuffin have presented a solution to my issues above that I find very good.

I've had more of a think over this, and I think I'm with @dcowden on the implicit behaviour.
What do we think about this behaviour (replacing the first code example in https://github.com/fragmuffin/cqparts/issues/90#issuecomment-391333698)
>>> c1 = Cube(size=10)
>>> c2 = Cube(size=10)
>>> c1.local_obj is c2.local_obj
False
>>> c1 == c2
True
>>> hash(c1) == hash(c2)
True
>>> c1.local_obj = c1.local_obj.faces('>Z').hole(1)
>>> c1 == c2
False

Enabling smart equality, and hashing would allow Part instances to be added to a set() or as a key in a dict().
Changing local_obj would set a flag so equality cannot be reached (unless the other object is a CAD instance)
@gntech I like the explicit instance behaviour as well; so those examples would be the same.
If we can all agree on https://github.com/fragmuffin/cqparts/issues/90#issuecomment-391333698 with the above alteration, then I'll start creating test-cases to drive development.

Oh wait!, we can't have:
>>> c1 == c2
True

Because they'll have different world_coords; implying 2 physically separate objects are equal is not intuitive.
I need to think some more :thinking:

Maybe the geometry/shape and its position need to be separated somehow?

I think we need this:
>>> c1 = Cube(size=10)
>>> c2 = Cube(size=10)
>>> c1.local_obj is c2.local_obj
False
>>> c1.local_obj == c2.local_obj
True
>>> hash(c1.local_obj) == hash(c2.local_obj)
True

Then to find how many parts of a certain kind should be manufactured one just needs to create a set of all local_obj in the top assembly (finding distinct shapes).
If local_obj would be immutable it could behave like string in python and I think it would not surprise people:
>>> a = "hello"
>>> b = "hello"
>>> a is b
True
>>> c = [1]
>>> b = [1]
>>> c is b
False
>>>

But that might be a premature optimization (?).

Actually, I'm coming back around to:
>>> c1 = Cube(size=10)
>>> c2 = Cube(size=10)
>>> c1 == c2
True

Each Part (c1 and c2) represent both their local geometry, and the world geometry simultaneously.
When testing the condition c1 == c2, the author wants to test the equality of one of:

world geometry, or
local geometry

However 2 parts should never be identical, and in the same location, so from a world context, this comparison should always be False.
But I can imagine testing the equality of local geometry would be done routinely. BOM for example.
OK! enough talk! I know what to do now...
These nuances can be more effectively sorted out in a PR anyway, so I'll invite you all to review when it's done.

@here make sure to think through what happens in this case, which is what really threw me for loops in core cq:
c1 = Cube(size=10)
c2 = Cube(size=10)
d = c1
c3 = c1.cut_a_hole_out_of_it() #pseudo code for any code that ends up modifying the object from c1

ok, now for the interesting questions!
what do c3, d, and c1 all point to?
in cq, c1 == c3, which is a cube with a hole,  which people generally found counter intiuitive
so i think based on that lessson,
d == c1 == a cube (still)
c3 = a new object that's a cube with a hole

I couldn't have said it better myself @zwn :+1:
>>> c1 = Cube(size=10)
>>> c2 = Cube(size=10)

>>> d = c1
>>> d is c1
True

>>> c3 = c1.cut_a_hole_out_of_it()
>>> c3 is None
True

I think that's what most programmers would expect to happen.
Another improvement to cqparts would be to get the following happening:
>>> d = c1.copy(instance=False)
# equivalent to
>>> from copy import copy
>>> d = copy(c1)
>>> d is c1
False
>>> d == c1
True

see #96
so you could explicitly control what happens to c3
>>> c3 = copy(c1).cut_a_hole_out_of_it()  # c3 = holey cube, c3 = cube
>>> # or, reversed
>>> c3 = copy(c1)  # c3 = cube
>>> c1.cut_a_hole_out_of_it()  # c1 = holey cube

reminder : update part build cycle docs as part of change

OK, I think this discussion has come to a close. I've created #97 to track the changes, mainly because this is a huge thread, with a lot of back-and-forth discussion, which results in ~50% misinformation :wink:
Thank you so much to everyone that's participated: @gntech @zignig @dcowden @zwn
I'll ping you all when I've made a PR

Just a small note on the naming. SketchUp allows you to select a geometry and call "Make Component" on it. When you later change the component, all copies of it are updated. You can select one copy of the component and call "Make Unique" on it which will do the obvious thing. So, all copies of SketchUp Components are by default shallow (share the geometry). A shallow copy can be "upgraded" in place to deep copy by "making it unique".

@zwn yep, that's a very intuitive flow.  It works the same way as linked styles do in Google Docs/MS Word.

I'd like to make a table of all these terms at some point.
It makes transitioning from one language/CAD-tool to another much easier.


change in reading camera geometries from simtealarray files

Note: this breaks some of the examples, since class CameraGeometry has now an additional argument (camera rotation angle).  Only read_hessio_single_tel.py  works properly with these changes. Note however the calibration that is currently applied is faulty (unrelated to these changes) and images will sometimes look weird.
The changes are the following:
Now focal length is used to distinguish cameras with the same number of pixels (instead of pixel separation)
Got rid of assumption that pixels 0 and 1 are contiguous in the calculation of pixel spacing
Introduced an overall camera rotation to bring all cameras to a common system of coordinates - actually, though the cameras are no longer "rotated", images are not yet consistent among telescopes in the same location

the problem with the calibration is in pyhessio, so that should be fixed soon (the  data are interleaved incorrectly).
For the examples, we may just decide to remove the read_hessio example (the one with all telescopes), since that is not really a supported mode. Or we can fix it.
Note that this should also be added to the instrument module, since CameraGeometry will be deprecated soon and replaced by the equivalent in instrument.
Let me know when this is ready to be merged.

Could you confirm that calibration error  in pyhessio is result of get_calibration function ?
I have just checked and I did not find where low gain data and high gain data  are interlaced.


Ecr lambda permissions

lots of changes when I run terraform plan on intg not associated with is branch
Plan: 4 to add, 43 to change, 4 to destroy.
are they ok to ignore
including
module.cloudfront_certificate.aws_acm_certificate.cert
module.consignment_api_certificate.aws_acm_certificate.cert
module.frontend.aws_elasticache_replication_group.redis_replication_group will be updated in-place

... lots of changes when I run terraform plan on intg not associated with is branch...

@ian-hoyle This should be resolved now. This happens when a newer version of Terraform is used for plan or apply operations.


gitea: 1.7.6 -> 1.8.0

Motivation for this change
Changelog: https://github.com/go-gitea/gitea/releases/tag/v1.8.0
Things done

[X] Tested using sandboxing (nix.useSandbox on NixOS, or option sandbox in nix.conf on non-NixOS)
Built on platform(s)

[X] NixOS
[ ] macOS
[ ] other Linux distributions

[ ] Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
[X] Tested compilation of all pkgs that depend on this change using nix-shell -p nix-review --run "nix-review wip"
[ ] Tested execution of all binary files (usually in ./result/bin/)
[X] Determined the impact on package closure size (by running nix path-info -S before and after)
[X] Assured whether relevant documentation is up to date
[X] Fits CONTRIBUTING.md.

@GrahamcOfBorg build gitea nixosTests.gitea

should this be backported as there are security fixes ?

@nyanloutre Hard to say since gitea tend to sometimes break things in minor versions... But they also abandon minor versions quite fast...

I think this breaks with databases other than sqlite right now, but I'm not 100% sure.
gitea[22208]: panic: default addr for network '/var/lib/gitea/data' unknown
gitea[22208]: goroutine 1 [running]:
gitea[22208]: code.gitea.io/gitea/vendor/github.com/go-macaron/session.Sessioner(0xc000607560, 0x1, 0x1, 0x8, 0x9)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/vendor/github.com/go-macaron/session/session.go:155 +0x23e
gitea[22208]: code.gitea.io/gitea/routers/routes.NewMacaron(0xc0002272c0)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/routers/routes/routes.go:142 +0xafe
gitea[22208]: code.gitea.io/gitea/cmd.runWeb(0xc0002272c0, 0x0, 0x0)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/cmd/web.go:125 +0xae
gitea[22208]: code.gitea.io/gitea/vendor/github.com/urfave/cli.HandleAction(0x14af380, 0x172d400, 0xc0002272c0, 0xc000602300, 0x0)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/vendor/github.com/urfave/cli/app.go:471 +0xad
gitea[22208]: code.gitea.io/gitea/vendor/github.com/urfave/cli.Command.Run(0x16c0a60, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x16e3ed7, 0x16, 0x0, ...)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/vendor/github.com/urfave/cli/command.go:191 +0x928
gitea[22208]: code.gitea.io/gitea/vendor/github.com/urfave/cli.(*App).Run(0xc000608000, 0xc00000e060, 0x2, 0x2, 0x0, 0x0)
gitea[22208]:     /build/go/src/code.gitea.io/gitea/vendor/github.com/urfave/cli/app.go:241 +0x658
gitea[22208]: main.main()
gitea[22208]:     /build/go/src/code.gitea.io/gitea/main.go:57 +0x426
systemd[1]: gitea.service: Main process exited, code=exited, status=2/INVALIDARGUMENT
systemd[1]: gitea.service: Failed with result 'exit-code'.

The module should probably be changed to not specify [database] → PATH = …, when the database type is not sqlite.

@ajs124 As far as I know, the path setting is simply ignored for everything but sqlite. Notice also that the tests for gitea in NixOS are run for mysql, postgresql and sqlite (and succeeded with this PR.)
This error looks fishy, though: gitea[22208]: panic: default addr for network '/var/lib/gitea/data' unknown

I think this breaks with databases other than sqlite right now, but I'm not 100% sure.

gogs and gitea have always had the promise that "it's just a binary that you run and you don't have to care". Both for running and upgrading. Hence they kinda promise that they will migrate the database as well, otherwise the upgrades wouldn't be smooth as they promise.
I know that we once did an upgrade of gitea in a backport, and one user got upset because it broke because of locales that we copied to the statedir, so with the upgrades the user had the wrong locales. This was fixed in a098cc98d9da60692cb41d5f3f417708de00a9e4 so we put the symlink to the currently running versions locales in the statedir. So the locales won't ever break again on upgrades.
So I think it would be smooth to backport this.

I support a backport as @etu does. It might be worth enabling the dump option by default given how frequently important updates occur. This might save some grief for anyone misguided enough to run gitea without proper backups (what was I thinking?!)...

It might be worth enabling the dump option by default given how frequently important updates occur.

That change would not be backported though... :-)


Search results are not sorted

I am using jekyll-lunr-js-search on my Jekyll test site:
blog.hazenet.dk
But the search results are not sorted in any meaningful way.
I have changed the "displaydate" return format in the search.min.js file.
If I search for update, the results are:
03 April 2014 · You're up and running!
03 March 2014 · Hello What Ever 2014
03 March 2015 · You good to go
03 March 2013 · Hello What Ever 2013
03 March 2015 · Hello What Ever 2015
The same issue is also seen on the "official" demo site:
10consulting.com/search
I would actually like to sort on the "pubdate" string, but display the "displaydate" string.

Agreed that some support for sorting would be nice!

Lunr orders search results by their relevance:

Results will be returned sorted by their score, the most relevant results will be returned first.
-- Lunr

I'm happy to accept a pull request to provide an override to order results to your preference.

Ok thanks, I figured it was something like that. I wonder if there's a way to tweak the settings so that it excludes certain elements from scoring. For instance, if you wanted to match based on title only or something.
It's just a little strange that results like this
coeurdecode.com/search?q=unity show
show up in a strange order.
I also wonder if it's not sorting by date as a secondary when two scores are tied.


Refactoring: don’t repeat button definitions, use the centrally defined ones

The hostname panel used to repeat the button definition of the success button for no good reason. Also, btn-danger should live in button.css.

LGTM, thanks!


Backport "Fix a REPL bad symbolic reference" to LTS

Backports #19786 to the LTS branch.
PR submitted by the release tooling.
[skip ci]

Backport temporarily suspended as it causes a new regressions in the OpenCB


chore: [sc-323673] Update multiple key dependency versions, address most build warnings

Context
A consumer of this library is encountering the following error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
 at [Source: (String)""; line: 1, column: 0]
    at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:59)
    at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:4821)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4723)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3677)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3660)
    at tech.figure.classification.asset.util.extensions.TxResponseExtensionsKt.toProvenanceTxEventsAc(TxResponseExtensions.kt:22)

This seems like a deserialization issue caused by outdated models. Attempting to fix it by bumping several key dependency versions that are sorely outdated.
Changes

Bump version of several Provenance-related dependencies to latest
Address most of the project's build warnings

Superseded by #140


add missing compilation of C files

without these extra files, symbols are missing when mander gets imported.

Looks good, thanks!


[simtest] create TempDir with nondeterministic randomness

Description
In simtests, calling tempfile::TempDir::new() will produce deterministic instead of random results. So potentially multiple tests may use the same "tempdir" leading to test failures. This PR tries to find tempfile::TempDir::new() callsites that can run in simtests, and wrap them in nondeterministic!() macro to ensure uniqueness of results.
Test Plan
CI

If your changes are not user-facing and do not break anything, you can skip the following section. Otherwise, please briefly describe what has changed under the Release Notes section.
Type of Change (Check all that apply)

[ ] protocol change
[ ] user-visible impact
[ ] breaking change for a client SDKs
[ ] breaking change for FNs (FN binary must upgrade)
[ ] breaking change for validators or node operators (must upgrade binaries)
[ ] breaking change for on-chain data layout
[ ] necessitate either a data wipe or data migration

Release notes

Wow, good catch. I previously observed that TempDir::new() always returned different values, and so I thought this wasn't necessary. However, after looking at the code, this turns out to be because TempDir uses a static RNG, so it will not repeat its output in a single process. But you are right that two processes running at the same time can collide.
Makes me wonder how anything worked!

Wow, good catch. I previously observed that TempDir::new() always returned different values, and so I thought this wasn't necessary. However, after looking at the code, this turns out to be because TempDir uses a static RNG, so it will not repeat its output in a single process. But you are right that two processes running at the same time can collide.
Makes me wonder how anything worked!

Yeah I don't know how test runners are setup, but maybe tests with conflicting directories were a minority and not running in parallel most of the time. But I'm not 100% sure if that is indeed the case or these changes can fix the issue. If not, I hope there is a way to "turn on logging" for rm so we can figure out the root cause.


client side pagination is buggy when enabling vertical scrolling

I'm submitting a ...  (check one with "x")
[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here

Current behavior
cannot scroll or jump(with the last page button in footer) to last page when enabling vertical scrolling and client side pagination
Expected behavior
correctly scroll to last page
Reproduction of the problem
https://plnkr.co/edit/imR3FcbgeRyUp0krAxsi?p=preview
What is the motivation / use case for changing the behavior?
user is able to use both the pagination and scrolling to view all rows
Please tell us about your environment:

Table version: 0.7.x
latest
Angular version: 2.0.x
latest
Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
chrome latest
Language: [all | TypeScript X.X | ES6/7 | ES5]
typescript

@collapsarzhang  Based on the plunkr, I can see that the jump to the last page already happens.   I think the number of pages shown in the paginator is one more than what it should have.   Is this the behavior you are observing?

@java2kus if you click on last page then second last page directly, you can see they are on different row position. but if you scroll to bottom or click on last page button  directly, then the pagination indication will not go to the last one(in this case 1667), but stops at second last one(1666)

I think the last page is 1666 only and not 1667.   Having said that investigation needs to be done on why an extra page number is shown here.

@java2kus ok, I see what you meant here. also, in my plunker, you can also see the same issue from https://github.com/swimlane/angular2-data-table/issues/223

Closing due to inactivity and age. If its still an issue, please reopen.

This doesn't work. As soon as I use vertical scroll on client side, the pagination is gone. The page count is still there, but for example, if I click 2 it scrolls 20% below, not goes to the second page.


Exclude nans from normalized dispersion vector

nans accumulate in the beginning of dispersion_norm vector and lead to an overestimated dispersion cutoff, if there are zero-expression genes.
Fixes #115.

Thank you!


Update clarityRef.json to latest from stacks-blockchain

This updates the clarityRef.json file from blockstack/stacks-blockchain@8054315
Grabbed via:
docker run -it blockstack/stacks-blockchain:latest blockstack-core docgen > clarityRef.json

LGTM :) Thanks @kantai !


feat(DEV-11776): [Demo app] assorted fixes

This is a PR for multiple fixes for our auth demo app.

Discussed with @nekedos lack of e2e tests for the sdk-react package and agreed to implement some after 15.08.2024. A test for DataGrid rendering and scrolling should be done.


Combining Stack-based and Tree-based .navigationDestination navigation leads to infinite loop

Description
Using NavigationStackStore, with a destination view then presenting a third view using .navigationDestination(store:destination). When the third view is presented, SwiftUI goes into an infinite loop, consuming 100% CPU and increasing amount of memory.
Checklist

[X] I have determined whether this bug is also reproducible in a vanilla SwiftUI project.
[X] If possible, I've reproduced the issue using the main branch of this package.
[X] This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior
The third view is pushed onto the navigation stack
Actual behavior
The app hangs with 100% CPU usage
Steps to reproduce
I've made a sample project that demonstrates the bug, along with demonstrations of the same functionality working correctly in plain SwiftUI. The TCA version is shown below.
In the sample project there are 3 tabs:

Vanilla SwiftUI - using an array-based NavigationStack path
VanillaStack - TCA, using an array-based NavigationStack path. Note: I had to extract NavigationDestinationView to stop this happening in this implementation
TCA - TCA, using NavigationStackStore

struct ContentFeature: ReducerProtocol {
    struct State {
      var path = StackState<Path.State>()
    }
    public enum Action {
      case path(StackAction<Path.State, Path.Action>)
    }

    var body: some ReducerProtocolOf<Self> {
      Reduce { state, action in
        switch action {
        case .path:
          return .none
        }
      }
      .forEach(\.path, action: /Action.path) {
        Path()
      }
    }

    public struct Path: ReducerProtocol {
      public enum State {
        case int(IntFeature.State)
      }
      public enum Action {
        case int(IntFeature.Action)
      }
      public var body: some ReducerProtocolOf<Self> {
        Scope(state: /State.int, action: /Action.int) {
          IntFeature()
        }
      }
    }

  }

  struct ContentView: View {

    let store: StoreOf<ContentFeature>

    var body: some View {
      NavigationStackStore(store.scope(state: \.path, action: { .path($0) })) {
        VStack {
          NavigationLink("Nav link to 1", state: ContentFeature.Path.State.int(.init(num: 1)))
        }
        .padding()
      } destination: { store in
        DestinationSwitchView(store: store)
      }
    }
  }

  struct DestinationSwitchView: View {
    let store: StoreOf<ContentFeature.Path>
    var body: some View {
      SwitchStore(store) { state in
        switch state {
        case .int:
          CaseLet(
            state: /ContentFeature.Path.State.int,
            action: ContentFeature.Path.Action.int
          ) {
            IntView(store: $0)
          }
        }
      }
    }
  }

  struct IntFeature: ReducerProtocol {
    struct State: Equatable {
      var num: Int
      @PresentationState var dest: DestFeature.State? = nil
    }
    public enum Action {
      case showDest
      case dest(PresentationAction<DestFeature.Action>)
    }
    var body: some ReducerProtocolOf<Self> {
      Reduce { state, action in
        switch action {
        case .showDest:
          state.dest = .init()
          return .none

        case .dest:
          return .none
        }
      }
      .ifLet(\.$dest, action: /Action.dest) {
        DestFeature()
      }
    }
  }

  struct IntView: View {
    let store: StoreOf<IntFeature>

    var body: some View {
      WithViewStore(store, observe: { $0 }) { viewStore in
        VStack {
          Text("Number \(viewStore.num)")
          Button("Show 2") {
            viewStore.send(.showDest)
          }
        }
        .navigationDestination(
          store: store.scope(state: \.$dest, action: { .dest($0) })
        ) {
          DestView(store: $0)
        }
      }
    }
  }

  struct DestFeature: ReducerProtocol {
    struct State: Equatable {
    }
    enum Action {
    }
    var body: some ReducerProtocolOf<Self> {
      Reduce { state, action in
        switch action {
        }
      }
    }
  }

  struct DestView: View {
    let store: StoreOf<DestFeature>

    var body: some View {
      Text("Destination 2")
    }
  }

The Composable Architecture version information
0.55.1
Destination operating system
iOS 16.4
Xcode version information
Version 14.3.1 (14E300c)
Swift Compiler version information
swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-<IP_ADDRESS>.5 clang-14<IP_ADDRESS>.100)
Target: arm64-apple-macosx13.0

It looks like this can be fixed with the following patch to TCA:
diff --git a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift
index c417abedb..2ca2fbe57 100644
--- a/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift
+++ b/Sources/ComposableArchitecture/SwiftUI/NavigationStackStore.swift
@@ -105,9 +105,7 @@ public struct NavigationStackStore<State, Action, Root: View, Destination: View>
       self.root
         .environment(\.navigationDestinationType, State.self)
         .navigationDestination(for: Component<State>.self) { component in
-          self.destination(component)
-            .environment(\.navigationDestinationType, State.self)
-            .id(component.id)
+          NavigationDestinationView(component: component, destination: self.destination)
         }
     }
   }
@@ -144,6 +142,16 @@ public struct _NavigationLinkStoreContent<State, Label: View>: View {
   }
 }

+private struct NavigationDestinationView<State, Destination: View>: View {
+  let component: Component<State>
+  let destination: (Component<State>) -> Destination
+  var body: some View {
+    self.destination(component)
+      .environment(\.navigationDestinationType, State.self)
+      .id(component.id)
+  }
+}
+
 @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
 extension NavigationLink where Destination == Never {
   /// Creates a navigation link that presents the view corresponding to an element of

This workaround was based on this comment: https://github.com/pointfreeco/swift-composable-architecture/discussions/1842#discussioncomment-4708055


Fix tests and switch to pytest-catchlog

Current coverage is 98.29% (diff: 100%)

Merging #75 into master will increase coverage by 0.17%

@@             master        #75   diff @@
==========================================
  Files           155        155
  Lines         12306      12307     +1
  Methods           0          0
  Messages          0          0
  Branches          0          0
==========================================
+ Hits          12074      12097    +23
+ Misses          232        210    -22
  Partials          0          0

Powered by Codecov. Last update a0f7eb8...d75394d


Unable to call Google Font

Challenge Import a Google Font has an issue.
User Agent is: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36.
Please describe how to reproduce this issue, and include links to screenshots if possible.
 Get help from someone at FCC chatroom

@arun1595 is correct. Thanks @arun1595 :smile:


[Feature]: Add tags

Contact Details
No response
The Problem
|
How to Fix It
No response
Additional Context
No response
Specific File Path(s) on the Repo to be Modified/Refered to
No response
Code of Conduct

[X] I agree to follow this project's Code of Conduct

Duplicate of #109


Consider making SizeLimit a trait rather than an enum

-pub struct Deserializer<R> {
+pub struct Deserializer<R, L> {
     reader: R,
-    size_limit: SizeLimit,
+    size_limit: L,
     read: u64
 }

This way the Infinite case would not be slowed down by all the branching in Deserializer::read_bytes:
match self.size_limit {
    SizeLimit::Infinite => Ok(()),
    SizeLimit::Bounded(x) if self.read <= x => Ok(()),
    SizeLimit::Bounded(_) => Err(ErrorKind::SizeLimit.into())
}

Something like:
trait SizeLimit {
    fn check(&self, read: u64) -> Result<()>;
}

struct Infinite;
impl SizeLimit for Infinite {
    #[inline]
    fn check(&self, _read: u64) -> Result<()> {
        Ok(())
    }
}

struct Bounded(u64);
impl SizeLimit for Bounded {
    fn check(&self, read: u64) -> Result<()> {
        if read <= self.0 {
            Ok(())
        } else {
            Err(ErrorKind::SizeLimit.into())
        }
    }
}

impl<R: Read, L: SizeLimit> Deserializer<R, L> {
    fn read_bytes(&mut self, count: u64) -> Result<()> {
        self.read += count;
        self.size_limit.check(self.read)
    }
}

The compiler may be able to generate faster code in the Infinite case knowing that no branching is happening.

A possible further advantage would be in the Infinite case, the size_of Deserializer drops from 40 bytes to 24 bytes (assuming reading from &[u8]) and in the Bounded case it drops to 32 bytes.
&[u8] // 16 bytes
bincode::SizeLimit // 16 bytes :(
u64 // 8 bytes

I was actually just thinking about this!
You could even go further and have the trait be something like:
trait SizeLimit {
    type Storage;
    fn create_storage() -> Self::Storage;
    fn add(&self, size: u64, storage: &mut Storage) -> Result<()>;
}

Then the Deserializer would look like
struct Deserializer<R: Read, L: SizeLimit> {
   reader: R,
   limit: L,
   storage: L::Storage,

This way, the implementation of Infinite would be
impl SizeLimit for Infinite {
    type Storage = ();
    fn add(&self, size: &u64, storage: &mut ()) -> Result<()> {}

^ Then sizeof(Deserializer<R, Infinite>) == sizeof(R)

A simpler / possibly faster alternative:
trait SizeLimit {
    fn add(&mut self, n: u64) -> Result<()>;
}

That way it doesn't need a pointer dereference for both self and storage.

but then implementors of SizeLimit would need to carry their own storage.  This would make struct Bounded(u64) not work.
In addition, I don't think any implementors of SizeLimit would ever dereference self.

struct Bounded(u64) would work:
impl SizeLimit for Bounded {
    fn add(&mut self, n: u64) -> Result<()> {
        if self.0 >= n {
            self.0 -= n;
            Ok(())
        } else {
            Err(...)
        }
    }
}

Bounded would need to dereference self in order to find out what the bound is, right...? You were envisioning storing the bound in (*self).0 and storing the current pos in *storage.

Oh yeah, I suppose that does work.  I was thinking of how I could expand this to also do structure size estimation, but I think I know how to deal with that now.


Dev/pub and road mapping

Description
This is a small PR to add the visualisation of Pubs and Road Incidents that I made to the examples sections of the stack manager and uploader.
The relevant config files were copied to their respective example directory in TheWorldAvatar/Deploy/stacks/dynamic/examples/stack-data-uploader as well as links in README files to where to get the data.
I also created two folders in TheWorldAvatar/Deploy/stacks/dynamic/examples/stack-manager' to separate example config files for 'pylons-and=veg and pubs-and-road-incidents. The move explains the large number of files / lines changed.
Seán's pylons and veg files were never merged to main after being added as examples. Since I branched off of that branch to create Dev/pub and road mapping, those commits were carried here and are included in this PR.

I'll close this request and delete the branch in order to rename it


Fixed hover for clusters page, impruve speling fore go rapport card.

Fixes #424.

Coverage decreased (-0.02%) to 55.321% when pulling 8469bad9f3a5cc6e0650be427e50d4899ec1f149 on TheKLARKEN:master into 2bd4e007597c4e00ad676854518add3efc364f98 on supergiant:master.

 SG 1.0 Bug Squash: Issue 424 Events for cluster components don't appear in the console

LGTM